@@ -57,11 +57,37 @@ public static SemaphoreSlim GetWriteLock(string connectionString)
5757 /// <param name="connectionString">The connection string.</param>
5858 /// <param name="options">The concurrency options.</param>
5959 /// <returns>A <see cref="SqliteConcurrencyInterceptor"/> instance.</returns>
60+ /// <exception cref="ArgumentException">Thrown when the provided <paramref name="options"/> do not match the options of an existing interceptor for the same <paramref name="connectionString"/>.</exception>
61+ /// <remarks>
62+ /// Callers must use consistent options for the same connection string, as interceptors are cached and shared.
63+ /// </remarks>
6064 public static SqliteConcurrencyInterceptor GetInterceptor ( string connectionString , SqliteConcurrencyOptions options )
6165 {
66+ if ( _interceptors . TryGetValue ( connectionString , out var existingInterceptor ) )
67+ {
68+ if ( ! existingInterceptor . Options . Equals ( options ) )
69+ {
70+ throw new ArgumentException (
71+ $ "Mismatched SqliteConcurrencyOptions for connection string. " +
72+ $ "Existing options: { FormatOptions ( existingInterceptor . Options ) } , " +
73+ $ "Incoming options: { FormatOptions ( options ) } . " +
74+ $ "Interceptors are shared per connection string and must be configured consistently.",
75+ nameof ( options ) ) ;
76+ }
77+ return existingInterceptor ;
78+ }
79+
6280 return _interceptors . GetOrAdd ( connectionString , cs => new SqliteConcurrencyInterceptor ( options , cs ) ) ;
6381 }
6482
83+ private static string FormatOptions ( SqliteConcurrencyOptions options )
84+ {
85+ return $ "[MaxRetryAttempts={ options . MaxRetryAttempts } , " +
86+ $ "BusyTimeout={ options . BusyTimeout } , " +
87+ $ "CommandTimeout={ options . CommandTimeout } , " +
88+ $ "WalAutoCheckpoint={ options . WalAutoCheckpoint } ]";
89+ }
90+
6591 private static string ComputeOptimizedConnectionString ( string originalConnectionString )
6692 {
6793 var builder = new SqliteConnectionStringBuilder ( originalConnectionString )
@@ -98,20 +124,34 @@ public static void ApplyRuntimePragmas(DbConnection connection, SqliteConcurrenc
98124 var dataSource = builder . DataSource ;
99125
100126 // 1. Database-scoped Pragmas - Run once per process
101- if ( _initializedDatabases . TryAdd ( dataSource , true ) )
127+ if ( ! _initializedDatabases . ContainsKey ( dataSource ) )
102128 {
103129 var lockObj = _pragmaLocks . GetOrAdd ( dataSource , _ => new object ( ) ) ;
104130 lock ( lockObj )
105131 {
106- using var initCommand = sqliteConnection . CreateCommand ( ) ;
107- initCommand . CommandText = $@ "
108- PRAGMA journal_mode = WAL;
109- PRAGMA page_size = 4096;
110- PRAGMA auto_vacuum = INCREMENTAL;
111- PRAGMA journal_size_limit = 134217728;
112- PRAGMA wal_autocheckpoint = { options . WalAutoCheckpoint } ;
113- " ;
114- initCommand . ExecuteNonQuery ( ) ;
132+ if ( ! _initializedDatabases . ContainsKey ( dataSource ) )
133+ {
134+ try
135+ {
136+ using var initCommand = sqliteConnection . CreateCommand ( ) ;
137+ initCommand . CommandText = $@ "
138+ PRAGMA journal_mode = WAL;
139+ PRAGMA page_size = 4096;
140+ PRAGMA auto_vacuum = INCREMENTAL;
141+ PRAGMA journal_size_limit = 134217728;
142+ PRAGMA wal_autocheckpoint = { options . WalAutoCheckpoint } ;
143+ " ;
144+ initCommand . ExecuteNonQuery ( ) ;
145+
146+ _initializedDatabases . TryAdd ( dataSource , true ) ;
147+ }
148+ catch
149+ {
150+ // Ensure we don't leave it marked as initialized if it failed
151+ _initializedDatabases . TryRemove ( dataSource , out _ ) ;
152+ throw ;
153+ }
154+ }
115155 }
116156 }
117157
0 commit comments