diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
index d55b7a63f9e..75386a35107 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
@@ -18,6 +18,8 @@ public class CosmosDatabaseCreator : IDatabaseCreator
private readonly IDesignTimeModel _designTimeModel;
private readonly IUpdateAdapterFactory _updateAdapterFactory;
private readonly IDatabase _database;
+ private readonly ICurrentDbContext _currentContext;
+ private readonly IDbContextOptions _contextOptions;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -29,12 +31,16 @@ public CosmosDatabaseCreator(
ICosmosClientWrapper cosmosClient,
IDesignTimeModel designTimeModel,
IUpdateAdapterFactory updateAdapterFactory,
- IDatabase database)
+ IDatabase database,
+ ICurrentDbContext currentContext,
+ IDbContextOptions contextOptions)
{
_cosmosClient = cosmosClient;
_designTimeModel = designTimeModel;
_updateAdapterFactory = updateAdapterFactory;
_database = database;
+ _currentContext = currentContext;
+ _contextOptions = contextOptions;
}
///
@@ -55,7 +61,21 @@ public virtual bool EnsureCreated()
if (created)
{
- Seed();
+ InsertData();
+ }
+
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ var seed = coreOptionsExtension.Seeder;
+ if (seed != null)
+ {
+ seed(_currentContext.Context, created);
+ }
+ else if (coreOptionsExtension.AsyncSeeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
}
return created;
@@ -81,7 +101,21 @@ public virtual async Task EnsureCreatedAsync(CancellationToken cancellatio
if (created)
{
- await SeedAsync(cancellationToken).ConfigureAwait(false);
+ await InsertDataAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ var seedAsync = coreOptionsExtension.AsyncSeeder;
+ if (seedAsync != null)
+ {
+ await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false);
+ }
+ else if (coreOptionsExtension.Seeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
}
return created;
@@ -153,9 +187,9 @@ private static IEnumerable GetContainersToCreate(IModel mod
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual void Seed()
+ public virtual void InsertData()
{
- var updateAdapter = AddSeedData();
+ var updateAdapter = AddModelData();
_database.SaveChanges(updateAdapter.GetEntriesToSave());
}
@@ -166,14 +200,14 @@ public virtual void Seed()
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual Task SeedAsync(CancellationToken cancellationToken = default)
+ public virtual Task InsertDataAsync(CancellationToken cancellationToken = default)
{
- var updateAdapter = AddSeedData();
+ var updateAdapter = AddModelData();
return _database.SaveChangesAsync(updateAdapter.GetEntriesToSave(), cancellationToken);
}
- private IUpdateAdapter AddSeedData()
+ private IUpdateAdapter AddModelData()
{
var updateAdapter = _updateAdapterFactory.CreateStandalone();
foreach (var entityType in _designTimeModel.Model.GetEntityTypes())
diff --git a/src/EFCore.Design/Design/Internal/DbContextOperations.cs b/src/EFCore.Design/Design/Internal/DbContextOperations.cs
index 0c4ae03d1af..00d878a8176 100644
--- a/src/EFCore.Design/Design/Internal/DbContextOperations.cs
+++ b/src/EFCore.Design/Design/Internal/DbContextOperations.cs
@@ -1,14 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.IO;
using System.Text;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
-using Microsoft.CodeAnalysis.Simplification;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
diff --git a/src/EFCore.Design/Design/Internal/MigrationsOperations.cs b/src/EFCore.Design/Design/Internal/MigrationsOperations.cs
index 419d658cd2e..e841e680ac9 100644
--- a/src/EFCore.Design/Design/Internal/MigrationsOperations.cs
+++ b/src/EFCore.Design/Design/Internal/MigrationsOperations.cs
@@ -220,8 +220,7 @@ public virtual void UpdateDatabase(
EnsureServices(services);
var migrator = services.GetRequiredService();
-
- migrator.Migrate(targetMigration: targetMigration);
+ migrator.Migrate(targetMigration);
}
_reporter.WriteInformation(DesignStrings.Done);
diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs
index 654528b4a6c..69fc4d05515 100644
--- a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs
+++ b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs
@@ -12,6 +12,8 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
public class InMemoryDatabaseCreator : IDatabaseCreator
{
private readonly IDatabase _database;
+ private readonly ICurrentDbContext _currentContext;
+ private readonly IDbContextOptions _contextOptions;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -19,9 +21,14 @@ public class InMemoryDatabaseCreator : IDatabaseCreator
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public InMemoryDatabaseCreator(IDatabase database)
+ public InMemoryDatabaseCreator(
+ IDatabase database,
+ ICurrentDbContext currentContext,
+ IDbContextOptions contextOptions)
{
_database = database;
+ _currentContext = currentContext;
+ _contextOptions = contextOptions;
}
///
@@ -58,7 +65,25 @@ public virtual Task EnsureDeletedAsync(CancellationToken cancellationToken
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual bool EnsureCreated()
- => Database.EnsureDatabaseCreated();
+ {
+ var created = Database.EnsureDatabaseCreated();
+
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ var seed = coreOptionsExtension.Seeder;
+ if (seed != null)
+ {
+ seed(_currentContext.Context, created);
+ }
+ else if (coreOptionsExtension.AsyncSeeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
+ }
+
+ return created;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -66,8 +91,26 @@ public virtual bool EnsureCreated()
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual Task EnsureCreatedAsync(CancellationToken cancellationToken = default)
- => Task.FromResult(Database.EnsureDatabaseCreated());
+ public virtual async Task EnsureCreatedAsync(CancellationToken cancellationToken = default)
+ {
+ var created = Database.EnsureDatabaseCreated();
+
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ var seedAsync = coreOptionsExtension.AsyncSeeder;
+ if (seedAsync != null)
+ {
+ await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false);
+ }
+ else if (coreOptionsExtension.Seeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
+ }
+
+ return created;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index f487c54ff62..34189265a32 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -79,6 +79,7 @@ private enum Id
ColumnOrderIgnoredWarning,
PendingModelChangesWarning,
NonTransactionalMigrationOperationWarning,
+ AcquiringMigrationLock,
// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
@@ -749,6 +750,19 @@ private static EventId MakeMigrationsId(Id id)
///
public static readonly EventId NonTransactionalMigrationOperationWarning = MakeMigrationsId(Id.NonTransactionalMigrationOperationWarning);
+ ///
+ /// A migration lock is being acquired.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId AcquiringMigrationLock = MakeMigrationsId(Id.AcquiringMigrationLock);
+
private static readonly string _queryPrefix = DbLoggerCategory.Query.Name + ".";
private static EventId MakeQueryId(Id id)
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
index d704bcf1f1b..e86dea9a04b 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
@@ -2393,6 +2393,36 @@ private static string NonTransactionalMigrationOperationWarning(EventDefinitionB
return d.GenerateMessage(commandText, p.Migration.GetType().ShortDisplayName());
}
+ ///
+ /// Logs for the event.
+ ///
+ /// The diagnostics logger to use.
+ public static void AcquiringMigrationLock(
+ this IDiagnosticsLogger diagnostics)
+ {
+ var definition = RelationalResources.LogAcquiringMigrationLock(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics);
+ }
+
+ if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ var eventData = new EventData(
+ definition,
+ AcquiringMigrationLock);
+
+ diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+ }
+ }
+
+ private static string AcquiringMigrationLock(EventDefinitionBase definition, EventData payload)
+ {
+ var d = (EventDefinition)definition;
+ return d.GenerateMessage();
+ }
+
///
/// Logs for the event.
///
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index 51e0e3b1aee..9b57a07998e 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -358,6 +358,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogNoMigrationsFound;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogAcquiringMigrationLock;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
index e355e7a7c23..5d8e3bd558f 100644
--- a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
@@ -123,13 +123,6 @@ public static void Migrate(this DatabaseFacade databaseFacade)
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
- ///
- /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
- ///
- ///
- /// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
- /// lock is released when the migration operation completes.
- ///
///
///
/// Note that this API is mutually exclusive with . EnsureCreated does not use migrations
@@ -145,10 +138,8 @@ public static void Migrate(this DatabaseFacade databaseFacade)
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static void Migrate(
this DatabaseFacade databaseFacade,
- Action? seed,
- string? targetMigration = null,
- TimeSpan? lockTimeout = null)
- => databaseFacade.GetRelationalService().Migrate(seed, targetMigration, lockTimeout);
+ string? targetMigration)
+ => databaseFacade.GetRelationalService().Migrate(targetMigration);
///
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
@@ -184,13 +175,6 @@ public static Task MigrateAsync(
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
- ///
- /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
- ///
- ///
- /// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
- /// lock is released when the migration operation completes.
- ///
/// A to observe while waiting for the task to complete.
///
///
@@ -209,11 +193,9 @@ public static Task MigrateAsync(
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static Task MigrateAsync(
this DatabaseFacade databaseFacade,
- Func? seed,
- string? targetMigration = null,
- TimeSpan? lockTimeout = null,
+ string? targetMigration,
CancellationToken cancellationToken = default)
- => databaseFacade.GetRelationalService().MigrateAsync(seed, targetMigration, lockTimeout, cancellationToken);
+ => databaseFacade.GetRelationalService().MigrateAsync(targetMigration, cancellationToken);
///
/// Executes the given SQL against the database and returns the number of rows affected.
diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
index 2a526d3dfc2..d0ee126a44c 100644
--- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
+++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
@@ -96,8 +96,7 @@ public static readonly IDictionary RelationalServi
typeof(IAggregateMethodCallTranslatorPlugin),
new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true)
},
- { typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
- { typeof(IMigratorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }
+ { typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
};
///
diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs
index e3d1a03e75f..78dc3b24013 100644
--- a/src/EFCore.Relational/Migrations/HistoryRepository.cs
+++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs
@@ -197,18 +197,16 @@ protected virtual IReadOnlyList GetCreateCommands()
///
/// Gets an exclusive lock on the database.
///
- /// The time to wait for the lock before an exception is thrown.
/// An object that can be disposed to release the lock.
- public abstract IDisposable GetDatabaseLock(TimeSpan timeout);
+ public abstract IDisposable GetDatabaseLock();
///
/// Gets an exclusive lock on the database.
///
- /// The time to wait for the lock before an exception is thrown.
/// A to observe while waiting for the task to complete.
/// An object that can be disposed to release the lock.
/// If the is canceled.
- public abstract Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default);
+ public abstract Task GetDatabaseLockAsync(CancellationToken cancellationToken = default);
///
/// Configures the entity type mapped to the history table.
diff --git a/src/EFCore.Relational/Migrations/IHistoryRepository.cs b/src/EFCore.Relational/Migrations/IHistoryRepository.cs
index 4d52db38043..7bf3a461650 100644
--- a/src/EFCore.Relational/Migrations/IHistoryRepository.cs
+++ b/src/EFCore.Relational/Migrations/IHistoryRepository.cs
@@ -77,18 +77,16 @@ Task> GetAppliedMigrationsAsync(
///
/// Gets an exclusive lock on the database.
///
- /// The time to wait for the lock before an exception is thrown.
/// An object that can be disposed to release the lock.
- IDisposable GetDatabaseLock(TimeSpan timeout);
+ IDisposable GetDatabaseLock();
///
/// Gets an exclusive lock on the database.
///
- /// The time to wait for the lock before an exception is thrown.
/// A to observe while waiting for the task to complete.
/// An object that can be disposed to release the lock.
/// If the is canceled.
- Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default);
+ Task GetDatabaseLockAsync(CancellationToken cancellationToken = default);
///
/// Generates a SQL script that will create the history table.
diff --git a/src/EFCore.Relational/Migrations/IMigrator.cs b/src/EFCore.Relational/Migrations/IMigrator.cs
index 0b02cbe7df3..b7735b4f0f7 100644
--- a/src/EFCore.Relational/Migrations/IMigrator.cs
+++ b/src/EFCore.Relational/Migrations/IMigrator.cs
@@ -26,37 +26,23 @@ public interface IMigrator
/// Migrates the database to either a specified target migration or up to the latest
/// migration that exists in the .
///
- ///
- /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
- ///
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
- ///
- /// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
- /// lock is released when the migration operation completes.
- ///
///
/// See Database migrations for more information and examples.
///
[RequiresUnreferencedCode("Migration generation currently isn't compatible with trimming")]
[RequiresDynamicCode("Migrations operations are not supported with NativeAOT")]
- void Migrate(Action? seed = null, string? targetMigration = null, TimeSpan? lockTimeout = null);
+ void Migrate(string? targetMigration = null);
///
/// Migrates the database to either a specified target migration or up to the latest
/// migration that exists in the .
///
- ///
- /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
- ///
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
- ///
- /// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
- /// lock is released when the migration operation completes.
- ///
/// A to observe while waiting for the task to complete.
/// A task that represents the asynchronous operation
///
@@ -66,9 +52,7 @@ public interface IMigrator
[RequiresUnreferencedCode("Migration generation currently isn't compatible with trimming")]
[RequiresDynamicCode("Migrations operations are not supported with NativeAOT")]
Task MigrateAsync(
- Func? seed = null,
string? targetMigration = null,
- TimeSpan? lockTimeout = null,
CancellationToken cancellationToken = default);
///
diff --git a/src/EFCore.Relational/Migrations/IMigratorData.cs b/src/EFCore.Relational/Migrations/IMigratorData.cs
deleted file mode 100644
index 60531a1c699..00000000000
--- a/src/EFCore.Relational/Migrations/IMigratorData.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.EntityFrameworkCore.Migrations;
-
-///
-/// A class that holds the results from the last migrations application.
-///
-///
-/// See Database migrations for more information and examples.
-///
-public interface IMigratorData
-{
- ///
- /// The migrations that were applied to the database.
- ///
- public IReadOnlyList AppliedMigrations { get; }
-
- ///
- /// The migrations that were reverted from the database.
- ///
- public IReadOnlyList RevertedMigrations { get; }
-
- ///
- /// The target migration.
- /// if all migrations were reverted or no target migration was specified.
- ///
- public Migration? TargetMigration { get; }
-}
diff --git a/src/EFCore.Relational/Migrations/IMigratorPlugin.cs b/src/EFCore.Relational/Migrations/IMigratorPlugin.cs
deleted file mode 100644
index ae06c4d968e..00000000000
--- a/src/EFCore.Relational/Migrations/IMigratorPlugin.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.EntityFrameworkCore.Migrations;
-
-///
-///
-/// A service on the EF internal service provider that allows providers or extensions to execute logic
-/// after is called.
-///
-///
-/// This type is typically used by providers or extensions. It is generally not used in application code.
-///
-///
-///
-/// The service lifetime is . This means a single instance
-/// is used by many instances. The implementation must be thread-safe.
-/// This service cannot depend on services registered as .
-///
-public interface IMigratorPlugin
-{
- ///
- /// Called by before applying the migrations.
- ///
- /// The that is being migrated.
- /// The that contains the result of the migrations application.
- ///
- /// See Database migrations for more information and examples.
- ///
- void Migrating(DbContext context, IMigratorData data);
-
- ///
- /// Called by before applying the migrations.
- ///
- /// The that is being migrated.
- /// The that contains the result of the migrations application.
- ///
- /// See Database migrations for more information and examples.
- ///
- /// A to observe while waiting for the task to complete.
- /// A task that represents the asynchronous operation
- /// If the is canceled.
- Task MigratingAsync(
- DbContext context,
- IMigratorData data,
- CancellationToken cancellationToken = default);
-
- ///
- /// Called by after applying the migrations, but before the seeding action.
- ///
- /// The that is being migrated.
- /// The that contains the result of the migrations application.
- ///
- /// See Database migrations for more information and examples.
- ///
- void Migrated(DbContext context, IMigratorData data);
-
- ///
- /// Called by after applying the migrations, but before the seeding action.
- ///
- /// The that is being migrated.
- /// The that contains the result of the migrations application.
- ///
- /// See Database migrations for more information and examples.
- ///
- /// A to observe while waiting for the task to complete.
- /// A task that represents the asynchronous operation
- /// If the is canceled.
- Task MigratedAsync(
- DbContext context,
- IMigratorData data,
- CancellationToken cancellationToken = default);
-}
diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs b/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs
index e545d4c13b5..4b2d78a85bc 100644
--- a/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs
+++ b/src/EFCore.Relational/Migrations/Internal/MigrationCommandExecutor.cs
@@ -11,21 +11,14 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
-public class MigrationCommandExecutor : IMigrationCommandExecutor
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class MigrationCommandExecutor(IExecutionStrategy executionStrategy) : IMigrationCommandExecutor
{
- private readonly IExecutionStrategy _executionStrategy;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public MigrationCommandExecutor(IExecutionStrategy executionStrategy)
- {
- _executionStrategy = executionStrategy;
- }
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -36,19 +29,21 @@ public virtual void ExecuteNonQuery(
IEnumerable migrationCommands,
IRelationalConnection connection)
{
+ // TODO: Remove ToList, see #19710
+ var commands = migrationCommands.ToList();
var userTransaction = connection.CurrentTransaction;
if (userTransaction is not null
- && (migrationCommands.Any(x => x.TransactionSuppressed) || _executionStrategy.RetriesOnFailure))
+ && (commands.Any(x => x.TransactionSuppressed) || executionStrategy.RetriesOnFailure))
{
throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction);
}
using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
- var parameters = new ExecuteParameters(migrationCommands.ToList(), connection);
+ var parameters = new ExecuteParameters(commands, connection);
if (userTransaction is null)
{
- _executionStrategy.Execute(parameters, static (_, p) => Execute(p, beginTransaction: true), verifySucceeded: null);
+ executionStrategy.Execute(parameters, static (_, p) => Execute(p, beginTransaction: true), verifySucceeded: null);
}
else
{
@@ -114,34 +109,28 @@ public virtual async Task ExecuteNonQueryAsync(
IRelationalConnection connection,
CancellationToken cancellationToken = default)
{
+ var commands = migrationCommands.ToList();
var userTransaction = connection.CurrentTransaction;
if (userTransaction is not null
- && (migrationCommands.Any(x => x.TransactionSuppressed) || _executionStrategy.RetriesOnFailure))
+ && (commands.Any(x => x.TransactionSuppressed) || executionStrategy.RetriesOnFailure))
{
throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction);
}
- var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
- try
- {
- var parameters = new ExecuteParameters(migrationCommands.ToList(), connection);
- if (userTransaction is null)
- {
- await _executionStrategy.ExecuteAsync(
- parameters,
- static (_, p, ct) => ExecuteAsync(p, beginTransaction: true, ct),
- verifySucceeded: null,
- cancellationToken).ConfigureAwait(false);
- }
- else
- {
- await ExecuteAsync(parameters, beginTransaction: false, cancellationToken).ConfigureAwait(false);
- }
+ using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
+ var parameters = new ExecuteParameters(commands, connection);
+ if (userTransaction is null)
+ {
+ await executionStrategy.ExecuteAsync(
+ parameters,
+ static (_, p, ct) => ExecuteAsync(p, beginTransaction: true, ct),
+ verifySucceeded: null,
+ cancellationToken).ConfigureAwait(false);
}
- finally
+ else
{
- await transactionScope.DisposeAsyncIfAvailable().ConfigureAwait(false);
+ await ExecuteAsync(parameters, beginTransaction: false, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
index 4012a1a7a53..9d525dd4e95 100644
--- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs
+++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
-using Microsoft.EntityFrameworkCore.Storage;
namespace Microsoft.EntityFrameworkCore.Migrations.Internal;
@@ -26,11 +25,10 @@ public class Migrator : IMigrator
private readonly IModelRuntimeInitializer _modelRuntimeInitializer;
private readonly IDiagnosticsLogger _logger;
private readonly IRelationalCommandDiagnosticsLogger _commandLogger;
- private readonly IEnumerable _plugins;
private readonly IMigrationsModelDiffer _migrationsModelDiffer;
private readonly IDesignTimeModel _designTimeModel;
private readonly string _activeProvider;
- private static readonly TimeSpan _defaultLockTimeout = TimeSpan.FromHours(1);
+ private readonly IDbContextOptions _contextOptions;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -52,9 +50,9 @@ public Migrator(
IDiagnosticsLogger logger,
IRelationalCommandDiagnosticsLogger commandLogger,
IDatabaseProvider databaseProvider,
- IEnumerable plugins,
IMigrationsModelDiffer migrationsModelDiffer,
- IDesignTimeModel designTimeModel)
+ IDesignTimeModel designTimeModel,
+ IDbContextOptions contextOptions)
{
_migrationsAssembly = migrationsAssembly;
_historyRepository = historyRepository;
@@ -68,10 +66,10 @@ public Migrator(
_modelRuntimeInitializer = modelRuntimeInitializer;
_logger = logger;
_commandLogger = commandLogger;
- _plugins = plugins;
_migrationsModelDiffer = migrationsModelDiffer;
_designTimeModel = designTimeModel;
_activeProvider = databaseProvider.Name;
+ _contextOptions = contextOptions;
}
///
@@ -80,7 +78,7 @@ public Migrator(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual void Migrate(Action? seed, string? targetMigration, TimeSpan? lockTimeout)
+ public virtual void Migrate(string? targetMigration)
{
if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
@@ -99,7 +97,8 @@ public virtual void Migrate(Action? seed, string? targ
{
_connection.Open();
- using var _ = _historyRepository.GetDatabaseLock(lockTimeout ?? _defaultLockTimeout);
+ _logger.AcquiringMigrationLock();
+ using var _ = _historyRepository.GetDatabaseLock();
if (!_historyRepository.Exists())
{
@@ -111,28 +110,30 @@ public virtual void Migrate(Action? seed, string? targ
targetMigration,
out var migratorData);
- foreach (var plugin in _plugins)
- {
- plugin.Migrating(_currentContext.Context, migratorData);
- }
-
var commandLists = GetMigrationCommandLists(migratorData);
foreach (var commandList in commandLists)
{
_migrationCommandExecutor.ExecuteNonQuery(commandList(), _connection);
}
- foreach (var plugin in _plugins)
- {
- plugin.Migrated(_currentContext.Context, migratorData);
- }
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+ var seed = coreOptionsExtension.Seeder;
if (seed != null)
{
- using var transaction = _connection.BeginTransaction();
- seed(_currentContext.Context, migratorData);
+ var context = _currentContext.Context;
+ var operationsPerformed = migratorData.AppliedMigrations.Count != 0
+ || migratorData.RevertedMigrations.Count != 0;
+ using var transaction = context.Database.BeginTransaction();
+ seed(context, operationsPerformed);
transaction.Commit();
}
+ else if (coreOptionsExtension.AsyncSeeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
+ }
}
finally
{
@@ -147,9 +148,7 @@ public virtual void Migrate(Action? seed, string? targ
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual async Task MigrateAsync(
- Func? seed,
string? targetMigration,
- TimeSpan? lockTimeout = null,
CancellationToken cancellationToken = default)
{
if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
@@ -169,7 +168,8 @@ public virtual async Task MigrateAsync(
{
await _connection.OpenAsync(cancellationToken).ConfigureAwait(false);
- var dbLock = await _historyRepository.GetDatabaseLockAsync(lockTimeout ?? _defaultLockTimeout, cancellationToken).ConfigureAwait(false);
+ _logger.AcquiringMigrationLock();
+ var dbLock = await _historyRepository.GetDatabaseLockAsync(cancellationToken).ConfigureAwait(false);
await using var _ = dbLock.ConfigureAwait(false);
if (!await _historyRepository.ExistsAsync(cancellationToken).ConfigureAwait(false))
@@ -182,11 +182,6 @@ public virtual async Task MigrateAsync(
targetMigration,
out var migratorData);
- foreach (var plugin in _plugins)
- {
- await plugin.MigratingAsync(_currentContext.Context, migratorData, cancellationToken).ConfigureAwait(false);
- }
-
var commandLists = GetMigrationCommandLists(migratorData);
foreach (var commandList in commandLists)
{
@@ -194,18 +189,25 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection,
.ConfigureAwait(false);
}
- foreach (var plugin in _plugins)
- {
- await plugin.MigratedAsync(_currentContext.Context, migratorData, cancellationToken).ConfigureAwait(false);
- }
+ var coreOptionsExtension =
+ _contextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
- if (seed != null)
+ var seedAsync = coreOptionsExtension.AsyncSeeder;
+ if (seedAsync != null)
{
- var transaction = await _connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
+ var context = _currentContext.Context;
+ var operationsPerformed = migratorData.AppliedMigrations.Count != 0
+ || migratorData.RevertedMigrations.Count != 0;
+ var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
await using var __ = transaction.ConfigureAwait(false);
- await seed(_currentContext.Context, migratorData, cancellationToken).ConfigureAwait(false);
+ await seedAsync(context, operationsPerformed, cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
+ else if (coreOptionsExtension.Seeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
+ }
}
finally
{
@@ -213,7 +215,7 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection,
}
}
- private IEnumerable>> GetMigrationCommandLists(IMigratorData parameters)
+ private IEnumerable>> GetMigrationCommandLists(MigratorData parameters)
{
var migrationsToApply = parameters.AppliedMigrations;
var migrationsToRevert = parameters.RevertedMigrations;
@@ -273,7 +275,7 @@ private IEnumerable>> GetMigrationCommandLi
protected virtual void PopulateMigrations(
IEnumerable appliedMigrationEntries,
string? targetMigration,
- out IMigratorData parameters)
+ out MigratorData parameters)
{
var appliedMigrations = new Dictionary();
var unappliedMigrations = new Dictionary();
diff --git a/src/EFCore.Relational/Migrations/Internal/MigratorData.cs b/src/EFCore.Relational/Migrations/Internal/MigratorData.cs
index 97c47555d2f..6a4f0706460 100644
--- a/src/EFCore.Relational/Migrations/Internal/MigratorData.cs
+++ b/src/EFCore.Relational/Migrations/Internal/MigratorData.cs
@@ -13,7 +13,6 @@ public class MigratorData(
IReadOnlyList appliedMigrations,
IReadOnlyList revertedMigrations,
Migration? targetMigration)
- : IMigratorData
{
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -21,7 +20,7 @@ public class MigratorData(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public IReadOnlyList AppliedMigrations { get; } = appliedMigrations;
+ public virtual IReadOnlyList AppliedMigrations { get; } = appliedMigrations;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -29,7 +28,7 @@ public class MigratorData(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public IReadOnlyList RevertedMigrations { get; } = revertedMigrations;
+ public virtual IReadOnlyList RevertedMigrations { get; } = revertedMigrations;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -37,5 +36,5 @@ public class MigratorData(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public Migration? TargetMigration { get; } = targetMigration;
+ public virtual Migration? TargetMigration { get; } = targetMigration;
}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 3b7b9c4c49e..54aabd7a2f9 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -2108,7 +2108,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec
nodeType, expressionType);
///
- /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
+ /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
///
public static string UnsupportedPropertyType(object? entity, object? property, object? clrType)
=> string.Format(
@@ -2233,6 +2233,31 @@ public static class RelationalResources
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.EntityFrameworkCore.Properties.RelationalStrings", typeof(RelationalResources).Assembly);
+ ///
+ /// Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations for more information if this takes too long.
+ ///
+ public static EventDefinition LogAcquiringMigrationLock(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogAcquiringMigrationLock;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogAcquiringMigrationLock,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.AcquiringMigrationLock,
+ LogLevel.Information,
+ "RelationalEventId.AcquiringMigrationLock",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.AcquiringMigrationLock,
+ _resourceManager.GetString("LogAcquiringMigrationLock")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// An ambient transaction has been detected, but the current provider does not support ambient transactions. See https://go.microsoft.com/fwlink/?LinkId=800142
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 7099418d2e3..84f26811cc3 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -1,17 +1,17 @@
-
@@ -589,6 +589,10 @@
Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'.
+
+ Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
+ Information RelationalEventId.AcquiringMigrationLock
+
An ambient transaction has been detected, but the current provider does not support ambient transactions. See https://go.microsoft.com/fwlink/?LinkId=800142Warning RelationalEventId.AmbientTransactionWarning
diff --git a/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs b/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs
index dbbd3b083fb..ddd5799033f 100644
--- a/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs
+++ b/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs
@@ -234,23 +234,40 @@ public virtual async Task EnsureDeletedAsync(CancellationToken cancellatio
///
public virtual bool EnsureCreated()
{
- using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
+ using var transactionScope = new TransactionScope(
+ TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
+
+ var operationsPerformed = false;
+ if (!Exists())
+ {
+ Create();
+ CreateTables();
+ operationsPerformed = true;
+ }
+ else if (!HasTables())
{
- if (!Exists())
- {
- Create();
- CreateTables();
- return true;
- }
-
- if (!HasTables())
- {
- CreateTables();
- return true;
- }
+ CreateTables();
+ operationsPerformed = true;
}
- return false;
+ var coreOptionsExtension =
+ Dependencies.ContextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ var seed = coreOptionsExtension.Seeder;
+ if (seed != null)
+ {
+ var context = Dependencies.CurrentContext.Context;
+ using var transaction = context.Database.BeginTransaction();
+ seed(context, operationsPerformed);
+ transaction.Commit();
+ }
+ else if (coreOptionsExtension.AsyncSeeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
+ }
+
+ return operationsPerformed;
}
///
@@ -266,30 +283,42 @@ public virtual bool EnsureCreated()
/// If the is canceled.
public virtual async Task EnsureCreatedAsync(CancellationToken cancellationToken = default)
{
- var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
- try
+ using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
+
+ var operationsPerformed = false;
+ if (!await ExistsAsync(cancellationToken).ConfigureAwait(false))
{
- if (!await ExistsAsync(cancellationToken).ConfigureAwait(false))
- {
- await CreateAsync(cancellationToken).ConfigureAwait(false);
- await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
+ await CreateAsync(cancellationToken).ConfigureAwait(false);
+ await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
- return true;
- }
+ operationsPerformed = true;
+ }
+ else if (!await HasTablesAsync(cancellationToken).ConfigureAwait(false))
+ {
+ await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
- if (!await HasTablesAsync(cancellationToken).ConfigureAwait(false))
- {
- await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
+ operationsPerformed = true;
+ }
+
+ var coreOptionsExtension =
+ Dependencies.ContextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
- return true;
- }
+ var seedAsync = coreOptionsExtension.AsyncSeeder;
+ if (seedAsync != null)
+ {
+ var context = Dependencies.CurrentContext.Context;
+ var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
+ await using var _ = transaction.ConfigureAwait(false);
+ await seedAsync(context, operationsPerformed, cancellationToken).ConfigureAwait(false);
+ await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
- finally
+ else if (coreOptionsExtension.Seeder != null)
{
- await transactionScope.DisposeAsyncIfAvailable().ConfigureAwait(false);
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
}
- return false;
+ return operationsPerformed;
}
///
diff --git a/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs b/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs
index 0b14d27ec79..8343a802486 100644
--- a/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs
+++ b/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs
@@ -53,6 +53,7 @@ public RelationalDatabaseCreatorDependencies(
ISqlGenerationHelper sqlGenerationHelper,
IExecutionStrategy executionStrategy,
ICurrentDbContext currentContext,
+ IDbContextOptions contextOptions,
IRelationalCommandDiagnosticsLogger commandLogger)
{
Connection = connection;
@@ -62,6 +63,7 @@ public RelationalDatabaseCreatorDependencies(
SqlGenerationHelper = sqlGenerationHelper;
ExecutionStrategy = executionStrategy;
CurrentContext = currentContext;
+ ContextOptions = contextOptions;
CommandLogger = commandLogger;
}
@@ -100,6 +102,11 @@ public RelationalDatabaseCreatorDependencies(
///
public IRelationalCommandDiagnosticsLogger CommandLogger { get; init; }
+ ///
+ /// Gets the context options.
+ ///
+ public IDbContextOptions ContextOptions { get; init; }
+
///
/// Contains the currently in use.
///
diff --git a/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs b/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
index cf365bcc2cb..47bede43829 100644
--- a/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
+++ b/src/EFCore.SqlServer/Migrations/Internal/SqlServerHistoryRepository.cs
@@ -60,13 +60,13 @@ protected override bool InterpretExistsResult(object? value)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override IDisposable GetDatabaseLock(TimeSpan timeout)
+ public override IDisposable GetDatabaseLock()
{
var dbLock = CreateMigrationDatabaseLock();
int result;
try
{
- result = (int)CreateGetLockCommand(timeout).ExecuteScalar(CreateRelationalCommandParameters())!;
+ result = (int)CreateGetLockCommand().ExecuteScalar(CreateRelationalCommandParameters())!;
}
catch
{
@@ -92,13 +92,13 @@ public override IDisposable GetDatabaseLock(TimeSpan timeout)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override async Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
+ public override async Task GetDatabaseLockAsync(CancellationToken cancellationToken = default)
{
var dbLock = CreateMigrationDatabaseLock();
int result;
try
{
- result = (int)(await CreateGetLockCommand(timeout).ExecuteScalarAsync(CreateRelationalCommandParameters(), cancellationToken)
+ result = (int)(await CreateGetLockCommand().ExecuteScalarAsync(CreateRelationalCommandParameters(), cancellationToken)
.ConfigureAwait(false))!;
}
catch
@@ -119,13 +119,13 @@ public override async Task GetDatabaseLockAsync(TimeSpan timeo
: dbLock;
}
- private IRelationalCommand CreateGetLockCommand(TimeSpan timeout)
+ private IRelationalCommand CreateGetLockCommand()
=> Dependencies.RawSqlCommandBuilder.Build("""
DECLARE @result int;
-EXEC @result = sp_getapplock @Resource = '__EFMigrationsLock', @LockOwner = 'Session', @LockMode = 'Exclusive', @LockTimeout = @LockTimeout;
+EXEC @result = sp_getapplock @Resource = '__EFMigrationsLock', @LockOwner = 'Session', @LockMode = 'Exclusive';
SELECT @result
""",
- [new SqlParameter("@LockTimeout", timeout.TotalMilliseconds)]).RelationalCommand;
+ []).RelationalCommand;
private SqlServerMigrationDatabaseLock CreateMigrationDatabaseLock()
=> new(
diff --git a/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteHistoryRepository.cs b/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteHistoryRepository.cs
index 477a43a65eb..26b68e31ffa 100644
--- a/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteHistoryRepository.cs
+++ b/src/EFCore.Sqlite.Core/Migrations/Internal/SqliteHistoryRepository.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Text;
using Microsoft.EntityFrameworkCore.Sqlite.Internal;
namespace Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal;
@@ -103,7 +102,7 @@ public override string GetEndIfScript()
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override IDisposable GetDatabaseLock(TimeSpan timeout)
+ public override IDisposable GetDatabaseLock()
{
if (!InterpretExistsResult(Dependencies.RawSqlCommandBuilder.Build(CreateExistsSql(LockTableName))
.ExecuteScalar(CreateRelationalCommandParameters())))
@@ -112,8 +111,7 @@ public override IDisposable GetDatabaseLock(TimeSpan timeout)
}
var retryDelay = _retryDelay;
- var startTime = DateTimeOffset.UtcNow;
- while (DateTimeOffset.UtcNow - startTime < timeout)
+ while (true)
{
var dbLock = CreateMigrationDatabaseLock();
var insertCount = CreateInsertLockCommand(DateTimeOffset.UtcNow)
@@ -123,17 +121,6 @@ public override IDisposable GetDatabaseLock(TimeSpan timeout)
return dbLock;
}
- using var reader = CreateGetLockCommand().ExecuteReader(CreateRelationalCommandParameters());
- if (reader.Read())
- {
- var timestamp = reader.DbDataReader.GetFieldValue(1);
- if (DateTimeOffset.UtcNow - timestamp > timeout)
- {
- var id = reader.DbDataReader.GetFieldValue(0);
- CreateDeleteLockCommand(id).ExecuteNonQuery(CreateRelationalCommandParameters());
- }
- }
-
Thread.Sleep(retryDelay);
if (retryDelay < TimeSpan.FromMinutes(1))
{
@@ -150,7 +137,7 @@ public override IDisposable GetDatabaseLock(TimeSpan timeout)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override async Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
+ public override async Task GetDatabaseLockAsync(CancellationToken cancellationToken = default)
{
if (!InterpretExistsResult(await Dependencies.RawSqlCommandBuilder.Build(CreateExistsSql(LockTableName))
.ExecuteScalarAsync(CreateRelationalCommandParameters(), cancellationToken).ConfigureAwait(false)))
@@ -159,8 +146,7 @@ public override async Task GetDatabaseLockAsync(TimeSpan timeo
}
var retryDelay = _retryDelay;
- var startTime = DateTimeOffset.UtcNow;
- while (DateTimeOffset.UtcNow - startTime < timeout)
+ while (true)
{
var dbLock = CreateMigrationDatabaseLock();
var insertCount = await CreateInsertLockCommand(DateTimeOffset.UtcNow)
@@ -171,19 +157,6 @@ public override async Task GetDatabaseLockAsync(TimeSpan timeo
return dbLock;
}
- using var reader = await CreateGetLockCommand().ExecuteReaderAsync(CreateRelationalCommandParameters(), cancellationToken)
- .ConfigureAwait(false);
- if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
- {
- var timestamp = await reader.DbDataReader.GetFieldValueAsync(1).ConfigureAwait(false);
- if (DateTimeOffset.UtcNow - timestamp > timeout)
- {
- var id = await reader.DbDataReader.GetFieldValueAsync(0).ConfigureAwait(false);
- await CreateDeleteLockCommand(id).ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken)
- .ConfigureAwait(false);
- }
- }
-
await Task.Delay(_retryDelay, cancellationToken).ConfigureAwait(true);
if (retryDelay < TimeSpan.FromMinutes(1))
{
@@ -212,11 +185,6 @@ private IRelationalCommand CreateInsertLockCommand(DateTimeOffset timestamp)
""");
}
- private IRelationalCommand CreateGetLockCommand()
- => Dependencies.RawSqlCommandBuilder.Build($"""
-SELECT "Id", "Timestamp" FROM "{LockTableName}" LIMIT 1;
-""");
-
private IRelationalCommand CreateDeleteLockCommand(int? id = null)
{
var sql = $"""
diff --git a/src/EFCore/DbContextOptionsBuilder.cs b/src/EFCore/DbContextOptionsBuilder.cs
index c77a4a94144..25179a1f475 100644
--- a/src/EFCore/DbContextOptionsBuilder.cs
+++ b/src/EFCore/DbContextOptionsBuilder.cs
@@ -730,6 +730,50 @@ public virtual DbContextOptionsBuilder AddInterceptors(params IInterceptor[] int
public virtual DbContextOptionsBuilder ConfigureLoggingCacheTime(TimeSpan timeSpan)
=> WithOption(e => e.WithLoggingCacheTime(timeSpan));
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public virtual DbContextOptionsBuilder UseSeeding(Action seed)
+ => WithOption(e => e.WithSeeding(seed));
+
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied asynchronously.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public virtual DbContextOptionsBuilder UseAsyncSeeding(Func seedAsync)
+ => WithOption(e => e.WithAsyncSeeding(seedAsync));
+
///
/// Adds the given extension to the options. If an existing extension of the same type already exists, it will be replaced.
///
diff --git a/src/EFCore/DbContextOptionsBuilder`.cs b/src/EFCore/DbContextOptionsBuilder`.cs
index 6f7953c7d30..5e6b5a9e28f 100644
--- a/src/EFCore/DbContextOptionsBuilder`.cs
+++ b/src/EFCore/DbContextOptionsBuilder`.cs
@@ -629,4 +629,92 @@ public DbContextOptionsBuilder(DbContextOptions options)
/// The same builder instance so that multiple calls can be chained.
public new virtual DbContextOptionsBuilder ConfigureLoggingCacheTime(TimeSpan timeSpan)
=> (DbContextOptionsBuilder)base.ConfigureLoggingCacheTime(timeSpan);
+
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public new virtual DbContextOptionsBuilder UseSeeding(Action seed)
+ => (DbContextOptionsBuilder)base.UseSeeding((c, p) => seed(c, p));
+
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public virtual DbContextOptionsBuilder UseSeeding(Action seed)
+ => (DbContextOptionsBuilder)base.UseSeeding((c, p) => seed((TContext)c, p));
+
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied asynchronously.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public new virtual DbContextOptionsBuilder UseAsyncSeeding(Func seedAsync)
+ => (DbContextOptionsBuilder)base.UseAsyncSeeding((c, p, t) => seedAsync(c, p, t));
+
+ ///
+ /// Configures the seed method to run after
+ /// is called or after migrations are applied asynchronously.
+ /// It will be invoked even if no changes to the store were performed.
+ ///
+ ///
+ ///
+ /// The argument of the seed delegate indicates whether any store management
+ /// operation was performed.
+ ///
+ ///
+ /// It is recomended to also call with the same logic.
+ ///
+ ///
+ /// See Using DbContextOptions for more information and examples.
+ ///
+ ///
+ /// The seed method to run.
+ /// The same builder instance so that multiple calls can be chained.
+ public virtual DbContextOptionsBuilder UseAsyncSeeding(Func seedAsync)
+ => (DbContextOptionsBuilder)base.UseAsyncSeeding((c, p, t) => seedAsync((TContext)c, p, t));
}
diff --git a/src/EFCore/Infrastructure/CoreOptionsExtension.cs b/src/EFCore/Infrastructure/CoreOptionsExtension.cs
index 02d7a2c4161..3c87a8f759b 100644
--- a/src/EFCore/Infrastructure/CoreOptionsExtension.cs
+++ b/src/EFCore/Infrastructure/CoreOptionsExtension.cs
@@ -42,6 +42,8 @@ public class CoreOptionsExtension : IDbContextOptionsExtension
private DbContextOptionsExtensionInfo? _info;
private IEnumerable? _interceptors;
private IEnumerable? _singletonInterceptors;
+ private Action? _seed;
+ private Func? _seedAsync;
private static readonly TimeSpan DefaultLoggingCacheTime = TimeSpan.FromSeconds(1);
@@ -85,6 +87,8 @@ protected CoreOptionsExtension(CoreOptionsExtension copyFrom)
_serviceProviderCachingEnabled = copyFrom.ServiceProviderCachingEnabled;
_interceptors = copyFrom.Interceptors?.ToList();
_singletonInterceptors = copyFrom.SingletonInterceptors?.ToList();
+ _seed = copyFrom._seed;
+ _seedAsync = copyFrom._seedAsync;
if (copyFrom._replacedServices != null)
{
@@ -407,6 +411,36 @@ public virtual CoreOptionsExtension WithSingletonInterceptors(IEnumerable
+ /// Creates a new instance with all options the same as for this instance, but with the given option changed.
+ /// It is unusual to call this method directly. Instead use .
+ ///
+ /// The option to change.
+ /// A new instance with the option changed.
+ public virtual CoreOptionsExtension WithSeeding(Action seed)
+ {
+ var clone = Clone();
+
+ clone._seed = seed;
+
+ return clone;
+ }
+
+ ///
+ /// Creates a new instance with all options the same as for this instance, but with the given option changed.
+ /// It is unusual to call this method directly. Instead use .
+ ///
+ /// The option to change.
+ /// A new instance with the option changed.
+ public virtual CoreOptionsExtension WithAsyncSeeding(Func seedAsync)
+ {
+ var clone = Clone();
+
+ clone._seedAsync = seedAsync;
+
+ return clone;
+ }
+
///
/// The option set from the method.
///
@@ -529,6 +563,24 @@ public virtual IEnumerable? Interceptors
public virtual IEnumerable? SingletonInterceptors
=> _singletonInterceptors;
+ ///
+ /// The option set from the
+ ///
+ /// method.
+ ///
+ public virtual Action? Seeder
+ => _seed;
+
+ ///
+ /// The option set from the
+ ///
+ /// method.
+ ///
+ public virtual Func? AsyncSeeder
+ => _seedAsync;
+
///
/// Adds the services required to make the selected options work. This is used when there
/// is no external and EF is maintaining its own service
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 1a9d3320fa9..1aa731b3728 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -1155,7 +1155,7 @@ public static string ErrorMaterializingPropertyInvalidCast(object? entityType, o
entityType, property, expectedType, actualType);
///
- /// The methods '{methodName}' and '{asyncMethodName}' are not supported by the current database provider. Please contact the publisher of the database provider for more information.
+ /// The methods '{methodName}' and '{asyncMethodName}' are not supported by the current database provider. Please contact the publisher of the database provider for more information.
///
public static string ExecuteQueriesNotSupported(object? methodName, object? asyncMethodName)
=> string.Format(
@@ -1827,6 +1827,12 @@ public static string MemberListBindingNotSupported
public static string MemberMemberBindingNotSupported
=> GetString("MemberMemberBindingNotSupported");
+ ///
+ /// An asynchronous store managment operation was performed and no asynchronous seed delegate has been provided, however a synchronous seed delegate was. Set 'UseAsyncSeeding' option with a delegate equivalent to the one supplied in 'UseSeeding'.
+ ///
+ public static string MissingAsyncSeeder
+ => GetString("MissingAsyncSeeder");
+
///
/// The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.
///
@@ -1835,6 +1841,12 @@ public static string MissingBackingField(object? field, object? property, object
GetString("MissingBackingField", nameof(field), "1_property", "2_entityType"),
field, property, entityType);
+ ///
+ /// A synchronous store managment operation was performed and no synchronous seed delegate has been provided, however an asynchronous seed delegate was. Set 'UseSeeding' option with a delegate equivalent to the one supplied in 'UseAsyncSeeding'.
+ ///
+ public static string MissingSeeder
+ => GetString("MissingSeeder");
+
///
/// Runtime metadata changes are not allowed when the model hasn't been marked as read-only.
///
@@ -2159,14 +2171,6 @@ public static string NonIndexerEntityType(object? property, object? entityType,
GetString("NonIndexerEntityType", nameof(property), nameof(entityType), nameof(type)),
property, entityType, type);
- ///
- /// The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
- ///
- public static string NonQueryTranslationFailedWithDetails(object? expression, object? details)
- => string.Format(
- GetString("NonQueryTranslationFailedWithDetails", nameof(expression), nameof(details)),
- expression, details);
-
///
/// The collection type '{2_collectionType}' being used for navigation '{1_entityType}.{0_navigation}' does not implement 'INotifyCollectionChanged'. Any entity type configured to use the '{changeTrackingStrategy}' change tracking strategy must use collections that implement 'INotifyCollectionChanged'. Consider using 'ObservableCollection<T>' for this.
///
@@ -2175,6 +2179,14 @@ public static string NonNotifyingCollection(object? navigation, object? entityTy
GetString("NonNotifyingCollection", "0_navigation", "1_entityType", "2_collectionType", nameof(changeTrackingStrategy)),
navigation, entityType, collectionType, changeTrackingStrategy);
+ ///
+ /// The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
+ ///
+ public static string NonQueryTranslationFailedWithDetails(object? expression, object? details)
+ => string.Format(
+ GetString("NonQueryTranslationFailedWithDetails", nameof(expression), nameof(details)),
+ expression, details);
+
///
/// The foreign key {foreignKeyProperties} on the entity type '{declaringEntityType}' cannot have a required dependent end since it is not unique.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 41d62da4122..4849feeeac3 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -1139,9 +1139,15 @@
EF Core does not support MemberMemberBinding: 'new Blog { Data = { Name = "hello world" } }'.
+
+ An asynchronous store managment operation was performed and no asynchronous seed delegate has been provided, however a synchronous seed delegate was. Set 'UseAsyncSeeding' option with a delegate equivalent to the one supplied in 'UseSeeding'.
+
The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.
+
+ A synchronous store managment operation was performed and no synchronous seed delegate has been provided, however an asynchronous seed delegate was. Set 'UseSeeding' option with a delegate equivalent to the one supplied in 'UseAsyncSeeding'.
+
Runtime metadata changes are not allowed when the model hasn't been marked as read-only.
diff --git a/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs
index b690a982c4a..599d18467d9 100644
--- a/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs
@@ -59,39 +59,39 @@ protected override IDbContextTransaction BeginTransaction(DatabaseFacade facade)
=> new FakeDbContextTransaction();
public override Task Calling_Reload_on_an_Added_entity_that_is_not_in_database_is_no_op(bool async)
- => CosmosTestHelpers.Instance.NoSyncTest(async, a => base.Calling_Reload_on_an_Added_entity_that_is_not_in_database_is_no_op(a));
+ => CosmosTestHelpers.Instance.NoSyncTest(async, base.Calling_Reload_on_an_Added_entity_that_is_not_in_database_is_no_op);
public override Task Calling_Reload_on_an_Unchanged_entity_that_is_not_in_database_detaches_it(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
- async, a => base.Calling_Reload_on_an_Unchanged_entity_that_is_not_in_database_detaches_it(a));
+ async, base.Calling_Reload_on_an_Unchanged_entity_that_is_not_in_database_detaches_it);
public override Task Calling_Reload_on_a_Modified_entity_that_is_not_in_database_detaches_it(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
- async, a => base.Calling_Reload_on_a_Modified_entity_that_is_not_in_database_detaches_it(a));
+ async, base.Calling_Reload_on_a_Modified_entity_that_is_not_in_database_detaches_it);
public override Task Calling_Reload_on_a_Deleted_entity_that_is_not_in_database_detaches_it(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
- async, a => base.Calling_Reload_on_a_Deleted_entity_that_is_not_in_database_detaches_it(a));
+ async, base.Calling_Reload_on_a_Deleted_entity_that_is_not_in_database_detaches_it);
public override Task Calling_Reload_on_a_Detached_entity_that_is_not_in_database_detaches_it(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
- async, a => base.Calling_Reload_on_a_Detached_entity_that_is_not_in_database_detaches_it(a));
+ async, base.Calling_Reload_on_a_Detached_entity_that_is_not_in_database_detaches_it);
public override Task Calling_Reload_on_an_Unchanged_entity_makes_the_entity_unchanged(bool async)
- => CosmosTestHelpers.Instance.NoSyncTest(async, a => base.Calling_Reload_on_an_Unchanged_entity_makes_the_entity_unchanged(a));
+ => CosmosTestHelpers.Instance.NoSyncTest(async, base.Calling_Reload_on_an_Unchanged_entity_makes_the_entity_unchanged);
public override Task Calling_Reload_on_a_Modified_entity_makes_the_entity_unchanged(bool async)
- => CosmosTestHelpers.Instance.NoSyncTest(async, a => base.Calling_Reload_on_a_Modified_entity_makes_the_entity_unchanged(a));
+ => CosmosTestHelpers.Instance.NoSyncTest(async, base.Calling_Reload_on_a_Modified_entity_makes_the_entity_unchanged);
public override Task Calling_Reload_on_a_Deleted_entity_makes_the_entity_unchanged(bool async)
- => CosmosTestHelpers.Instance.NoSyncTest(async, a => base.Calling_Reload_on_a_Deleted_entity_makes_the_entity_unchanged(a));
+ => CosmosTestHelpers.Instance.NoSyncTest(async, base.Calling_Reload_on_a_Deleted_entity_makes_the_entity_unchanged);
public override Task Calling_Reload_on_an_Added_entity_that_was_saved_elsewhere_makes_the_entity_unchanged(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
- async, a => base.Calling_Reload_on_an_Added_entity_that_was_saved_elsewhere_makes_the_entity_unchanged(a));
+ async, base.Calling_Reload_on_an_Added_entity_that_was_saved_elsewhere_makes_the_entity_unchanged);
public override Task Calling_Reload_on_a_Detached_entity_makes_the_entity_unchanged(bool async)
- => CosmosTestHelpers.Instance.NoSyncTest(async, a => base.Calling_Reload_on_a_Detached_entity_makes_the_entity_unchanged(a));
+ => CosmosTestHelpers.Instance.NoSyncTest(async, base.Calling_Reload_on_a_Detached_entity_makes_the_entity_unchanged);
private class FakeDbContextTransaction : IDbContextTransaction
{
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/JsonQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/JsonQueryCosmosTest.cs
index d803f3ec4b5..46f242cc84c 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/JsonQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/JsonQueryCosmosTest.cs
@@ -7,6 +7,7 @@
namespace Microsoft.EntityFrameworkCore.Query;
+[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)]
public class JsonQueryCosmosTest : JsonQueryTestBase
{
private const string NotImplementedBindPropertyMessage
diff --git a/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs b/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs
index 5d879385922..e9f9bb666cb 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs
@@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Storage;
[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)]
public class CosmosDatabaseCreatorTest
{
- public static IEnumerable