dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.8k stars 3.2k forks source link

Programmatic Scaffold-Migration Issue After Updating to 9.0.0 #35146

Open oONoobMasterOo opened 3 days ago

oONoobMasterOo commented 3 days ago

After updating Npgsql to version 9.0.0, I encounter the following exception while attempting to scaffold migrations programmatically:

System.InvalidOperationException: Metadata changes are not allowed when the model has been marked as read-only.
   at Microsoft.EntityFrameworkCore.Infrastructure.Annotatable.EnsureMutable()
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.RemoveAnnotation(String name)
   at Microsoft.EntityFrameworkCore.Infrastructure.Annotatable.Microsoft.EntityFrameworkCore.Metadata.IMutableAnnotatable.RemoveAnnotation(String name)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.SnapshotModelProcessor.Process(IReadOnlyModel model, Boolean resetVersion)
   at Microsoft.EntityFrameworkCore.Migrations.Design.MigrationsScaffolder.ScaffoldMigration(String migrationName, String rootNamespace, String subNamespace, String language, Boolean dryRun)

my scaffold code:

var services = new ServiceCollection().AddEntityFrameworkDesignTimeServices().AddDbContextDesignTimeServices(context);

 var designTimeServices = new SqlServerDesignTimeServices();
 designTimeServices.ConfigureDesignTimeServices(services);

 var serviceProvider = services.BuildServiceProvider();
 var scaffolder = serviceProvider.GetRequiredService<IMigrationsScaffolder>();

 var migrationPath = Path.Combine(Hosting.ContentRootPath, @"..\", SystemName, "Migrations", DbContext.UseSqlServerProvider ? "SqlServer" : "NpgSql");
 if (!Directory.Exists(migrationPath)) Directory.CreateDirectory(migrationPath);

 var migration = scaffolder.ScaffoldMigration("my-migration-name","namespace"); 
cincuranet commented 3 days ago

This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

oONoobMasterOo commented 1 day ago

here is my full scaffold code that worked perfectly at version 8.0.11:

using (var DbContext = Activator.CreateInstance(MainDbContext, DbContextOptionsBuilderObject.Options, HttpContextAccessor) as DbContext)
{
    var relationalDatabaseCreator = DbContext.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
    var databaseExists = await relationalDatabaseCreator.ExistsAsync();
    var hasDifferences = false;

    var Migrations = DbContext.Database.GetMigrations();
    var migrationsAssembly = DbContext.GetService<IMigrationsAssembly>();
    if (migrationsAssembly.ModelSnapshot != null)
    {
        var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

        if (snapshotModel is IMutableModel mutableModel)
        {
            snapshotModel = mutableModel.FinalizeModel();
        }

        if (snapshotModel != null)
        {
            snapshotModel = DbContext.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
        }

        hasDifferences = DbContext.GetService<IMigrationsModelDiffer>().HasDifferences(snapshotModel?.GetRelationalModel(), DbContext.GetService<IDesignTimeModel>().Model?.GetRelationalModel());
    }
    if (!databaseExists || hasDifferences || migrationsAssembly.ModelSnapshot == null)
    {
        var services = new ServiceCollection().AddEntityFrameworkDesignTimeServices().AddDbContextDesignTimeServices(DbContext);

        var designTimeServices = new SqlServerDesignTimeServices();
        designTimeServices.ConfigureDesignTimeServices(services);

        var serviceProvider = services.BuildServiceProvider();
        var scaffolder = serviceProvider.GetRequiredService<IMigrationsScaffolder>();

        var migrationPath = Path.Combine(Hosting.ContentRootPath, @"..\", SystemName, "Migrations", DbContext.UseSqlServerProvider ? "SqlServer" : "NpgSql");
        if (!Directory.Exists(migrationPath)) Directory.CreateDirectory(migrationPath);

        var migration = scaffolder.ScaffoldMigration($"Migration-test1", $"Company.Systems.{SystemName}", $"Migrations.{(DbContext.UseSqlServerProvider ? "SqlServer" : "NpgSql")}");

        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.MigrationId + migration.FileExtension), migration.MigrationCode);
        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.MigrationId + ".Designer" + migration.FileExtension), migration.MetadataCode);
        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.SnapshotName + migration.FileExtension), migration.SnapshotCode);

        result.Add(new(true, "Main DbContext Scaffolded"));
    }
}

the exception happens at scaffolder.ScaffoldMigration()

AndriySvyryd commented 1 day ago

I'm not sure why you scaffold a new migration when the database doesn't exist, but try replacing hasDifferences || migrationsAssembly.ModelSnapshot == null with DbContext.Database.HasPendingModelChanges() and remove all the code that's no longer necessary:

using (var DbContext = Activator.CreateInstance(MainDbContext, DbContextOptionsBuilderObject.Options, HttpContextAccessor) as DbContext)
{
    var relationalDatabaseCreator = DbContext.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
    var databaseExists = await relationalDatabaseCreator.ExistsAsync();
    if (!databaseExists || DbContext.Database.HasPendingModelChanges())
    {
        var services = new ServiceCollection().AddEntityFrameworkDesignTimeServices().AddDbContextDesignTimeServices(DbContext);

        var designTimeServices = new SqlServerDesignTimeServices();
        designTimeServices.ConfigureDesignTimeServices(services);

        var serviceProvider = services.BuildServiceProvider();
        var scaffolder = serviceProvider.GetRequiredService<IMigrationsScaffolder>();

        var migrationPath = Path.Combine(Hosting.ContentRootPath, @"..\", SystemName, "Migrations", DbContext.UseSqlServerProvider ? "SqlServer" : "NpgSql");
        if (!Directory.Exists(migrationPath)) Directory.CreateDirectory(migrationPath);

        var migration = scaffolder.ScaffoldMigration($"Migration-test1", $"Company.Systems.{SystemName}", $"Migrations.{(DbContext.UseSqlServerProvider ? "SqlServer" : "NpgSql")}");

        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.MigrationId + migration.FileExtension), migration.MigrationCode);
        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.MigrationId + ".Designer" + migration.FileExtension), migration.MetadataCode);
        await System.IO.File.WriteAllTextAsync(Path.Combine(migrationPath, migration.SnapshotName + migration.FileExtension), migration.SnapshotCode);

        result.Add(new(true, "Main DbContext Scaffolded"));
    }
}
oONoobMasterOo commented 23 hours ago

I've used your code but am still encountering the error.

I’d like to debug it myself. Could you let me know which EF Core projects I should reference?