diff --git a/.editorconfig b/.editorconfig index 1ff0d18..fae3e2c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -384,7 +384,13 @@ dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case +# Test projects: concurrency fixtures intentionally capture DbContexts in parallel +# lambdas that complete before the outer `using` disposes them. Rider's +# AccessToDisposedClosure inspection fires on these false positives. +[tests/**/*.cs] +resharper_access_to_disposed_closure_highlighting = none + diff --git a/README.md b/README.md index 94ec782..4e7f2e4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Pessimistic locking for EF Core. Supports PostgreSQL, MySQL, and SQL Server. -- **Row-level locks** — `ForUpdate()` / `ForShare()` LINQ extension methods scoped to a transaction +- **Row-level locks** — `ForUpdate()` / `ForShare()` LINQ extensions, scoped to a transaction - **Distributed locks** — `AcquireDistributedLockAsync()` session-scoped advisory locks, no transaction required ## Installation @@ -34,14 +34,14 @@ services.AddDbContext(o => .UseLocking()); ``` -## Usage +## Row-level locks -All locking queries **require an active transaction**. +All row-level locking queries **require an active transaction**. ```csharp await using var tx = await ctx.Database.BeginTransactionAsync(); -// Basic exclusive lock (FOR UPDATE / WITH (UPDLOCK, HOLDLOCK, ROWLOCK)) +// Exclusive lock (FOR UPDATE / WITH (UPDLOCK, HOLDLOCK, ROWLOCK)) var product = await ctx.Products .Where(p => p.Id == id) .ForUpdate() @@ -53,7 +53,7 @@ var available = await ctx.Products .ForUpdate(LockBehavior.SkipLocked) .ToListAsync(); -// Fail immediately if lock cannot be acquired +// Fail immediately if the lock cannot be acquired var row = await ctx.Products .Where(p => p.Id == id) .ForUpdate(LockBehavior.NoWait) @@ -74,31 +74,23 @@ var row = await ctx.Products await tx.CommitAsync(); ``` -### PostgreSQL-only: ForNoKeyUpdate and ForKeyShare +### PostgreSQL-only modes -These modes are available when using the `EntityFrameworkCore.Locking.PostgreSQL` package: +Available when referencing `EntityFrameworkCore.Locking.PostgreSQL`: ```csharp // FOR NO KEY UPDATE — blocks writers but allows FOR KEY SHARE (FK lookups) -var row = await ctx.Products - .Where(p => p.Id == id) - .ForNoKeyUpdate() - .FirstOrDefaultAsync(); +await ctx.Products.Where(p => p.Id == id).ForNoKeyUpdate().FirstOrDefaultAsync(); // FOR KEY SHARE — minimal shared lock, only blocks FOR UPDATE -// Useful for FK-referencing queries that should not block non-key updates -var row = await ctx.Products - .Where(p => p.Id == id) - .ForKeyShare() - .FirstOrDefaultAsync(); +await ctx.Products.Where(p => p.Id == id).ForKeyShare().FirstOrDefaultAsync(); ``` -### Include with locking (PostgreSQL) +### Include with locking -PostgreSQL automatically scopes the lock to the root table when a collection `Include` is present (emits `FOR UPDATE OF "t"`), so you can use `Include` directly without `AsSplitQuery()`: +On PostgreSQL, collection `Include` emits `FOR UPDATE OF "t"` automatically to handle the outer join, so you can use `Include` directly without `AsSplitQuery()`: ```csharp -// Works — FOR UPDATE OF "p" is emitted automatically var product = await ctx.Products .Include(p => p.OrderLines) .Where(p => p.Id == id) @@ -106,9 +98,9 @@ var product = await ctx.Products .FirstOrDefaultAsync(); ``` -### Queue processing pattern +### Queue processing -A common use of `ForUpdate(LockBehavior.SkipLocked)` is a worker queue where multiple consumers race to claim items: +`ForUpdate(LockBehavior.SkipLocked)` is the standard building block for a worker queue where multiple consumers race to claim items: ```csharp await using var tx = await ctx.Database.BeginTransactionAsync(); @@ -127,46 +119,54 @@ await ctx.SaveChangesAsync(); await tx.CommitAsync(); ``` -## Distributed locks +### Lock modes and behaviors + +| Method | Generated SQL | +|--------|--------------| +| `ForUpdate()` | `FOR UPDATE` / `WITH (UPDLOCK, HOLDLOCK, ROWLOCK)` | +| `ForUpdate(LockBehavior.NoWait)` | `FOR UPDATE NOWAIT` / `SET LOCK_TIMEOUT 0` | +| `ForUpdate(LockBehavior.SkipLocked)` | `FOR UPDATE SKIP LOCKED` (PG/MySQL) / `WITH (UPDLOCK, ROWLOCK, READPAST)` (SQL Server) | +| `ForUpdate(LockBehavior.Wait, timeout)` | `SET LOCAL lock_timeout` (PG) / `SET SESSION innodb_lock_wait_timeout` (MySQL) / `SET LOCK_TIMEOUT` (SQL Server) | +| `ForShare()` | `FOR SHARE` (PostgreSQL/MySQL only) | +| `ForNoKeyUpdate()` | `FOR NO KEY UPDATE` (PostgreSQL only) | +| `ForKeyShare()` | `FOR KEY SHARE` (PostgreSQL only) | -Distributed (advisory) locks let you coordinate across processes without tying the lock to a database row or transaction. They are session-scoped — the lock is held until you dispose the handle, or until the connection drops. +## Distributed locks -No transaction is required. +Advisory locks coordinate across processes without tying the lock to a row or transaction. They are session-scoped — held until the handle is disposed or the connection drops. ```csharp -// Acquire — blocks until available (optional timeout) +// Blocks until available await using var handle = await ctx.Database.AcquireDistributedLockAsync("invoice:generate"); -// ... critical section ... -// lock released automatically on dispose -// With a timeout — throws LockTimeoutException if not acquired within 5 s +// With a timeout — throws LockTimeoutException if not acquired in time await using var handle = await ctx.Database.AcquireDistributedLockAsync( "report:daily", TimeSpan.FromSeconds(5)); -// With cancellation token +// With cancellation await using var handle = await ctx.Database.AcquireDistributedLockAsync( "report:daily", timeout: null, cancellationToken: ct); -// TryAcquire — returns null immediately if already held +// Non-blocking — returns null immediately if held var handle = await ctx.Database.TryAcquireDistributedLockAsync("invoice:generate"); if (handle is null) return Results.Conflict("Another process is generating the invoice."); await using (handle) { /* critical section */ } -// Synchronous variants are also available +// Synchronous variants using var handle = ctx.Database.AcquireDistributedLock("report:daily"); var handle = ctx.Database.TryAcquireDistributedLock("report:daily"); -// Check support at runtime +// Runtime check if (ctx.Database.SupportsDistributedLocks()) { ... } ``` ### Lock keys -Keys are plain strings, up to **255 characters**. The library handles provider-specific encoding internally: +Keys are plain strings, up to **255 characters**. Provider-specific encoding is handled internally: -- **PostgreSQL** — hashed to a `bigint` via XxHash32 with a namespace prefix (`"EFLK"`); the hash is computed in-process so no extra round-trip is needed. -- **MySQL** — passed as-is for keys ≤ 64 UTF-8 bytes; longer keys are SHA-256 hashed to `lock:` (64 chars). The `lock:` prefix is reserved. +- **PostgreSQL** — hashed to a `bigint` via XxHash32 with a namespace prefix (`"EFLK"`), computed in-process. +- **MySQL** — passed as-is for keys ≤ 64 UTF-8 bytes; longer keys are SHA-256 hashed to `lock:`. The `lock:` prefix is reserved. - **SQL Server** — passed as-is (max 255 chars, enforced upstream). ### Provider-specific behavior @@ -177,50 +177,9 @@ Keys are plain strings, up to **255 characters**. The library handles provider-s | Timeout | `SET LOCAL lock_timeout` (ms) | `GET_LOCK(@key, seconds)` — rounded up to 1 s | `@LockTimeout` ms | | Cancellation | Driver-level (best-effort) | `KILL QUERY` side-channel | Attention signal | -**MySQL timeout precision:** `GET_LOCK` timeout is in whole seconds. Sub-second timeouts are rounded up to 1 second. - -**Cancellation caveat:** advisory lock SQL is a blocking database call. Cancellation sends a cancel signal to the driver; if the driver does not honor it before the timeout fires, the call completes via timeout. Always combine a `timeout` with the `CancellationToken` for bounded waits. - -### Exception handling - -```csharp -try -{ - await using var handle = await ctx.Database.AcquireDistributedLockAsync( - "report:daily", TimeSpan.FromSeconds(5)); -} -catch (LockTimeoutException) -{ - // Not acquired within the timeout -} -catch (LockAlreadyHeldException ex) -{ - // Same DbContext + connection attempted to acquire the same key twice - // ex.Key contains the key name -} -catch (LockingConfigurationException) -{ - // Provider does not support distributed locks, or UseLocking() was not called -} -``` - -`LockAlreadyHeldException` is thrown synchronously before any database call when the same `(DbContext, connection, key)` triple is already registered. Acquiring the same key from two **different** `DbContext` instances on different connections will block (or return `null` for `TryAcquire`) as expected. - -## Lock modes and behaviors - -| Method | Generated SQL | -|--------|--------------| -| `ForUpdate()` | `FOR UPDATE` / `WITH (UPDLOCK, HOLDLOCK, ROWLOCK)` | -| `ForUpdate(LockBehavior.NoWait)` | `FOR UPDATE NOWAIT` / `SET LOCK_TIMEOUT 0` | -| `ForUpdate(LockBehavior.SkipLocked)` | `FOR UPDATE SKIP LOCKED` (PG/MySQL) / `WITH (UPDLOCK, ROWLOCK, READPAST)` (SQL Server) | -| `ForUpdate(LockBehavior.Wait, timeout)` | `SET LOCAL lock_timeout = '500ms'` (PG) / `SET SESSION innodb_lock_wait_timeout` (MySQL) / `SET LOCK_TIMEOUT 500` (SQL Server) | -| `ForShare()` | `FOR SHARE` (PostgreSQL/MySQL only) | -| `ForNoKeyUpdate()` | `FOR NO KEY UPDATE` (PostgreSQL only) | -| `ForKeyShare()` | `FOR KEY SHARE` (PostgreSQL only) | - -## Exception handling +Advisory lock SQL is a blocking database call. Cancellation is a best-effort signal to the driver; if the driver does not honor it before the timeout fires, the call completes via timeout. Combine a `timeout` with the `CancellationToken` for bounded waits. -Lock failures throw typed exceptions from `EntityFrameworkCore.Locking.Exceptions`: +## Exceptions ```csharp try @@ -230,30 +189,38 @@ try .ForUpdate(LockBehavior.NoWait) .FirstOrDefaultAsync(); } -catch (LockTimeoutException ex) +catch (LockTimeoutException) { - // Lock could not be acquired (NOWAIT or timeout exceeded) + // NOWAIT failed or timeout exceeded } -catch (DeadlockException ex) +catch (DeadlockException) { // Deadlock detected — retry the transaction } -catch (LockingConfigurationException ex) +catch (LockAlreadyHeldException ex) +{ + // Distributed lock: same key acquired twice on the same connection. + // ex.Key contains the key name. +} +catch (LockingConfigurationException) { // Programmer error: missing transaction, unsupported query shape, - // or unsupported lock mode for this provider + // or provider does not support the requested mode. } ``` +`LockAlreadyHeldException` is thrown synchronously before any database call when the same `(DbContext, connection, key)` triple is already registered. Acquiring the same key from two **different** `DbContext` instances on different connections will block (or return `null` for `TryAcquire`) as expected. + **Exception hierarchy:** -- `LockingException` (base) + +- `LockingException` - `LockAcquisitionFailedException` - - `LockTimeoutException` — timeout or NOWAIT failure - - `DeadlockException` — deadlock victim - - `LockAlreadyHeldException` — same key acquired twice on the same connection (distributed locks) - - `LockingConfigurationException` — programmer error (missing transaction, unsupported query shape, provider not configured) + - `LockTimeoutException` + - `DeadlockException` + - `LockAlreadyHeldException` + - `LockingConfigurationException` -## Provider limitations +## Provider support | Feature | PostgreSQL | MySQL | SQL Server | |---------|-----------|-------|-----------| @@ -265,26 +232,24 @@ catch (LockingConfigurationException ex) | `NoWait` | ✓ | ✓ | ✓ | | Wait with timeout | ✓ (ms) | ✓ (ceil to 1s) | ✓ (ms) | -`ForNoKeyUpdate` and `ForKeyShare` are PostgreSQL-only extension methods available when the `EntityFrameworkCore.Locking.PostgreSQL` package is installed. Using `ForShare` on SQL Server throws `LockingConfigurationException`. - -**SQL Server `SkipLocked` limitation:** SQL Server uses `WITH (UPDLOCK, ROWLOCK, READPAST)` instead of `SKIP LOCKED`. `READPAST` only skips rows held under row-level or page-level locks — rows under a table-level lock are blocked rather than skipped. For typical queue-processing workloads this behaves identically to `SKIP LOCKED` on PostgreSQL/MySQL. +SQL Server uses `WITH (UPDLOCK, ROWLOCK, READPAST)` for `SkipLocked` — `READPAST` skips rows held under row-level or page-level locks, but rows under a table-level lock are blocked rather than skipped. -**MySQL timeout precision:** MySQL's `innodb_lock_wait_timeout` is in whole seconds. Sub-second timeouts are rounded up to 1 second. +MySQL's `innodb_lock_wait_timeout` is in whole seconds, so sub-second timeouts are rounded up to 1 second. ## Unsupported query shapes -`UNION`, `EXCEPT`, `INTERSECT` with locking throw `LockingConfigurationException` at query execution time. Use per-query locks on individual queries before combining results. +`UNION`, `EXCEPT`, `INTERSECT` combined with locking throw `LockingConfigurationException` at query execution time. Lock individual queries before combining results. -`AsSplitQuery()` combined with locking throws `LockingConfigurationException` — use regular `Include()` instead (on PostgreSQL, `FOR UPDATE OF` is emitted automatically to handle outer joins). +`AsSplitQuery()` combined with locking throws `LockingConfigurationException` — use regular `Include()` instead. On PostgreSQL, `FOR UPDATE OF` is emitted automatically to handle the outer join. ## Supported database versions -| Database | Minimum version | Notes | -|----------|----------------|-------| -| PostgreSQL | **14** | Default minimum for Npgsql 8.x. PG 12+ works if you call `.SetPostgresVersion(12, 0)` in `UseNpgsql`. All locking features (`FOR NO KEY UPDATE`, `FOR KEY SHARE`, `SKIP LOCKED`, `NOWAIT`) have been available since PG 9.3/9.5. | -| MySQL | **8.0** | `FOR SHARE`, `SKIP LOCKED`, and `NOWAIT` were introduced in MySQL 8.0.1. MySQL 5.7 is not supported. | -| MariaDB | **10.6** | `SKIP LOCKED` requires 10.6+. `NOWAIT` requires 10.3+. `ForShare` emits `LOCK IN SHARE MODE` (MariaDB does not support the `FOR SHARE` syntax). | -| SQL Server | **2019** | All hints (`UPDLOCK`, `HOLDLOCK`, `ROWLOCK`, `READPAST`) and `SET LOCK_TIMEOUT` are available on all supported versions. Azure SQL Database is also supported. | +| Database | Minimum | Notes | +|----------|---------|-------| +| PostgreSQL | 14 | Default minimum for Npgsql 8.x. PG 12+ works if you call `.SetPostgresVersion(12, 0)` in `UseNpgsql`. | +| MySQL | 8.0 | `FOR SHARE`, `SKIP LOCKED`, and `NOWAIT` were introduced in 8.0.1. MySQL 5.7 is not supported. | +| MariaDB | 10.6 | `SKIP LOCKED` requires 10.6+. `NOWAIT` requires 10.3+. `ForShare` emits `LOCK IN SHARE MODE`. | +| SQL Server | 2019 | Azure SQL Database is also supported. | ## Target frameworks @@ -292,13 +257,13 @@ catch (LockingConfigurationException ex) ## Benchmarks -The `benchmarks/` directory contains BenchmarkDotNet benchmarks measuring the overhead added by the locking SQL generator and interceptor across all three providers. +The `benchmarks/` directory contains BenchmarkDotNet benchmarks measuring the overhead added by the SQL generator and interceptor. ```bash dotnet run -c Release --project benchmarks/EntityFrameworkCore.Locking.Benchmarks -- --version= ``` -The `--version` argument is required and labels the results folder (`benchmarks/EntityFrameworkCore.Locking.Benchmarks/results/v/`). Additional BenchmarkDotNet arguments (e.g. `--filter '*SqlGeneration*'`) can be appended after. +`--version` is required and labels the results folder (`results/v/`). Additional BenchmarkDotNet arguments (e.g. `--filter '*SqlGeneration*'`) can be appended. ## License diff --git a/src/EntityFrameworkCore.Locking.MySql/MySqlLockingServiceCollectionExtensions.cs b/src/EntityFrameworkCore.Locking.MySql/MySqlLockingServiceCollectionExtensions.cs index ae516df..3f6423d 100644 --- a/src/EntityFrameworkCore.Locking.MySql/MySqlLockingServiceCollectionExtensions.cs +++ b/src/EntityFrameworkCore.Locking.MySql/MySqlLockingServiceCollectionExtensions.cs @@ -7,9 +7,7 @@ namespace EntityFrameworkCore.Locking.MySql; public static class MySqlLockingServiceCollectionExtensions { - /// - /// Adds row-level locking support for MySQL (Pomelo). Call after UseMySql(). - /// + /// Enables the locking interceptor for MySQL (Pomelo). Call after UseMySql(). public static DbContextOptionsBuilder UseLocking( this DbContextOptionsBuilder optionsBuilder ) @@ -19,9 +17,7 @@ this DbContextOptionsBuilder optionsBuilder return optionsBuilder; } - /// - /// Adds row-level locking support for MySQL (Pomelo). Call after UseMySql(). - /// + /// public static DbContextOptionsBuilder UseLocking(this DbContextOptionsBuilder optionsBuilder) { var extension = new LockingOptionsExtension(new MySqlLockingProvider()); diff --git a/src/EntityFrameworkCore.Locking.PostgreSQL/PostgresLockingServiceCollectionExtensions.cs b/src/EntityFrameworkCore.Locking.PostgreSQL/PostgresLockingServiceCollectionExtensions.cs index 2a4d424..75bb22e 100644 --- a/src/EntityFrameworkCore.Locking.PostgreSQL/PostgresLockingServiceCollectionExtensions.cs +++ b/src/EntityFrameworkCore.Locking.PostgreSQL/PostgresLockingServiceCollectionExtensions.cs @@ -7,9 +7,7 @@ namespace EntityFrameworkCore.Locking.PostgreSQL; public static class PostgresLockingServiceCollectionExtensions { - /// - /// Adds row-level locking support for PostgreSQL (Npgsql). Call after UseNpgsql(). - /// + /// Enables the locking interceptor for PostgreSQL (Npgsql). Call after UseNpgsql(). public static DbContextOptionsBuilder UseLocking( this DbContextOptionsBuilder optionsBuilder ) @@ -19,9 +17,7 @@ this DbContextOptionsBuilder optionsBuilder return optionsBuilder; } - /// - /// Adds row-level locking support for PostgreSQL (Npgsql). Call after UseNpgsql(). - /// + /// public static DbContextOptionsBuilder UseLocking(this DbContextOptionsBuilder optionsBuilder) { var extension = new LockingOptionsExtension(new PostgresLockingProvider()); diff --git a/src/EntityFrameworkCore.Locking.SqlServer/SqlServerLockingServiceCollectionExtensions.cs b/src/EntityFrameworkCore.Locking.SqlServer/SqlServerLockingServiceCollectionExtensions.cs index 418546a..da5f725 100644 --- a/src/EntityFrameworkCore.Locking.SqlServer/SqlServerLockingServiceCollectionExtensions.cs +++ b/src/EntityFrameworkCore.Locking.SqlServer/SqlServerLockingServiceCollectionExtensions.cs @@ -7,9 +7,7 @@ namespace EntityFrameworkCore.Locking.SqlServer; public static class SqlServerLockingServiceCollectionExtensions { - /// - /// Adds row-level locking support for SQL Server. Call after UseSqlServer(). - /// + /// Enables the locking interceptor for SQL Server. Call after UseSqlServer(). public static DbContextOptionsBuilder UseLocking( this DbContextOptionsBuilder optionsBuilder ) @@ -19,9 +17,7 @@ this DbContextOptionsBuilder optionsBuilder return optionsBuilder; } - /// - /// Adds row-level locking support for SQL Server. Call after UseSqlServer(). - /// + /// public static DbContextOptionsBuilder UseLocking(this DbContextOptionsBuilder optionsBuilder) { var extension = new LockingOptionsExtension(new SqlServerLockingProvider()); diff --git a/src/EntityFrameworkCore.Locking/Abstractions/ILockingProvider.cs b/src/EntityFrameworkCore.Locking/Abstractions/ILockingProvider.cs index c8397a3..ebe53ce 100644 --- a/src/EntityFrameworkCore.Locking/Abstractions/ILockingProvider.cs +++ b/src/EntityFrameworkCore.Locking/Abstractions/ILockingProvider.cs @@ -1,9 +1,6 @@ namespace EntityFrameworkCore.Locking.Abstractions; -/// -/// Root provider abstraction. Phase 1 exposes only row-level locking. -/// Phase 2 adds advisory locks by implementing AdvisoryLockProvider — non-breaking via C# DIM. -/// +/// Root provider abstraction for the locking library. public interface ILockingProvider { /// Row-level lock SQL generator for this provider. @@ -15,9 +12,6 @@ public interface ILockingProvider /// Translates provider-specific database exceptions to typed locking exceptions. IExceptionTranslator ExceptionTranslator { get; } - /// - /// Phase 2 seam: advisory lock provider. Returns null in Phase 1. - /// Phase 2 providers override this default interface member. - /// + /// Advisory lock provider, or null if the provider does not support distributed locks. IAdvisoryLockProvider? AdvisoryLockProvider => null; } diff --git a/tests/EntityFrameworkCore.Locking.MySql.Tests/ConcurrencyTests.cs b/tests/EntityFrameworkCore.Locking.MySql.Tests/ConcurrencyTests.cs index 1db9e9d..4c879c3 100644 --- a/tests/EntityFrameworkCore.Locking.MySql.Tests/ConcurrencyTests.cs +++ b/tests/EntityFrameworkCore.Locking.MySql.Tests/ConcurrencyTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using EntityFrameworkCore.Locking.MySql.Tests.Fixtures; using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; diff --git a/tests/EntityFrameworkCore.Locking.MySql.Tests/ExceptionTranslationTests.cs b/tests/EntityFrameworkCore.Locking.MySql.Tests/ExceptionTranslationTests.cs index a08d8a0..8e33efd 100644 --- a/tests/EntityFrameworkCore.Locking.MySql.Tests/ExceptionTranslationTests.cs +++ b/tests/EntityFrameworkCore.Locking.MySql.Tests/ExceptionTranslationTests.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.MySql; using MySqlConnector; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.MySql.Tests/IntegrationTests.LockModeTests.cs b/tests/EntityFrameworkCore.Locking.MySql.Tests/IntegrationTests.LockModeTests.cs index 8237a44..059ce50 100644 --- a/tests/EntityFrameworkCore.Locking.MySql.Tests/IntegrationTests.LockModeTests.cs +++ b/tests/EntityFrameworkCore.Locking.MySql.Tests/IntegrationTests.LockModeTests.cs @@ -1,6 +1,5 @@ using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.MySql.Tests/SqlGenerationTests.cs b/tests/EntityFrameworkCore.Locking.MySql.Tests/SqlGenerationTests.cs index 11520e5..21f3e75 100644 --- a/tests/EntityFrameworkCore.Locking.MySql.Tests/SqlGenerationTests.cs +++ b/tests/EntityFrameworkCore.Locking.MySql.Tests/SqlGenerationTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking.MySql; using Xunit; namespace EntityFrameworkCore.Locking.MySql.Tests; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ConcurrencyTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ConcurrencyTests.cs index 284d2ea..c903611 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ConcurrencyTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ConcurrencyTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using EntityFrameworkCore.Locking.PostgreSQL.Tests.Fixtures; using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ExceptionTranslationTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ExceptionTranslationTests.cs index bd38b5f..f7c45f2 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ExceptionTranslationTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/ExceptionTranslationTests.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.PostgreSQL; using Npgsql; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.LockModeTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.LockModeTests.cs index 0828b4b..c53a913 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.LockModeTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.LockModeTests.cs @@ -1,7 +1,5 @@ using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.PostgreSQL; -using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryShapeTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryShapeTests.cs index 97b5de8..84c1371 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryShapeTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryShapeTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using EntityFrameworkCore.Locking.Exceptions; using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryStringTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryStringTests.cs index 82c34da..77c3f23 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryStringTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.QueryStringTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking.PostgreSQL; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.cs index c5a5220..e37749f 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/IntegrationTests.cs @@ -1,4 +1,3 @@ -using EntityFrameworkCore.Locking.PostgreSQL; using EntityFrameworkCore.Locking.PostgreSQL.Tests.Fixtures; using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/SqlGenerationTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/SqlGenerationTests.cs index b390c8f..0048e17 100644 --- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/SqlGenerationTests.cs +++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/SqlGenerationTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking.PostgreSQL; using Xunit; namespace EntityFrameworkCore.Locking.PostgreSQL.Tests; diff --git a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/ExceptionTranslationTests.cs b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/ExceptionTranslationTests.cs index 6f7f3b2..326814d 100644 --- a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/ExceptionTranslationTests.cs +++ b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/ExceptionTranslationTests.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.SqlServer; using Microsoft.Data.SqlClient; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/IntegrationTests.LockModeTests.cs b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/IntegrationTests.LockModeTests.cs index 83d7db1..bed7dcd 100644 --- a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/IntegrationTests.LockModeTests.cs +++ b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/IntegrationTests.LockModeTests.cs @@ -1,6 +1,5 @@ using AwesomeAssertions; using EntityFrameworkCore.Locking.Exceptions; -using EntityFrameworkCore.Locking.Tests.Infrastructure; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/SqlGenerationTests.cs b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/SqlGenerationTests.cs index 38b645f..d8ef342 100644 --- a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/SqlGenerationTests.cs +++ b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/SqlGenerationTests.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking.SqlServer; using Xunit; namespace EntityFrameworkCore.Locking.SqlServer.Tests; diff --git a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/ConcurrencyTestsBase.cs b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/ConcurrencyTestsBase.cs index 309a3a4..a240fcf 100644 --- a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/ConcurrencyTestsBase.cs +++ b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/ConcurrencyTestsBase.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using EntityFrameworkCore.Locking.Exceptions; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTests.QueryShapeTestsBase.cs b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTests.QueryShapeTestsBase.cs index ecd30ce..001b166 100644 --- a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTests.QueryShapeTestsBase.cs +++ b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTests.QueryShapeTestsBase.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using EntityFrameworkCore.Locking.Exceptions; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTestsBase.cs b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTestsBase.cs index 2010b7b..bc726b0 100644 --- a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTestsBase.cs +++ b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/IntegrationTestsBase.cs @@ -1,5 +1,4 @@ using AwesomeAssertions; -using EntityFrameworkCore.Locking; using Microsoft.EntityFrameworkCore; using Xunit;