Skip to content

Commit 57a1cf0

Browse files
committed
Improve SQLite command and adapter overloads
- add convenient overloads for parameters and cancellation - simplify overloads for prepared SQLite commands - add select and update adapter overloads - centralize insert command text generation - materialize non-list field sequences once - validate non-empty field collections - allow additional parameters used by appended SQL expressions - update public documentation
1 parent a2d86be commit 57a1cf0

6 files changed

Lines changed: 583 additions & 123 deletions

File tree

‎README.md‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,38 @@
1111
## Features
1212

1313
- **Synchronized Database Access**: Coordinates operations through reentrant process-local locks, including shared locks for multiple connections targeting the same database.
14-
- **Transaction Scope Management**: Provides explicit transaction state tracking, prevents nested transactions, and supports coordinated execution through reentrant connection locks.
14+
- **Transaction Scope Management**: Provides explicit transaction state tracking, prevents nested transactions, preserves original exceptions when rollback fails, and supports coordinated execution through reentrant connection locks.
1515
- **Database Context Management**: Manages connection state, transaction lifetime, commit, rollback, and deterministic resource cleanup.
1616
- **ADO.NET Utilities**: Provides helpers for commands, parameters, readers, data adapters, schema inspection, and common SQLite operations.
1717
- **Database Migrations**: Applies ordered schema and data migrations transactionally, records migration history with optional version metadata, and validates that the applied history matches the configured migration sequence.
1818

19-
## Important Considerations
19+
## Why NuExt.System.Data.SQLite?
2020

21-
SQLite allows multiple readers but serializes writes according to the configured journal mode and transaction state. Long-running write transactions can delay other operations and increase the likelihood of lock timeouts.
21+
`NuExt.System.Data.SQLite` is designed as a performance-oriented infrastructure layer over ADO.NET rather than an ORM. It keeps SQL, commands, parameters, readers, and transactions explicit while removing repetitive connection, transaction, and resource lifecycle code.
2222

23-
The library's shared lock is intentionally process-local. Applications that access the same database from multiple processes must still rely on SQLite locking, timeouts, and appropriate journal configuration.
23+
The library supports both standalone DAL operations and coordinated units of work, protects complete database operation scopes during concurrent and reentrant execution, and provides forward-only migrations with deterministic ordering and strict history validation.
24+
25+
The result is a lightweight data access foundation that adds predictable resource ownership, transaction coordination, and schema evolution without introducing change tracking, SQL generation, or a custom query language.
2426

2527
## Concurrency and Locking
2628

27-
`SQLiteDbConnection` uses a reentrant process-local lock to coordinate database operations. When shared locking is enabled, connections targeting the same database use the same lock. When shared locking is disabled, each wrapper connection uses its own lock.
29+
SQLite provides file-level locking and allows concurrent access through multiple connections, but application-level operations often consist of several related steps that must remain synchronized as a single scope. A transaction may include opening a connection, executing multiple commands, committing or rolling back, and restoring the original connection state. Coordinating only the individual SQL commands is not sufficient because another operation could enter between these steps.
30+
31+
`SQLiteDbConnection` addresses this by using a reentrant process-local lock around complete database operation scopes. Reentrancy allows higher-level operations, DAL components, and helper methods to call each other while participating in the same synchronized scope without deadlocking.
32+
33+
When shared locking is enabled, connections targeting the same database use the same process-local lock. This serializes competing operation scopes within the current process, reduces write contention, and helps prevent avoidable `"database is locked"` errors. When shared locking is disabled, each wrapper connection uses its own lock.
34+
35+
The process-local lock complements SQLite locking rather than replacing it. It cannot coordinate access from other processes or external database tools, which must still rely on SQLite file locking, busy timeouts, and an appropriate journal mode.
36+
37+
Transactions can be used directly when the caller guarantees exclusive connection access. Concurrent or reentrant scenarios should keep the complete transaction lifetime within `AcquireLock`, `ExecuteInTransaction`, or a provider-managed `SQLiteDbContext` scope.
38+
39+
## Database Context Ownership
40+
41+
SQLite DAL operations support both provider-managed and caller-owned database contexts.
2842

29-
This coordination helps reduce competing write operations and `"database is locked"` errors originating within the same process. It does not replace SQLite file locking and cannot coordinate access from other processes or external database tools.
43+
When no context is supplied, the provider creates the connection and context, acquires the required lock, commits the transaction on success, rolls it back on failure, and releases all owned resources.
3044

31-
Transactions can be used directly when the caller guarantees exclusive connection access. Concurrent or reentrant scenarios should execute the entire transaction lifetime within `AcquireLock` or use `ExecuteInTransaction`.
45+
An existing `SQLiteDbContext` can instead be shared across multiple DAL components. In this mode, the caller retains ownership of the transaction and decides when to commit, roll back, and dispose the context.
3246

3347
## Quick Examples
3448

@@ -39,7 +53,6 @@ connection.ExecuteInTransaction(transaction =>
3953
{
4054
connection.ExecuteNonQuery(
4155
"UPDATE Movies SET Title = @Title WHERE Id = @Id",
42-
CommandBehavior.Default,
4356
cancellationToken,
4457
DbType.String.CreateInputParam("@Title", title),
4558
DbType.Int64.CreateInputParam("@Id", id));
@@ -77,7 +90,7 @@ SQLiteDbMigration[] migrations =
7790
migrations.Migrate(CreateConnection, "1.0.0", cancellationToken);
7891
```
7992

80-
Migrations are applied in the order in which they are registered. Applied migrations are recorded in the `__nuext_migrations` table together with their application time and optional version information. When a connection factory is used, the complete migration batch is executed within a synchronized transaction and is rolled back if any migration fails. Migrations can also use an existing `SQLiteDbContext`, allowing the caller to include migrations and application operations in the same transaction and retain responsibility for committing or rolling it back.
93+
Migrations are applied in the order in which they are registered. Applied migrations are recorded in the `__nuext_migrations` table together with their application time and optional version information. When a connection factory is used, the complete migration batch is executed within a synchronized transaction and is rolled back if any migration fails. Migrations can also run within an existing `SQLiteDbContext`, allowing migration and application operations to share the same caller-owned transaction.
8194

8295
## Commonly Used Types
8396

@@ -102,7 +115,7 @@ Or via Visual Studio:
102115
2. Search for `NuExt.System.Data.SQLite`.
103116
3. Click "Install".
104117

105-
### Compatibility
118+
## Compatibility
106119

107120
**.NET Standard 2.0+, .NET 8/9/10 and .NET Framework 4.6.2+**
108121

‎samples/MoviesAppSample/DataAccess/MovieDal.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public ValueTask<bool> DeleteMovieAsync(SQLiteDbContext? context, long id, Cance
3131

3232
using var command = ctx.Connection.CreateCommandDelete(TableName, "WHERE Id=@Id",
3333
DbType.Int64.CreateInputParam("@Id", id));
34-
int affected = ctx.Connection.ExecuteNonQuery(command, cancellationToken: cancellationToken);
34+
int affected = ctx.Connection.ExecuteNonQuery(command, cancellationToken);
3535
return affected == 1;
3636
}, cancellationToken);
3737
}
@@ -55,7 +55,7 @@ public ValueTask<bool> DeleteMoviesAsync(SQLiteDbContext? context, IEnumerable<l
5555
{
5656
bool result = await personDal.DeleteMoviePersonsAsync(ctx, id, cancellationToken);
5757
command.Parameters["@Id"].Value = id;
58-
affected += ctx.Connection.ExecuteNonQuery(command, cancellationToken: cancellationToken);
58+
affected += ctx.Connection.ExecuteNonQuery(command, cancellationToken);
5959
}
6060
return affected > 0;
6161
}, cancellationToken);

‎src/NuExt.System.Data.SQLite.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ItemGroup>
1717

1818
<ItemGroup Condition="'$(NuExt_UseLocalProjects)' != 'true'">
19-
<PackageReference Include="NuExt.System.Data" Version="0.8.0" />
19+
<PackageReference Include="NuExt.System.Data" Version="0.8.1" />
2020
</ItemGroup>
2121

2222
<ItemGroup Condition="'$(NuExt_UseLocalProjects)' == 'true'">

0 commit comments

Comments
 (0)