33using System . Text ;
44using EntityFrameworkCore . Sqlite . Concurrency . Models ;
55using Microsoft . Data . Sqlite ;
6+ using Microsoft . Extensions . Logging ;
67
78
89namespace EntityFrameworkCore . Sqlite . Concurrency ;
@@ -153,15 +154,36 @@ public static void ApplyRuntimePragmas(DbConnection connection, SqliteConcurrenc
153154 {
154155 if ( ! _initializedDatabases . ContainsKey ( dataSource ) )
155156 {
157+ var logger = options . LoggerFactory ? . CreateLogger ( nameof ( SqliteConnectionEnhancer ) ) ;
158+
159+ // WAL mode is initialized first and in isolation so that a SQLITE_READONLY
160+ // failure (most commonly SQLITE_READONLY_CANTINIT on Windows when the .db-shm
161+ // file cannot be created or has wrong permissions) is caught and logged as a
162+ // warning rather than crashing the entire interceptor. Without WAL the database
163+ // still functions; concurrent write performance is reduced because reads and
164+ // writes cannot proceed simultaneously.
165+ try
166+ {
167+ using var walCommand = sqliteConnection . CreateCommand ( ) ;
168+ walCommand . CommandText = "PRAGMA journal_mode = WAL;" ;
169+ walCommand . ExecuteNonQuery ( ) ;
170+ }
171+ catch ( SqliteException ex ) when ( ex . SqliteErrorCode == 8 ) // SQLITE_READONLY
172+ {
173+ logger ? . LogWarning ( ex ,
174+ "Could not enable WAL mode for '{DataSource}' " +
175+ "(SQLITE_READONLY, extended code: {Extended}). " +
176+ "The database will use the default journal mode — concurrent read/write " +
177+ "performance will be reduced. To resolve: ensure the database directory " +
178+ "is writable and delete any stale .db-shm / .db-wal files alongside the " +
179+ "database, then restart the application." ,
180+ dataSource , ex . SqliteExtendedErrorCode ) ;
181+ }
182+
156183 try
157184 {
158185 using var initCommand = sqliteConnection . CreateCommand ( ) ;
159186 initCommand . CommandText = $@ "
160- -- WAL mode: readers and writers can proceed concurrently (readers never block
161- -- writers and writers never block readers). The WAL file must remain on the
162- -- same machine as the database — do not use WAL on network filesystems.
163- PRAGMA journal_mode = WAL;
164-
165187 -- 4 096 bytes aligns with modern OS page sizes (ext4, NTFS, APFS) and is the
166188 -- SQLite recommended default. Changing page_size after data exists has no effect
167189 -- without a VACUUM, so this is a no-op on pre-existing databases.
@@ -183,15 +205,18 @@ public static void ApplyRuntimePragmas(DbConnection connection, SqliteConcurrenc
183205 PRAGMA wal_autocheckpoint = { options . WalAutoCheckpoint } ;
184206 " ;
185207 initCommand . ExecuteNonQuery ( ) ;
186-
187- _initializedDatabases . TryAdd ( dataSource , true ) ;
188208 }
189- catch
209+ catch ( Exception ex )
190210 {
191- // Ensure we don't leave it marked as initialized if it failed
192- _initializedDatabases . TryRemove ( dataSource , out _ ) ;
193- throw ;
211+ logger ? . LogWarning ( ex ,
212+ "One or more database-initialization PRAGMAs failed for '{DataSource}'. " +
213+ "The connection-scoped PRAGMAs (busy_timeout, cache_size, etc.) will still be applied." ,
214+ dataSource ) ;
194215 }
216+
217+ // Mark as initialized regardless of partial failures above so that the
218+ // failed PRAGMAs are not retried on every subsequent connection open.
219+ _initializedDatabases . TryAdd ( dataSource , true ) ;
195220 }
196221 }
197222 }
0 commit comments