7

我正在将旧数据库迁移到一个新数据库,我们需要主要通过实体框架代码优先访问和“管理”(听起来可能有些矛盾)。

我们正在使用 MS SQL Server 2014。

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

  2. 从技术上讲,这些列没有计算列规范,而是在给定默认值时使用NEWID()andGETDATE()

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

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

INSERTs以上将指示实体框架在和期间忽略为此类属性提交任何提供的值UPDATEs

  1. 但是现在我们需要允许导入遗留记录并维护旧值,包括标记为的PRIMARY KEYIDENTITY

    1. 这意味着我们必须在插入这些记录时设置Id,GuidDTS属性。DatabaseGeneratedOption.None

    2. 对于 的情况Id,我们必须以某种方式SET IDENTITY_INSERT ... ON/OFF在连接会话中执行。

    3. 我们也想通过 Code-First 来完成这个导入过程。

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

    自创建数据库以来,支持上下文的模型已更改。考虑使用 Code First 迁移来更新数据库

  3. 我知道我们可以生成一个空的编码迁移,-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.Identity但问题仍然存在:有没有办法在运行时DatabaseGeneratedOption.Computed和运行时之间切换DatabaseGeneratedOption.None

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

4

1 回答 1

6

上下文的一定数量的配置总是依赖于运行时环境——例如,代理生成和验证。因此,Entity Framework 的运行时配置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
}
于 2014-08-15T09:40:36.083 回答