有没有办法以编程方式检查 Entity Framework Core 中的待定模型更改?

Posted

技术标签:

【中文标题】有没有办法以编程方式检查 Entity Framework Core 中的待定模型更改?【英文标题】:Is there a way to programmatically check pending model changes in Entity Framework Core? 【发布时间】:2020-06-26 08:25:33 【问题描述】:

我目前正在为 ASP.NET Core WebAPI 开发设置团队环境,使用 xUnit 和 GitLab CI 进行单元测试。对于数据库通信,我们使用 EF Core。

对于 EF Core,我们将使用 Code First 迁移,我们担心开发人员可能只会更新模型,而不会为他们的模型更改创建迁移。 因此,我们希望我们的 CI 运行代码库中存在的所有迁移,将它们与代码优先模型的当前状态进行比较,并在代码优先模型状态不等于运行所有迁移所产生的状态时失败。

有没有办法做到这一点?我在 EF Core 文档中找不到任何关于此的内容。

【问题讨论】:

AFAIK 无法在代码中比较模型状态,但您可以使用 CLI 生成 SQL 命令并进行比较?不是那种干净,但你有一个基本的比较。 我在这里有一些代码可以做到这一点:github.com/ErikEJ/EFCorePowerTools/blob/master/src/GUI/efpt/… - 但为什么不首先考虑数据库呢? @ErikEJ 因为 EF Core 不首先支持数据库,并且迁移提供了很多好处。感谢您的示例代码,我会检查一下:)。 @hyvte 感谢您的建议,我确实想到了这一点,但我更喜欢更“干净”的方法。 EF Core 优先支持数据库! 【参考方案1】:

如果您使用的是 EF (core) 5,则需要稍微不同的版本(也改编自 @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 );
    

【讨论】:

如何将其迁移到 EF Core 6?【参考方案2】:

对于 EF Core 6,来自 @ErikEJ 的优秀 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

【讨论】:

【参考方案3】:

感谢@ErikEJ 的示例代码,我能够编写以下测试,完全符合我的要求:

    using FluentAssertions;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Infrastructure;
    using Microsoft.EntityFrameworkCore.Migrations;
    using Xunit;

    /// <summary>
    /// Contains a test that verifies that the
    /// model does not contain any changes that are not included
    /// in the migrations.
    /// </summary>
    public class NoPendingModelChangesTest
    
        private static readonly string DummyConnectionString = @"Server=localhost;Database=DoesNotExist;Trusted_Connection=True;";

        /// <summary>
        /// Tests that the current model does not contain any changes
        /// that are not contained in the database migrators.
        /// In other words: tests that the current model state equals the
        /// state that results from all the migrations combined.
        /// </summary>
        [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 MyDatabase(
                new DbContextOptionsBuilder<MyDatabase>()
                    .UseSqlServer(DummyConnectionString)
                    .Options);

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

            var pendingModelChanges = modelDiffer
                .GetDifferences(
                    migrationsAssembly.ModelSnapshot?.Model,
                    ctx.Model);

            pendingModelChanges
                .Should()
                .BeEmpty(
                    because:
                        "the current model state should be equal to the state that results from all the migrations combined (try scaffolding a migration)");
        
    

【讨论】:

以上是关于有没有办法以编程方式检查 Entity Framework Core 中的待定模型更改?的主要内容,如果未能解决你的问题,请参考以下文章

C++:有没有办法以编程方式检查我的打印机是喷墨打印机还是激光打印机?

有没有办法以编程方式启用 POP

以编程方式检查 redshift 集群状态

有没有办法以编程方式识别 tcp 连接/端口的状态?

检查 Stackable 文件系统是不是以编程方式安装

有没有办法以编程方式测试浏览器 GPU 加速?