Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting pending model changes with EF Core 5 (in EF Core Power Tools) does not work #21953

Closed
ErikEJ opened this issue Aug 6, 2020 · 10 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@ErikEJ
Copy link
Contributor

ErikEJ commented Aug 6, 2020

Trying to get pending model changes fails with error - I am probably doing something wrong!

System.InvalidOperationException: The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed.
at Microsoft.EntityFrameworkCore.RelationalModelExtensions.GetRelationalModel(IModel model)

Steps to reproduce

        private string GetMigrationStatus(DbContext context)
        {
            var relationalDatabaseCreator = context.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
            if (relationalDatabaseCreator == null)
            {
                throw new Exception("Not a relational database, migrations are not supported");
            }
            var databaseExists = relationalDatabaseCreator.Exists();

            var migrationsAssembly = context.GetService<IMigrationsAssembly>();
            var modelDiffer = context.GetService<IMigrationsModelDiffer>();

#if CORE50
            var pendingModelChanges
                = (!databaseExists || migrationsAssembly.ModelSnapshot != null)
                    && modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model.GetRelationalModel(), context.Model.GetRelationalModel());
#else
            var pendingModelChanges
                = (!databaseExists || migrationsAssembly.ModelSnapshot != null)
                    && modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, context.Model);
#endif
            if (pendingModelChanges) return "Changes";

            var migrations = context.Database.GetMigrations().ToArray();

            if (!migrations.Any()) return "NoMigrations";

            var pendingMigrations
                = (databaseExists
                    ? context.Database.GetPendingMigrations()
                    : migrations)
                .ToArray();

            if (pendingMigrations.Any()) return "Pending";

            return "InSync";
        }

Further technical details

EF Core version: 5, preview 6
Database provider: Microsoft.EntityFrameworkCore.SqlServer)
Target framework: .NET Core 3.1

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 6, 2020

I tried:

        var modelProcessor = context.GetService<ISnapshotModelProcessor>();
        var contextModel = context.Model.GetRelationalModel();
        var modelSnapshot = migrationsAssembly.ModelSnapshot;
        var lastModel = modelProcessor.Process(modelSnapshot?.Model)?.GetRelationalModel(); 
        var pendingModelChanges
            = (!databaseExists || migrationsAssembly.ModelSnapshot != null)
                && modelDiffer.HasDifferences(lastModel, contextModel);
                && modelDiffer.HasDifferences(lastModel, contextModel);

But then I got:

System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Migrations.Internal.ISnapshotModelProcessor'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)

(and a database provider IS configured)

@AndriySvyryd
Copy link
Member

@ErikEJ ISnapshotModelProcessor service is added by DesignTimeServicesBuilder.Build(context)

But if you don't need to process old snapshots a simpler way to do this would be

var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);

var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);

var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
    ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
    context.Model.GetRelationalModel());

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 7, 2020

OMG - thanks, I will give it a try 😄

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 7, 2020

@AndriySvyryd Why do I need this 2 unused variables??

var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);

var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);

@AndriySvyryd
Copy link
Member

You don't need to keep them after the conventions have run.

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 8, 2020

I would say "simpler way" is a strong word (compared to the EF Core 3 solution)

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 8, 2020

I must be doing something wrong - unsure about the references to modelSnapshot and snapshot variables in your sample.

Now I am getting:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.OnAnnotationSet(String name, IConventionAnnotation annotation, IConventionAnnotation oldAnnotation)
   at Microsoft.EntityFrameworkCore.Infrastructure.ConventionAnnotatable.OnAnnotationSet(String name, Annotation annotation, Annotation oldAnnotation)
   at Microsoft.EntityFrameworkCore.Infrastructure.Annotatable.SetAnnotation(String name, Annotation annotation, Annotation oldAnnotation)
   at Microsoft.EntityFrameworkCore.Infrastructure.ConventionAnnotatable.SetAnnotation(String name, Object value, ConfigurationSource configurationSource)
   at Microsoft.EntityFrameworkCore.Infrastructure.ConventionAnnotatable.Microsoft.EntityFrameworkCore.Metadata.IConventionAnnotatable.SetAnnotation(String name, Object value, Boolean fromDataAnnotation)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Add(IConventionModel model, IRelationalAnnotationProvider relationalAnnotationProvider)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationalModelConvention.ProcessModelFinalized(IModel model)
   at ReverseEngineer20.EfCoreMigrationsBuilder.GetMigrationStatus(DbContext context) in C:\Code\EFCorePowerTools\src\GUI\efpt\EFCoreMigrationsBuilder.cs:line 118
   at ReverseEngineer20.EfCoreMigrationsBuilder.GetMigrationStatus(String outputPath) in C:\Code\EFCorePowerTools\src\GUI\efpt

Running this code:

        private string GetMigrationStatus(DbContext context)
        {
            var relationalDatabaseCreator = context.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
            if (relationalDatabaseCreator == null)
            {
                throw new Exception("Not a relational database, migrations are not supported");
            }
            var databaseExists = relationalDatabaseCreator.Exists();

            var migrationsAssembly = context.GetService<IMigrationsAssembly>();
            var modelDiffer = context.GetService<IMigrationsModelDiffer>();

            var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
            var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

            if (migrationsAssembly.ModelSnapshot != null)
            {
                var typeMappingConvention = new TypeMappingConvention(dependencies);
                typeMappingConvention.ProcessModelFinalizing(((IConventionModel)migrationsAssembly.ModelSnapshot.Model).Builder, null);
            }

            var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
            var sourceModel = relationalModelConvention.ProcessModelFinalized(context.Model);

            var hasDifferences = modelDiffer.HasDifferences(
                ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
                context.Model.GetRelationalModel());

            var pendingModelChanges
                = (!databaseExists || migrationsAssembly.ModelSnapshot != null)
                    && hasDifferences;

            if (pendingModelChanges) return "Changes";

            var migrations = context.Database.GetMigrations().ToArray();

            if (!migrations.Any()) return "NoMigrations";

            var pendingMigrations
                = (databaseExists
                    ? context.Database.GetPendingMigrations()
                    : migrations)
                .ToArray();

            if (pendingMigrations.Any()) return "Pending";

            return "InSync";
        }

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 8, 2020

I WAS doing it wrong - ended up with this, which appears to work to some extent:

            var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
            var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

            var hasDifferences = false;

            if (migrationsAssembly.ModelSnapshot != null)
            {
                var typeMappingConvention = new TypeMappingConvention(dependencies);
                typeMappingConvention.ProcessModelFinalizing(((IConventionModel)migrationsAssembly.ModelSnapshot.Model).Builder, null);

                var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
                var sourceModel = relationalModelConvention.ProcessModelFinalized(migrationsAssembly.ModelSnapshot.Model);

                hasDifferences = modelDiffer.HasDifferences(
                     ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
                    context.Model.GetRelationalModel());
            }

            var pendingModelChanges = (!databaseExists || hasDifferences);

@ErikEJ
Copy link
Contributor Author

ErikEJ commented Aug 8, 2020

@AndriySvyryd Thanks for your help, btw!

@dotnet dotnet deleted a comment Aug 8, 2020
@AndriySvyryd
Copy link
Member

I would say "simpler way" is a strong word (compared to the EF Core 3 solution)

I was comparing it to the DesignTimeServicesBuilder.Build(context) way. This will become simpler when #9329 is implemented.

@ajcvickers ajcvickers added the closed-no-further-action The issue is closed and no further action is planned. label Aug 10, 2020
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

3 participants