如何在运行时在DatabaseGeneratedOption.Identity,Computed和None之间切换,而不必生成空的DbMigrations

时间:2014-08-14 21:21:41

标签: sql-server entity-framework ef-code-first ef-migrations identity-insert

我正在将遗留数据库迁移到新数据库,我们需要主要通过实体框架代码优先来访问和“管理”(因为它可能听起来具有反应性)。

我们正在使用MS SQL Server 2014。

  1. 旧数据库包含一些带有计算列的表。 典型的GUID和DateTime内容。

  2. 从技术上讲,这些列没有计算列规范,而是给出了NEWID()GETDATE()的默认值

  3. 我们都知道配置DbContext来处理这些属性非常容易,如下所示:

    modelBuilder.Entity<Foo>()
                .Property(t => t.Guid)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    modelBuilder.Entity<Bar>()
                .Property(t => t.DTS)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    

    以上内容将指示实体框架忽略在INSERTsUPDATEs期间提交此类属性的任何提供值。

    1. 但现在我们需要允许导入旧版记录并维护OLD值,包括 PRIMARY KEY ,标记为{{1} }

      1. 这意味着我们必须在插入这些记录时将IDENTITYIdGuid属性设置为DTS

      2. 对于DatabaseGeneratedOption.None的情况,我们必须以某种方式在连接会话中执行Id

      3. 我们想要这样做 也可以通过Code-First导入流程。

    2. 如果我修改模型并“临时”并在创建数据库后将这些属性设置为SET IDENTITY_INSERT ... ON/OFF,我们将得到典型的:

      自创建数据库以来,支持上下文的模型已更改。请考虑使用“代码优先迁移”来更新数据库

    3. 据我所知,我们可以使用DatabaseGeneratedOption.None生成一个空的编码迁移,以便“建立”最新版本的上下文,但这不是一个可接受的策略,因为我们必须仅为此目的来回运行空迁移。


    4. 半个回答:

      我们考虑过将这些属性赋予可空类型,即

      -IgnoreChanges

      关注初始public class Foo { ... public Guid? Guid { get; set; } } public class Bar { ... public DateTime? DTS { get; set; } } 中的默认值:

      DbMigration

      问题:

      但问题仍然存在:是否有办法在运行时在CreateTable( "dbo.Foos", c => new { Id = c.Int(nullable: false, identity: true), Guid = c.Guid(nullable: false, defaultValueSql: "NEWID()"), }) .PrimaryKey(t => t.Id); CreateTable( "dbo.Bars", c => new { Id = c.Int(nullable: false, identity: true), DTS = c.Guid(nullable: false, defaultValueSql: "GETDATE()"), }) .PrimaryKey(t => t.Id); DatabaseGeneratedOption.IdentityDatabaseGeneratedOption.Computed之间切换?

      至少,我们如何在运行时打开/关闭DatabaseGeneratedOption.None

1 个答案:

答案 0 :(得分:5)

上下文的一定量配置始终取决于运行时环境 - 例如,代理生成和验证。因此,实体框架DbContext的运行时配置是我非常重视的。

虽然我从未使用过这种方法来根据每个用例来切换上下文的配置,但我认为没有理由不这样做。

最简单的形式是,可以通过为每个环境提供一组EntityTypeConfiguration类来实现。然后,每个配置集在每个环境的基础上连接到DbContext。同样,在最简单的形式中,这可以通过每个环境具有DbContext类型来实现。在您的情况下,这将是每个用例。

不太天真,我通常将环境的配置封装在特定于环境的工作单元中。例如,Asp.Net环境的工作单元具有基础DbContext,其被配置为将验证委托给Web框架,以及关闭代理生成以防止序列化问题。我想这种方法对你的问题有相似的用处。

例如(使用暴力代码):

// Foo Configuration which enforces computed columns
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
    public FooConfiguration()
    {           
        Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
        Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    }
}

// Foo configuration that allows computed columns to be overridden
public class FooConfiguration2 : EntityTypeConfiguration<Foo>
{
    public FooConfiguration2()
    {           
        Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
        Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
    }
}

// DbContext that enforces computed columns
public class MyContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new FooConfiguration());     
    }
}

// DbContext that allows computed columns to be overridden
public class MyContext2 : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new FooConfiguration2());     
    }
}

这显然可以整理 - 我们通常使用工厂和策略模式的组合来封装运行时特定上下文的创建。与DI容器结合使用,可以在每个环境的基础上注入正确的设置配置类。

使用示例:

[Fact]
public void CanConfigureContextAtRuntime()
{
    // Enforce computed columns
    using (var context = new EfContext())
    {
        var foo1 = new Foo();
        context.Foos.Add(foo1);                             
        context.SaveChanges();
    }

    // Allow overridden computed columns
    using (var context = new EfContext2())
    {              
        var foo2 = new Foo { DateTime = DateTime.Now.AddYears(-3) };
        context.Foos.Add(foo2);
        context.SaveChanges();
    }

    // etc
}