具有DatabaseGeneratedOption.Identity的EF6实体Guid Id强制插入我的Id值

时间:2017-11-10 16:18:41

标签: c# database entity-framework entity-framework-6 code-first

我正在尝试使用EF导出/导入DbContext的现有数据库。在此上下文中,有几个具有Guid Id属性的实体,其中DatabaseGeneratedOption.Identity由ModelBuilder定义。当我重新导入实体时,我想使用序列化对象的Id值,但是在保存更改时它总是生成一个新的Id值。在这种情况下,有没有办法强制EF使用我的Id值?我知道DatabaseGeneratedOption.None将允许我这样做,但随后我将始终负责生成Id。我知道没有使用顺序Guids就会出现索引的分段问题,所以我不想这样做。

我运气不好还是有人找到了伎俩?

更新:我们已决定将所有Guid Id从DatabaseGeneratedOption.Identity更改为DatabaseGenerationOption.None并自行提供ID。虽然这会导致索引碎片化,但我们不希望这对我们表的较小规模造成问题。

1 个答案:

答案 0 :(得分:2)

您可以通过定义从基本上下文派生的两个上下文来实现您想要的效果。一个上下文用DatabaseGeneratedOption.Identity定义其键,另一个用DatabaseGeneratedOption.None定义。第一个将是您的常规应用程序的上下文。

由于Guid主键不是真正的标识列,这是可能的。它们只是具有默认约束的列,因此可以在不使用值的情况下插入,或者使用值插入,而无需设置identity_insert

为了证明这是有效的,我使用了一个非常简单的类:

public class Planet
{
    public Guid ID { get; set; }
    public string Name { get; set; }
}

基础背景:

public abstract class BaseContext : DbContext
{
    private readonly DatabaseGeneratedOption _databaseGeneratedOption;

    protected BaseContext(string conString, DatabaseGeneratedOption databaseGeneratedOption)
        : base(conString)
    {
        this._databaseGeneratedOption = databaseGeneratedOption;
    }

    public DbSet<Planet> Planets { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Planet>().HasKey(p => p.ID);
        modelBuilder.Entity<Planet>().Property(p => p.ID)
                    .HasDatabaseGeneratedOption(this._databaseGeneratedOption);
        base.OnModelCreating(modelBuilder);
    }
}

上下文子类:

public class GenerateKeyContext : BaseContext
{
    public GenerateKeyContext(string conString)
        : base(conString, DatabaseGeneratedOption.Identity)
    { }
}

public class InsertKeyContext : BaseContext
{
    public InsertKeyContext(string conString)
        : base(conString, DatabaseGeneratedOption.None)
    { }
}

我首先运行一些代码来创建和播种源数据库:

var db1 = @"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidGen";
var db2 = @"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidInsert";

// Set initializers:
// 1. just for testing.
Database.SetInitializer(new DropCreateDatabaseAlways<GenerateKeyContext>());
// 2. prevent model check.
Database.SetInitializer<InsertKeyContext>(null);

using (var context = new GenerateKeyContext(db1))
{
    var earth = new Planet { Name = "Earth", };
    var mars = new Planet { Name = "Mars", };
    context.Planets.Add(earth);
    context.Planets.Add(mars);

    context.SaveChanges();
}

目标数据库:

using (var context = new GenerateKeyContext(db2))
{
    context.Database.Initialize(true);
}

最后,这是执行实际工作的代码:

var planets = new List<UserQuery.Planet>();
using (var context = new GenerateKeyContext(db1))
{
    planets = context.Planets.AsNoTracking().ToList();
}
using (var context = new InsertKeyContext(db2))
{
    context.Planets.AddRange(planets);
    context.SaveChanges();
}

现在,在两个数据库中,您都会看到两个具有相同键值的记录。

您可能想知道:为什么我不能使用一个上下文类,并使用或不使用Identity选项构建它?这是因为EF仅为上下文类型构建一次EDM模型并将其存储在AppDomain中。因此,您首先使用的选项将确定EF将用于您的上下文类的模型。