Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to programmatically check pending model changes in Entity Framework Core?

I am currently in the progress of setting up a team environment for ASP.NET Core WebAPI development, using xUnit for unit tests in combination with GitLab CI. For database communication, we use EF Core.

For EF Core we are going to use Code First Migrations and we are worried that a developer might only update the model and not also create a migration for their model change. Thus, we want our CI to run all migrations that exist in the codebase, compare them with the current state of the code first model and fail when the code first model state is not equal to the state that results from running all the migrations.

Is there a way to do this? I cannot find anything about this in the EF Core documentation.

like image 489
Arthur Heidt Avatar asked Mar 02 '23 17:03

Arthur Heidt


2 Answers

For EF Core 6, from @ErikEJ's excellent EF Core Power Tools:

var migrationsAssembly = _ctx.GetService<IMigrationsAssembly>();

var hasDifferences = false;
if (migrationsAssembly.ModelSnapshot != null) {
    var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

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

    snapshotModel = _ctx.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
    hasDifferences = _ctx.GetService<IMigrationsModelDiffer>().HasDifferences(
        snapshotModel.GetRelationalModel(),
        _ctx.GetService<IDesignTimeModel>().Model.GetRelationalModel());
}

https://github.com/ErikEJ/EFCorePowerTools/blob/5a16c37c59be854605f3e81d3131011d96c96704/src/GUI/efpt30.core/EFCoreMigrationsBuilder.cs#L98

like image 197
Simopaa Avatar answered Mar 05 '23 16:03

Simopaa


If you are using EF (core) 5 you'll need a slightly different version (also adapted from @ErikEJ sample code)

    [Fact]
    public void ModelDoesNotContainPendingChanges()
    {
        // Do not use the test database, the SQL Server model provider must be
        // used as that is the model provider that is used for scaffolding migrations.
        using var ctx = new DataContext(
            new DbContextOptionsBuilder<DataContext>()
                .UseNpgsql(DummyConnectionString)
                .Options);

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

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

        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);

        var finalSourceModel = ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel();
        var finalTargetModel = ctx.Model.GetRelationalModel();

        var hasDifferences = modelDiffer.HasDifferences(finalSourceModel, finalTargetModel);
        if(hasDifferences)
        {
            var changes = modelDiffer.GetDifferences(finalSourceModel, finalTargetModel);
            Assert.True(false, $"{changes.Count} changes between migrations and model. Debug this test for more details");
        }

        Assert.False( hasDifferences );
    }
like image 20
Ben Avatar answered Mar 05 '23 14:03

Ben