我正在构建一个基于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以获得所有必需的服务?
答案 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());
}
}
}