diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
index e46852477e3..ce010e25e54 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
@@ -17,6 +17,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
@@ -28,12 +30,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;
}
///
@@ -54,7 +60,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;
@@ -80,7 +100,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;
@@ -139,9 +173,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());
}
@@ -152,14 +186,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 43a22860130..da28c248e01 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/Extensions/RelationalDatabaseFacadeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
index 1e059f7bb04..5d8e3bd558f 100644
--- a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
@@ -123,9 +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.
- ///
///
///
/// Note that this API is mutually exclusive with . EnsureCreated does not use migrations
@@ -141,9 +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)
- => databaseFacade.GetRelationalService().Migrate(seed, targetMigration);
+ string? targetMigration)
+ => databaseFacade.GetRelationalService().Migrate(targetMigration);
///
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
@@ -179,9 +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.
- ///
/// A to observe while waiting for the task to complete.
///
///
@@ -200,10 +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,
+ string? targetMigration,
CancellationToken cancellationToken = default)
- => databaseFacade.GetRelationalService().MigrateAsync(seed, targetMigration, 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/IMigrator.cs b/src/EFCore.Relational/Migrations/IMigrator.cs
index 0f6e7eea25d..b7735b4f0f7 100644
--- a/src/EFCore.Relational/Migrations/IMigrator.cs
+++ b/src/EFCore.Relational/Migrations/IMigrator.cs
@@ -26,9 +26,6 @@ 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.
///
@@ -37,15 +34,12 @@ public interface IMigrator
///
[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);
+ 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.
///
@@ -58,7 +52,6 @@ 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,
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 275d4b3d258..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/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
index f11f168c755..bed46c5073b 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 static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace Microsoft.EntityFrameworkCore.Migrations.Internal;
@@ -26,10 +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 readonly IDbContextOptions _contextOptions;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -51,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;
@@ -67,10 +66,10 @@ public Migrator(
_modelRuntimeInitializer = modelRuntimeInitializer;
_logger = logger;
_commandLogger = commandLogger;
- _plugins = plugins;
_migrationsModelDiffer = migrationsModelDiffer;
_designTimeModel = designTimeModel;
_activeProvider = databaseProvider.Name;
+ _contextOptions = contextOptions;
}
///
@@ -79,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)
+ public virtual void Migrate(string? targetMigration)
{
if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
@@ -110,28 +109,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
{
@@ -146,7 +147,6 @@ 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,
CancellationToken cancellationToken = default)
{
@@ -181,11 +181,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)
{
@@ -193,18 +188,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
{
@@ -212,7 +214,7 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection,
}
}
- private IEnumerable>> GetMigrationCommandLists(IMigratorData parameters)
+ private IEnumerable>> GetMigrationCommandLists(MigratorData parameters)
{
var migrationsToApply = parameters.AppliedMigrations;
var migrationsToRevert = parameters.RevertedMigrations;
@@ -272,7 +274,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..84068ea7053 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
diff --git a/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs b/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs
index dbbd3b083fb..76450abe6b7 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())
{
- if (!Exists())
- {
- Create();
- CreateTables();
- return true;
- }
+ Create();
+ CreateTables();
+ operationsPerformed = true;
+ }
+ else if (!HasTables())
+ {
+ CreateTables();
+ operationsPerformed = true;
+ }
- if (!HasTables())
- {
- CreateTables();
- return true;
- }
+ 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 false;
+ return operationsPerformed;
}
///
@@ -267,6 +284,8 @@ public virtual bool EnsureCreated()
public virtual async Task EnsureCreatedAsync(CancellationToken cancellationToken = default)
{
var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
+
+ var operationsPerformed = false;
try
{
if (!await ExistsAsync(cancellationToken).ConfigureAwait(false))
@@ -274,14 +293,31 @@ public virtual async Task EnsureCreatedAsync(CancellationToken cancellatio
await CreateAsync(cancellationToken).ConfigureAwait(false);
await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
- return true;
+ operationsPerformed = true;
}
-
- if (!await HasTablesAsync(cancellationToken).ConfigureAwait(false))
+ else if (!await HasTablesAsync(cancellationToken).ConfigureAwait(false))
{
await CreateTablesAsync(cancellationToken).ConfigureAwait(false);
- return true;
+ operationsPerformed = true;
+ }
+
+ var coreOptionsExtension =
+ Dependencies.ContextOptions.FindExtension()
+ ?? new CoreOptionsExtension();
+
+ 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);
+ }
+ else if (coreOptionsExtension.Seeder != null)
+ {
+ throw new InvalidOperationException(CoreStrings.MissingSeeder);
}
}
finally
@@ -289,7 +325,7 @@ public virtual async Task EnsureCreatedAsync(CancellationToken cancellatio
await transactionScope.DisposeAsyncIfAvailable().ConfigureAwait(false);
}
- 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/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..4ae76051683 100644
--- a/src/EFCore/DbContextOptionsBuilder`.cs
+++ b/src/EFCore/DbContextOptionsBuilder`.cs
@@ -629,4 +629,48 @@ 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 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 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 ed27431a234..7154d3d3615 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -1141,7 +1141,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(
@@ -1813,6 +1813,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}'.
///
@@ -1821,6 +1827,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.
///
@@ -2145,14 +2157,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.
///
@@ -2161,6 +2165,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 dd90b7d71b3..67ad8b40583 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -1133,9 +1133,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