diff --git a/CLAUDE.md b/CLAUDE.md
index 36d4e5b..cdf23e9 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -45,7 +45,7 @@ The library is split into a provider-agnostic core and three database provider p
### Distributed advisory locks flow
-1. `DbContextDistributedLockExtensions.AcquireDistributedLockAsync()` resolves `IAdvisoryLockProvider` from `ILockingProvider.AdvisoryLockProvider`, opens the connection if needed, calls `DistributedLockRegistry.RegisterOrThrow` to prevent double-acquisition on the same context+connection, then delegates to the provider.
+1. `DatabaseFacadeDistributedLockExtensions.AcquireDistributedLockAsync()` (called as `ctx.Database.AcquireDistributedLockAsync(...)`) resolves `IAdvisoryLockProvider` from `ILockingProvider.AdvisoryLockProvider`, opens the connection if needed, calls `DistributedLockRegistry.RegisterOrThrow` to prevent double-acquisition on the same context+connection, then delegates to the provider.
2. Each provider sends native advisory lock SQL (`pg_advisory_lock`, MySQL `GET_LOCK`, SQL Server `sp_getapplock`).
3. The returned `IDistributedLockHandle` releases the lock on dispose via a captured callback; `DistributedLockCleanupInterceptor` performs best-effort cleanup if handles are not disposed before connection close.
diff --git a/README.md b/README.md
index 77163c1..94ec782 100644
--- a/README.md
+++ b/README.md
@@ -135,30 +135,30 @@ No transaction is required.
```csharp
// Acquire — blocks until available (optional timeout)
-await using var handle = await ctx.AcquireDistributedLockAsync("invoice:generate");
+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
-await using var handle = await ctx.AcquireDistributedLockAsync(
+await using var handle = await ctx.Database.AcquireDistributedLockAsync(
"report:daily", TimeSpan.FromSeconds(5));
// With cancellation token
-await using var handle = await ctx.AcquireDistributedLockAsync(
+await using var handle = await ctx.Database.AcquireDistributedLockAsync(
"report:daily", timeout: null, cancellationToken: ct);
// TryAcquire — returns null immediately if already held
-var handle = await ctx.TryAcquireDistributedLockAsync("invoice:generate");
+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
-using var handle = ctx.AcquireDistributedLock("report:daily");
-var handle = ctx.TryAcquireDistributedLock("report:daily");
+using var handle = ctx.Database.AcquireDistributedLock("report:daily");
+var handle = ctx.Database.TryAcquireDistributedLock("report:daily");
// Check support at runtime
-if (ctx.SupportsDistributedLocks()) { ... }
+if (ctx.Database.SupportsDistributedLocks()) { ... }
```
### Lock keys
@@ -186,7 +186,7 @@ Keys are plain strings, up to **255 characters**. The library handles provider-s
```csharp
try
{
- await using var handle = await ctx.AcquireDistributedLockAsync(
+ await using var handle = await ctx.Database.AcquireDistributedLockAsync(
"report:daily", TimeSpan.FromSeconds(5));
}
catch (LockTimeoutException)
diff --git a/samples/InventoryApi/Program.cs b/samples/InventoryApi/Program.cs
index 48a407e..03aaa9c 100644
--- a/samples/InventoryApi/Program.cs
+++ b/samples/InventoryApi/Program.cs
@@ -203,7 +203,7 @@
"/inventory/snapshot",
async (InventoryDbContext db) =>
{
- await using var handle = await db.TryAcquireDistributedLockAsync("inventory:snapshot");
+ await using var handle = await db.Database.TryAcquireDistributedLockAsync("inventory:snapshot");
if (handle is null)
return Results.Conflict(new { error = "A snapshot is already in progress." });
@@ -234,7 +234,7 @@
{
try
{
- await using var handle = await db.AcquireDistributedLockAsync(
+ await using var handle = await db.Database.AcquireDistributedLockAsync(
"products:price-sync",
timeout: TimeSpan.FromSeconds(3)
);
diff --git a/samples/QueueProcessor/Program.cs b/samples/QueueProcessor/Program.cs
index 7dd9757..7900c84 100644
--- a/samples/QueueProcessor/Program.cs
+++ b/samples/QueueProcessor/Program.cs
@@ -42,7 +42,7 @@
Console.WriteLine("\nRunning maintenance sweep...");
await using (var db = new JobDbContext(optionsBuilder.Options))
{
- await using var sweepHandle = await db.TryAcquireDistributedLockAsync("jobs:maintenance-sweep");
+ await using var sweepHandle = await db.Database.TryAcquireDistributedLockAsync("jobs:maintenance-sweep");
if (sweepHandle is null)
{
Console.WriteLine("Another process is already running the sweep — skipping.");
diff --git a/samples/README.md b/samples/README.md
index 05569e6..f450f18 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -46,7 +46,7 @@ var job = await db.Jobs
After all workers finish, a maintenance sweep requeues any jobs stuck in `Processing` for more than 5 minutes:
```csharp
-await using var sweepHandle = await db.TryAcquireDistributedLockAsync("jobs:maintenance-sweep");
+await using var sweepHandle = await db.Database.TryAcquireDistributedLockAsync("jobs:maintenance-sweep");
if (sweepHandle is null)
{
Console.WriteLine("Another process is already running the sweep — skipping.");
diff --git a/src/EntityFrameworkCore.Locking/Extensions/DbContextDistributedLockExtensions.cs b/src/EntityFrameworkCore.Locking/Extensions/DatabaseFacadeDistributedLockExtensions.cs
similarity index 70%
rename from src/EntityFrameworkCore.Locking/Extensions/DbContextDistributedLockExtensions.cs
rename to src/EntityFrameworkCore.Locking/Extensions/DatabaseFacadeDistributedLockExtensions.cs
index e73523e..2a95753 100644
--- a/src/EntityFrameworkCore.Locking/Extensions/DbContextDistributedLockExtensions.cs
+++ b/src/EntityFrameworkCore.Locking/Extensions/DatabaseFacadeDistributedLockExtensions.cs
@@ -5,31 +5,32 @@
using EntityFrameworkCore.Locking.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace EntityFrameworkCore.Locking;
///
-/// Extension methods on for acquiring distributed (advisory) locks.
-/// No active transaction is required — locks are session-scoped.
+/// Extension methods on (accessed via DbContext.Database) for
+/// acquiring distributed (advisory) locks. No active transaction is required — locks are session-scoped.
///
-public static class DbContextDistributedLockExtensions
+public static class DatabaseFacadeDistributedLockExtensions
{
///
/// Acquires a distributed lock with the given key, blocking until it is available.
///
- /// The DbContext whose connection will hold the lock.
+ /// The whose connection will hold the lock.
/// Lock key (1–255 characters).
/// Maximum time to wait. Throws if exceeded. Null = wait indefinitely.
/// Cancellation token. Cancellation is best-effort (driver-dependent).
public static async Task AcquireDistributedLockAsync(
- this DbContext ctx,
+ this DatabaseFacade database,
string key,
TimeSpan? timeout = null,
CancellationToken ct = default
)
{
- var (provider, connection, openedByMe) = await PrepareAsync(ctx, key, ct).ConfigureAwait(false);
+ var (ctx, provider, connection, openedByMe) = await PrepareAsync(database, key, ct).ConfigureAwait(false);
try
{
DistributedLockRegistry.RegisterOrThrow(ctx, connection, key);
@@ -56,12 +57,12 @@ public static async Task AcquireDistributedLockAsync(
/// Returns null immediately if the lock is held by another connection.
///
public static async Task TryAcquireDistributedLockAsync(
- this DbContext ctx,
+ this DatabaseFacade database,
string key,
CancellationToken ct = default
)
{
- var (provider, connection, openedByMe) = await PrepareAsync(ctx, key, ct).ConfigureAwait(false);
+ var (ctx, provider, connection, openedByMe) = await PrepareAsync(database, key, ct).ConfigureAwait(false);
try
{
DistributedLockRegistry.RegisterOrThrow(ctx, connection, key);
@@ -93,12 +94,12 @@ public static async Task AcquireDistributedLockAsync(
/// Acquires a distributed lock synchronously.
public static IDistributedLockHandle AcquireDistributedLock(
- this DbContext ctx,
+ this DatabaseFacade database,
string key,
TimeSpan? timeout = null
)
{
- var (provider, connection, openedByMe) = PrepareSync(ctx, key);
+ var (ctx, provider, connection, openedByMe) = PrepareSync(database, key);
try
{
DistributedLockRegistry.RegisterOrThrow(ctx, connection, key);
@@ -121,9 +122,9 @@ public static IDistributedLockHandle AcquireDistributedLock(
}
/// Attempts to acquire a distributed lock synchronously. Returns null if contested.
- public static IDistributedLockHandle? TryAcquireDistributedLock(this DbContext ctx, string key)
+ public static IDistributedLockHandle? TryAcquireDistributedLock(this DatabaseFacade database, string key)
{
- var (provider, connection, openedByMe) = PrepareSync(ctx, key);
+ var (ctx, provider, connection, openedByMe) = PrepareSync(database, key);
try
{
DistributedLockRegistry.RegisterOrThrow(ctx, connection, key);
@@ -153,46 +154,51 @@ public static IDistributedLockHandle AcquireDistributedLock(
}
}
- /// Returns true if the current DbContext's provider supports distributed locks.
- public static bool SupportsDistributedLocks(this DbContext ctx)
+ /// Returns true if the configured EF Core provider supports distributed locks.
+ public static bool SupportsDistributedLocks(this DatabaseFacade database)
{
- var lp = ctx.GetInfrastructure().GetService();
+ var lp = ((IInfrastructure)database).Instance.GetService();
return lp?.AdvisoryLockProvider is not null;
}
- private static async Task<(IAdvisoryLockProvider provider, DbConnection connection, bool openedByMe)> PrepareAsync(
+ private static async Task<(
DbContext ctx,
- string key,
- CancellationToken ct
- )
+ IAdvisoryLockProvider provider,
+ DbConnection connection,
+ bool openedByMe
+ )> PrepareAsync(DatabaseFacade database, string key, CancellationToken ct)
{
ValidateKey(key);
- var provider = ResolveProvider(ctx);
- var connection = ctx.Database.GetDbConnection();
+ var ctx = GetContext(database);
+ var provider = ResolveProvider(database);
+ var connection = database.GetDbConnection();
bool openedByMe = false;
if (connection.State != ConnectionState.Open)
{
await connection.OpenAsync(ct).ConfigureAwait(false);
openedByMe = true;
}
- return (provider, connection, openedByMe);
+ return (ctx, provider, connection, openedByMe);
}
- private static (IAdvisoryLockProvider provider, DbConnection connection, bool openedByMe) PrepareSync(
+ private static (
DbContext ctx,
- string key
- )
+ IAdvisoryLockProvider provider,
+ DbConnection connection,
+ bool openedByMe
+ ) PrepareSync(DatabaseFacade database, string key)
{
ValidateKey(key);
- var provider = ResolveProvider(ctx);
- var connection = ctx.Database.GetDbConnection();
+ var ctx = GetContext(database);
+ var provider = ResolveProvider(database);
+ var connection = database.GetDbConnection();
bool openedByMe = false;
if (connection.State != ConnectionState.Open)
{
connection.Open();
openedByMe = true;
}
- return (provider, connection, openedByMe);
+ return (ctx, provider, connection, openedByMe);
}
private static void ValidateKey(string key)
@@ -203,9 +209,12 @@ private static void ValidateKey(string key)
throw new ArgumentException("Lock key must not exceed 255 characters.", nameof(key));
}
- private static IAdvisoryLockProvider ResolveProvider(DbContext ctx)
+ private static DbContext GetContext(DatabaseFacade database) =>
+ ((IDatabaseFacadeDependenciesAccessor)database).Context;
+
+ private static IAdvisoryLockProvider ResolveProvider(DatabaseFacade database)
{
- var lp = ctx.GetInfrastructure().GetService();
+ var lp = ((IInfrastructure)database).Instance.GetService();
if (lp is null)
throw new LockingConfigurationException(
"No ILockingProvider is registered. Call UseLocking() when configuring the DbContext."
diff --git a/tests/EntityFrameworkCore.Locking.MySql.Tests/DistributedLockIntegrationTests.cs b/tests/EntityFrameworkCore.Locking.MySql.Tests/DistributedLockIntegrationTests.cs
index 9d6ef83..15d3809 100644
--- a/tests/EntityFrameworkCore.Locking.MySql.Tests/DistributedLockIntegrationTests.cs
+++ b/tests/EntityFrameworkCore.Locking.MySql.Tests/DistributedLockIntegrationTests.cs
@@ -1,5 +1,4 @@
using AwesomeAssertions;
-using EntityFrameworkCore.Locking;
using EntityFrameworkCore.Locking.MySql.Tests.Fixtures;
using EntityFrameworkCore.Locking.Tests.Infrastructure;
using Microsoft.EntityFrameworkCore;
@@ -29,7 +28,7 @@ public async Task LongKey_ExceededMysqlLimit_HashesCorrectly()
// Key > 64 chars is hashed to lock: (64 chars total)
var longKey = new string('x', 100);
await using var ctx = CreateContext();
- await using var handle = await ctx.AcquireDistributedLockAsync(longKey);
+ await using var handle = await ctx.Database.AcquireDistributedLockAsync(longKey);
handle.Should().NotBeNull();
handle.Key.Should().Be(longKey); // public Key is the original, not encoded
}
diff --git a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/DistributedLockIntegrationTests.cs b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/DistributedLockIntegrationTests.cs
index aa760b7..0218059 100644
--- a/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/DistributedLockIntegrationTests.cs
+++ b/tests/EntityFrameworkCore.Locking.PostgreSQL.Tests/DistributedLockIntegrationTests.cs
@@ -1,5 +1,4 @@
using AwesomeAssertions;
-using EntityFrameworkCore.Locking;
using EntityFrameworkCore.Locking.PostgreSQL.Tests.Fixtures;
using EntityFrameworkCore.Locking.Tests.Infrastructure;
using Microsoft.EntityFrameworkCore;
@@ -21,12 +20,12 @@ public async Task Acquire_Contested_BlocksUntilReleased()
const string key = "pg-block-key";
await using var ctxA = CreateContext();
- var handleA = await ctxA.AcquireDistributedLockAsync(key);
+ var handleA = await ctxA.Database.AcquireDistributedLockAsync(key);
var acquireTask = Task.Run(async () =>
{
await using var ctxB = CreateContext();
- await using var h = await ctxB.AcquireDistributedLockAsync(key);
+ await using var h = await ctxB.Database.AcquireDistributedLockAsync(key);
});
var completed = await Task.WhenAny(acquireTask, Task.Delay(300));
@@ -42,12 +41,12 @@ public async Task TwoContexts_DifferentConnections_CanBothHoldSameKey()
const string key = "pg-registry-scope";
await using var ctxA = CreateContext();
- await using var hA = await ctxA.AcquireDistributedLockAsync(key);
+ await using var hA = await ctxA.Database.AcquireDistributedLockAsync(key);
await using var ctxB = CreateContext();
- var hB = await ctxB.TryAcquireDistributedLockAsync(key);
+ var hB = await ctxB.Database.TryAcquireDistributedLockAsync(key);
await hA.DisposeAsync();
- hB = await ctxB.TryAcquireDistributedLockAsync(key);
+ hB = await ctxB.Database.TryAcquireDistributedLockAsync(key);
hB.Should().NotBeNull("after ctxA releases, ctxB should acquire");
await hB!.DisposeAsync();
}
@@ -58,14 +57,14 @@ public async Task Acquire_Cancelled_WithTimeout_ThrowsOperationCanceled()
const string key = "pg-cancel-with-timeout";
await using var ctxA = CreateContext();
- await using var handleA = await ctxA.AcquireDistributedLockAsync(key);
+ await using var handleA = await ctxA.Database.AcquireDistributedLockAsync(key);
await using var ctxB = CreateContext();
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(200));
var sw = System.Diagnostics.Stopwatch.StartNew();
- Func act = () => ctxB.AcquireDistributedLockAsync(key, TimeSpan.FromSeconds(10), cts.Token);
+ Func act = () => ctxB.Database.AcquireDistributedLockAsync(key, TimeSpan.FromSeconds(10), cts.Token);
await act.Should().ThrowAsync();
sw.Stop();
sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(15));
diff --git a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/DistributedLockIntegrationTests.cs b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/DistributedLockIntegrationTests.cs
index d3dfdba..d8d2e30 100644
--- a/tests/EntityFrameworkCore.Locking.SqlServer.Tests/DistributedLockIntegrationTests.cs
+++ b/tests/EntityFrameworkCore.Locking.SqlServer.Tests/DistributedLockIntegrationTests.cs
@@ -1,5 +1,4 @@
using AwesomeAssertions;
-using EntityFrameworkCore.Locking;
using EntityFrameworkCore.Locking.SqlServer.Tests.Fixtures;
using EntityFrameworkCore.Locking.Tests.Infrastructure;
using Microsoft.EntityFrameworkCore;
@@ -20,14 +19,14 @@ public async Task Acquire_Cancelled_WithTimeout_Throws()
{
const string key = "ss-cancel-timeout";
await using var ctxA = CreateContext();
- await using var handleA = await ctxA.AcquireDistributedLockAsync(key);
+ await using var handleA = await ctxA.Database.AcquireDistributedLockAsync(key);
await using var ctxB = CreateContext();
using var cts = new CancellationTokenSource();
cts.CancelAfter(200);
var sw = System.Diagnostics.Stopwatch.StartNew();
- Func act = () => ctxB.AcquireDistributedLockAsync(key, TimeSpan.FromSeconds(10), cts.Token);
+ Func act = () => ctxB.Database.AcquireDistributedLockAsync(key, TimeSpan.FromSeconds(10), cts.Token);
await act.Should().ThrowAsync();
sw.Stop();
sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(15));
diff --git a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/DistributedLockIntegrationTestsBase.cs b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/DistributedLockIntegrationTestsBase.cs
index 6cccb6d..3d42091 100644
--- a/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/DistributedLockIntegrationTestsBase.cs
+++ b/tests/EntityFrameworkCore.Locking.Tests.Infrastructure/DistributedLockIntegrationTestsBase.cs
@@ -1,5 +1,4 @@
using AwesomeAssertions;
-using EntityFrameworkCore.Locking;
using EntityFrameworkCore.Locking.Exceptions;
using Xunit;
@@ -27,7 +26,7 @@ public async Task InitializeAsync()
public async Task Acquire_Free_Succeeds()
{
await using var ctx = CreateContext();
- await using var handle = await ctx.AcquireDistributedLockAsync("free-key");
+ await using var handle = await ctx.Database.AcquireDistributedLockAsync("free-key");
handle.Should().NotBeNull();
handle.Key.Should().Be("free-key");
}
@@ -36,7 +35,7 @@ public async Task Acquire_Free_Succeeds()
public void SupportsDistributedLocks_ReturnsTrue()
{
using var ctx = CreateContext();
- ctx.SupportsDistributedLocks().Should().BeTrue();
+ ctx.Database.SupportsDistributedLocks().Should().BeTrue();
}
[Fact]
@@ -45,12 +44,12 @@ public async Task Dispose_ReleasesLock_VerifiedByOtherConnection()
const string key = "release-key";
await using var ctxA = CreateContext();
- var handle = await ctxA.AcquireDistributedLockAsync(key);
+ var handle = await ctxA.Database.AcquireDistributedLockAsync(key);
await handle.DisposeAsync();
await using var ctxB = CreateContext();
- await using var handleB = await ctxB.TryAcquireDistributedLockAsync(key);
+ await using var handleB = await ctxB.Database.TryAcquireDistributedLockAsync(key);
handleB.Should().NotBeNull();
}
@@ -58,7 +57,7 @@ public async Task Dispose_ReleasesLock_VerifiedByOtherConnection()
public async Task TryAcquire_Free_ReturnsHandle()
{
await using var ctx = CreateContext();
- await using var handle = await ctx.TryAcquireDistributedLockAsync("try-free");
+ await using var handle = await ctx.Database.TryAcquireDistributedLockAsync("try-free");
handle.Should().NotBeNull();
}
@@ -68,10 +67,10 @@ public async Task TryAcquire_Contested_ReturnsNull()
const string key = "try-contested";
await using var ctxA = CreateContext();
- await using var handleA = await ctxA.AcquireDistributedLockAsync(key);
+ await using var handleA = await ctxA.Database.AcquireDistributedLockAsync(key);
await using var ctxB = CreateContext();
- var handleB = await ctxB.TryAcquireDistributedLockAsync(key);
+ var handleB = await ctxB.Database.TryAcquireDistributedLockAsync(key);
handleB.Should().BeNull();
}
@@ -81,11 +80,11 @@ public async Task Acquire_Timeout_ThrowsLockTimeout()
const string key = "timeout-key";
await using var ctxA = CreateContext();
- await using var handleA = await ctxA.AcquireDistributedLockAsync(key);
+ await using var handleA = await ctxA.Database.AcquireDistributedLockAsync(key);
await using var ctxB = CreateContext();
var sw = System.Diagnostics.Stopwatch.StartNew();
- Func act = () => ctxB.AcquireDistributedLockAsync(key, DistributedLockAcquireTimeout);
+ Func act = () => ctxB.Database.AcquireDistributedLockAsync(key, DistributedLockAcquireTimeout);
await act.Should().ThrowAsync();
sw.Stop();
sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(10));
@@ -95,7 +94,7 @@ public async Task Acquire_Timeout_ThrowsLockTimeout()
public async Task ReleaseAsync_Idempotent()
{
await using var ctx = CreateContext();
- var handle = await ctx.AcquireDistributedLockAsync("idempotent");
+ var handle = await ctx.Database.AcquireDistributedLockAsync("idempotent");
await handle.ReleaseAsync();
await handle.ReleaseAsync(); // must not throw
}
@@ -104,9 +103,11 @@ public async Task ReleaseAsync_Idempotent()
public async Task DoubleAcquire_SameContext_ThrowsLockAlreadyHeld()
{
await using var ctx = CreateContext();
- await using var h1 = await ctx.AcquireDistributedLockAsync("double");
+ await using var h1 = await ctx.Database.AcquireDistributedLockAsync("double");
- var ex = await Assert.ThrowsAsync(() => ctx.AcquireDistributedLockAsync("double"));
+ var ex = await Assert.ThrowsAsync(() =>
+ ctx.Database.AcquireDistributedLockAsync("double")
+ );
ex.Key.Should().Be("double");
}
}
diff --git a/tests/EntityFrameworkCore.Locking.Tests/DistributedLockUnitTests.cs b/tests/EntityFrameworkCore.Locking.Tests/DistributedLockUnitTests.cs
index a8794d4..551127f 100644
--- a/tests/EntityFrameworkCore.Locking.Tests/DistributedLockUnitTests.cs
+++ b/tests/EntityFrameworkCore.Locking.Tests/DistributedLockUnitTests.cs
@@ -1,12 +1,10 @@
using System.Data;
using System.Data.Common;
using AwesomeAssertions;
-using EntityFrameworkCore.Locking;
using EntityFrameworkCore.Locking.Abstractions;
using EntityFrameworkCore.Locking.Exceptions;
using EntityFrameworkCore.Locking.Internal;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
using Xunit;
namespace EntityFrameworkCore.Locking.Tests;
@@ -36,14 +34,14 @@ public void LockAlreadyHeldException_InheritsLockingException()
public async Task AcquireDistributedLockAsync_NullKey_ThrowsArgumentException()
{
await using var ctx = CreateContext();
- await Assert.ThrowsAsync(() => ctx.AcquireDistributedLockAsync(null!));
+ await Assert.ThrowsAsync(() => ctx.Database.AcquireDistributedLockAsync(null!));
}
[Fact]
public async Task AcquireDistributedLockAsync_EmptyKey_ThrowsArgumentException()
{
await using var ctx = CreateContext();
- await Assert.ThrowsAsync(() => ctx.AcquireDistributedLockAsync(""));
+ await Assert.ThrowsAsync(() => ctx.Database.AcquireDistributedLockAsync(""));
}
[Fact]
@@ -51,7 +49,7 @@ public async Task AcquireDistributedLockAsync_KeyTooLong_ThrowsArgumentException
{
await using var ctx = CreateContext();
var longKey = new string('a', 256);
- await Assert.ThrowsAsync(() => ctx.AcquireDistributedLockAsync(longKey));
+ await Assert.ThrowsAsync(() => ctx.Database.AcquireDistributedLockAsync(longKey));
}
[Fact]
@@ -59,7 +57,7 @@ public async Task AcquireDistributedLockAsync_MaxKey255_Accepted()
{
await using var ctx = CreateContext();
var key = new string('a', 255);
- await using var handle = await ctx.AcquireDistributedLockAsync(key);
+ await using var handle = await ctx.Database.AcquireDistributedLockAsync(key);
handle.Should().NotBeNull();
handle.Key.Should().Be(key);
}
@@ -70,7 +68,7 @@ public async Task AcquireDistributedLockAsync_MaxKey255_Accepted()
public void SupportsDistributedLocks_WithFakeProvider_ReturnsTrue()
{
using var ctx = CreateContext();
- ctx.SupportsDistributedLocks().Should().BeTrue();
+ ctx.Database.SupportsDistributedLocks().Should().BeTrue();
}
// --- Handle idempotence ---
@@ -79,7 +77,7 @@ public void SupportsDistributedLocks_WithFakeProvider_ReturnsTrue()
public async Task ReleaseAsync_CalledTwice_IsIdempotent()
{
await using var ctx = CreateContext();
- var handle = await ctx.AcquireDistributedLockAsync("key");
+ var handle = await ctx.Database.AcquireDistributedLockAsync("key");
await handle.ReleaseAsync();
// Second release should not throw
await handle.ReleaseAsync();
@@ -89,7 +87,7 @@ public async Task ReleaseAsync_CalledTwice_IsIdempotent()
public async Task DisposeAfterRelease_IsIdempotent()
{
await using var ctx = CreateContext();
- var handle = await ctx.AcquireDistributedLockAsync("key");
+ var handle = await ctx.Database.AcquireDistributedLockAsync("key");
await handle.ReleaseAsync();
await handle.DisposeAsync(); // no throw
}
@@ -100,9 +98,11 @@ public async Task DisposeAfterRelease_IsIdempotent()
public async Task DoubleAcquire_SameKey_ThrowsLockAlreadyHeld()
{
await using var ctx = CreateContext();
- await using var h1 = await ctx.AcquireDistributedLockAsync("dup-key");
+ await using var h1 = await ctx.Database.AcquireDistributedLockAsync("dup-key");
- var ex = await Assert.ThrowsAsync(() => ctx.AcquireDistributedLockAsync("dup-key"));
+ var ex = await Assert.ThrowsAsync(() =>
+ ctx.Database.AcquireDistributedLockAsync("dup-key")
+ );
ex.Key.Should().Be("dup-key");
}
@@ -110,10 +110,10 @@ public async Task DoubleAcquire_SameKey_ThrowsLockAlreadyHeld()
public async Task AfterRelease_SameKey_CanBeAcquiredAgain()
{
await using var ctx = CreateContext();
- var h1 = await ctx.AcquireDistributedLockAsync("reuse-key");
+ var h1 = await ctx.Database.AcquireDistributedLockAsync("reuse-key");
await h1.ReleaseAsync();
- await using var h2 = await ctx.AcquireDistributedLockAsync("reuse-key");
+ await using var h2 = await ctx.Database.AcquireDistributedLockAsync("reuse-key");
h2.Should().NotBeNull();
}
@@ -123,7 +123,7 @@ public async Task AfterRelease_SameKey_CanBeAcquiredAgain()
public async Task TryAcquireDistributedLockAsync_FreeKey_ReturnsHandle()
{
await using var ctx = CreateContext();
- var handle = await ctx.TryAcquireDistributedLockAsync("free");
+ var handle = await ctx.Database.TryAcquireDistributedLockAsync("free");
handle.Should().NotBeNull();
await handle!.DisposeAsync();
}