如何使用Entity Framework Core功能更改数据库?

时间:2018-04-20 16:27:07

标签: c# asp.net-core entity-framework-core

我正在构建一个基于Entity Framework Core的应用程序,我希望在运行时应用迁移。

我的目标是将当前数据库模型放在内存中并创建一个新模型,然后使用IMigrationsModelDiffer.GetDifferences().计算两个模型之间的差异

从那里开始,我不想将差异打印到Migration类中,而是直接创建MigrationCommand并将这些命令应用到我的数据库中。

以上听起来相当简单但我在依赖注入系统方面遇到了很多问题。

这是我现在的代码:

static DbContextOptions GetOptions(IModel model)
{
    var builder = new DbContextOptionsBuilder();
    builder
        .UseSqlServer(connStr)
        .UseModel(model);
    return builder.Options;
}
class Test1ModelAEntity
{
    public int Id { get; set; }
    public string StrProp { get; set; }
}
static void Main(string[] args)
{
    var sqlServerServices = new ServiceCollection()
        .AddEntityFrameworkSqlServer()
        .BuildServiceProvider();
    var conventions = new ConventionSet();
    sqlServerServices.GetRequiredService<IConventionSetBuilder>().AddConventions(conventions);

    var emptyModelBuilder = new ModelBuilder(conventions);
    var emptyModel = emptyModelBuilder.Model;

    var test1ModelBuilder = new ModelBuilder(conventions);
    test1ModelBuilder.Entity<Test1ModelAEntity>()
        .ToTable("ModelA");
    var test1Model = test1ModelBuilder.Model;

    using (TestContext ctx = new TestContext(GetOptions(emptyModel)))
    {
        var migrationServices = new ServiceCollection()
            .AddDbContextDesignTimeServices(ctx)
            .AddEntityFrameworkSqlServer()
            .BuildServiceProvider();
        var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
        var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
        var connection = migrationServices.GetRequiredService<IRelationalConnection>();
        migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
    }
}

此代码使用此堆栈跟踪抛出NullReferenceException

at Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping.<>c.<GetRootType>b__10_0(IEntityType t)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetSortedProperties(TableMapping target)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<Add>d__37.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<DiffCollection>d__73`1.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
at Sandbox.Program.Main(String[] args) in D:\Sandbox\Program.cs:line 108

我检查了源代码,看来EFCore解释我的模型的方式存在问题。我正在使用EFCore 2.1版预览版2。

我真的主要在IServiceCollection上尝试随机配置,因为我不知道如何设置它。我也试图远离EFCore的内部课程,但如果需要,我可以暂时使用一两个。

有没有办法利用EFCore的内置功能在给定一对IModel s的情况下生成一些SQL?如果是这样,我如何设置DI以获得所有必需的服务?

1 个答案:

答案 0 :(得分:2)

感谢您指出正确方向的意见。

总之,我试图使用空的约定集创建我的模型。这显然会导致各种各样的问题,因为你必须明确地生成整个模型,这非常复杂。

要使用预期的约定集,我必须使用ConventionSet.CreateConventionSet从我的上下文中获取它。我还必须手动验证我的模型,然后才能在查询和插入命令中使用它。其余的逻辑基本相同。

这是我的最终代码,包括我为防止一切按预期工作而进行的测试:

static DbContextOptions GetOptions(IModel model)
{
    var builder = new DbContextOptionsBuilder();
    builder
        .UseSqlServer(connStr)
        .UseModel(model);
    return builder.Options;
}

//Test 1
class Test1EntityA
{
    public int Id { get; set; }
    public string StrProp { get; set; }
}

//Test 2
class Test2EntityA
{
    public int Id { get; set; }
    public string StrProp { get; set; }
    public ICollection<Test2ModelBEntity> Children { get; set; }
}
class Test2EntityB
{
    public int Id { get; set; }
    public int EntityAId { get; set; }
    public Test2EntityA EntityA { get; set; }
}

static void Main(string[] args)
{
    var emptyModelBuilder = new ModelBuilder(new ConventionSet());
    var emptyModel = emptyModelBuilder.Model;
    using (var baseCtx = new TestContext(GetOptions(emptyModel)))
    {
        //Get all services we need from the base context
        var conventions = ConventionSet.CreateConventionSet(baseCtx);
        var migrationServices = new ServiceCollection()
            .AddDbContextDesignTimeServices(baseCtx)
            .AddEntityFrameworkSqlServer()
            .BuildServiceProvider();

        //Test 1
        var test1ModelBuilder = new ModelBuilder(conventions);
        test1ModelBuilder.Entity<Test1EntityA>()
            .ToTable("EntityA");
        var test1Model = test1ModelBuilder.GetInfrastructure().Metadata;
        test1Model.Validate();

        var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
        var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
        var connection = migrationServices.GetRequiredService<IRelationalConnection>();
        migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);

        using (TestContext ctx = new TestContext(GetOptions(test1Model)))
        {
            ctx.Set<Test1EntityA>().Add(new Test1EntityA
            {
                StrProp = "test1",
            });
            ctx.SaveChanges();
        }

        //Test 2
        var test2ModelBuilder = new ModelBuilder(conventions);
        test2ModelBuilder.Entity<Test2EntityA>()
            .ToTable("EntityA");
        test2ModelBuilder.Entity<Test2EntityB>()
            .ToTable("EntityB");
        var test2Model = test2ModelBuilder.GetInfrastructure().Metadata;
        test2Model.Validate();

        operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(test1Model, test2Model);
        commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test2Model);
        migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);

        using (TestContext ctx = new TestContext(GetOptions(test2Model)))
        {
            var e = new Test2EntityA
            {
                StrProp = "test2",
            };
            ctx.Set<Test2EntityA>().Add(e);
            ctx.Set<Test2EntityB>().Add(new Test2EntityB
            {
                EntityA = e,
            });
            ctx.SaveChanges();

            Console.WriteLine(ctx.Set<Test2EntityB>().Where(b => b.EntityA.StrProp == "test2").Count());
        }
    }
}