diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
index a24b3799..1ff80c58 100644
--- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
+++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
@@ -60,9 +60,6 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
///
/// Prevents the Entity Framework Core stores from using bulk operations.
///
- ///
- /// Note: bulk operations are only supported when targeting .NET 7.0 and higher.
- ///
/// The instance.
public OpenIddictEntityFrameworkCoreBuilder DisableBulkOperations()
=> Configure(options => options.DisableBulkOperations = true);
diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
index 49f349bb..afa60cca 100644
--- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
+++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
@@ -4,7 +4,6 @@
* the license and the contributors participating to this project.
*/
-using System.Data;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.EntityFrameworkCore;
@@ -214,37 +213,4 @@ public static class OpenIddictEntityFrameworkCoreHelpers
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreScopeConfiguration())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreTokenConfiguration());
}
-
- ///
- /// Tries to create a new with the specified .
- ///
- /// The Entity Framework Core context.
- /// The desired level of isolation.
- /// The that can be used to abort the operation.
- /// The if it could be created, otherwise.
- internal static async ValueTask CreateTransactionAsync(
- this DbContext context, IsolationLevel level, CancellationToken cancellationToken)
- {
- ArgumentNullException.ThrowIfNull(context);
-
- // Note: transactions that specify an explicit isolation level are only supported by
- // relational providers and trying to use them with a different provider results in
- // an invalid operation exception being thrown at runtime. To prevent that, a manual
- // check is made to ensure the underlying transaction manager is relational.
- var manager = context.Database.GetService();
- if (manager is IRelationalTransactionManager)
- {
- try
- {
- return await context.Database.BeginTransactionAsync(level, cancellationToken);
- }
-
- catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
- {
- return null;
- }
- }
-
- return null;
- }
}
diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs
index 42cc0731..ef0ac88f 100644
--- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs
+++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs
@@ -15,8 +15,5 @@ public sealed class OpenIddictEntityFrameworkCoreOptions
///
/// Gets or sets a boolean indicating whether bulk operations should be disabled.
///
- ///
- /// Note: bulk operations are only supported when targeting .NET 7.0 and higher.
- ///
public bool DisableBulkOperations { get; set; }
}
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
index 45074311..c43a71b4 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
@@ -134,7 +134,8 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the application.
await (from token in context.Set()
@@ -155,7 +156,11 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (DbUpdateConcurrencyException exception)
@@ -176,13 +181,15 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.Serializable, cancellationToken);
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
- var authorizations = await (from authorization in context.Set().Include(authorization => authorization.Tokens).AsTracking()
- where authorization.Application!.Id!.Equals(application.Id)
- select authorization).ToListAsync(cancellationToken);
+ var authorizations = await (
+ from authorization in context.Set().Include(authorization => authorization.Tokens).AsTracking()
+ where authorization.Application!.Id!.Equals(application.Id)
+ select authorization).ToListAsync(cancellationToken);
foreach (var authorization in authorizations)
{
@@ -195,10 +202,11 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
}
// Remove all the tokens associated with the application.
- var tokens = await (from token in context.Set().AsTracking()
- where token.Authorization == null
- where token.Application!.Id!.Equals(application.Id)
- select token).ToListAsync(cancellationToken);
+ var tokens = await (
+ from token in context.Set().AsTracking()
+ where token.Authorization == null
+ where token.Application!.Id!.Equals(application.Id)
+ select token).ToListAsync(cancellationToken);
foreach (var token in tokens)
{
@@ -210,7 +218,11 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (DbUpdateConcurrencyException exception)
@@ -735,4 +747,37 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
return converter.ConvertToInvariantString(identifier);
}
}
+
+ ///
+ /// Tries to create a new with the specified .
+ ///
+ /// The Entity Framework Core context.
+ /// The desired level of isolation.
+ /// The that can be used to abort the operation.
+ /// The if it could be created, otherwise.
+ protected virtual async ValueTask CreateTransactionAsync(
+ DbContext context, IsolationLevel level, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+
+ // Note: transactions that specify an explicit isolation level are only supported by
+ // relational providers and trying to use them with a different provider results in
+ // an invalid operation exception being thrown at runtime. To prevent that, a manual
+ // check is made to ensure the underlying transaction manager is relational.
+ var manager = context.GetService();
+ if (manager is IRelationalTransactionManager)
+ {
+ try
+ {
+ return await context.Database.BeginTransactionAsync(level, cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ return null;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
index 9a2e1ef8..60ccc566 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
@@ -133,7 +133,8 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the authorization.
await (from token in context.Set().AsTracking()
@@ -148,7 +149,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (DbUpdateConcurrencyException exception)
@@ -169,7 +174,8 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the authorization.
var tokens = await (from token in context.Set().AsTracking()
@@ -186,7 +192,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (DbUpdateConcurrencyException exception)
@@ -524,7 +534,8 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.RepeatableRead, cancellationToken);
var authorizations = await
(from authorization in context.Set().Include(authorization => authorization.Tokens).AsTracking()
@@ -545,7 +556,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
@@ -943,4 +958,37 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
return converter.ConvertToInvariantString(identifier);
}
}
+
+ ///
+ /// Tries to create a new with the specified .
+ ///
+ /// The Entity Framework Core context.
+ /// The desired level of isolation.
+ /// The that can be used to abort the operation.
+ /// The if it could be created, otherwise.
+ protected virtual async ValueTask CreateTransactionAsync(
+ DbContext context, IsolationLevel level, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+
+ // Note: transactions that specify an explicit isolation level are only supported by
+ // relational providers and trying to use them with a different provider results in
+ // an invalid operation exception being thrown at runtime. To prevent that, a manual
+ // check is made to ensure the underlying transaction manager is relational.
+ var manager = context.GetService();
+ if (manager is IRelationalTransactionManager)
+ {
+ try
+ {
+ return await context.Database.BeginTransactionAsync(level, cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ return null;
+ }
+ }
+
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
index 24d64199..f98a9848 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
@@ -559,7 +559,8 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
- using var transaction = await context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
+ await using var transaction = await CreateTransactionAsync(context,
+ IsolationLevel.RepeatableRead, cancellationToken);
var tokens = await
(from token in context.Set().AsTracking()
@@ -577,7 +578,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
try
{
await context.SaveChangesAsync(cancellationToken);
- transaction?.Commit();
+
+ if (transaction is not null)
+ {
+ await transaction.CommitAsync(cancellationToken);
+ }
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
@@ -1100,4 +1105,37 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
return converter.ConvertToInvariantString(identifier);
}
}
+
+ ///
+ /// Tries to create a new with the specified .
+ ///
+ /// The Entity Framework Core context.
+ /// The desired level of isolation.
+ /// The that can be used to abort the operation.
+ /// The if it could be created, otherwise.
+ protected virtual async ValueTask CreateTransactionAsync(
+ DbContext context, IsolationLevel level, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+
+ // Note: transactions that specify an explicit isolation level are only supported by
+ // relational providers and trying to use them with a different provider results in
+ // an invalid operation exception being thrown at runtime. To prevent that, a manual
+ // check is made to ensure the underlying transaction manager is relational.
+ var manager = context.GetService();
+ if (manager is IRelationalTransactionManager)
+ {
+ try
+ {
+ return await context.Database.BeginTransactionAsync(level, cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ return null;
+ }
+ }
+
+ return null;
+ }
}