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

EF6 entity with DatabaseGeneratedOption.Identity Guid Id force insert my Id value(具有 DatabaseGeneratedOption.Identity Guid Id 的 EF6 实体强制插入我的 Id 值)

本文介绍了具有 DatabaseGeneratedOption.Identity Guid Id 的 EF6 实体强制插入我的 Id 值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to use EF to export/import the existing database of a DbContext. In this context, there are several entities with Guid Id properties with DatabaseGeneratedOption.Identity defined by the ModelBuilder. When I re-import the entities, I want to use the Id value from the serialized object, but it always generates a new Id value when I save the changes. Is there any way to force EF to use my Id value in this case? I know DatabaseGeneratedOption.None will allow me to do it, but then I will always be responsible for generating the Id. I know there segmentation issues of the index that occur without using sequential Guids, so I do not want to do this.

是我运气不好还是有人发现了窍门?

Am I out of luck or has anyone found a trick?

更新:我们决定简单地将所有 Guid Id 从 DatabaseGeneratedOption.Identity 更改为 DatabaseGenerationOption.None 并自己提供 Id.虽然这会导致索引碎片,但我们预计这不会成为我们的表较小的问题.

Update: we have decided to simply change all Guid Id from DatabaseGeneratedOption.Identity to DatabaseGenerationOption.None and provide the Id ourselves. Although this leads to index fragmentation, we do not expect this to be a problem with the smaller size of our tables.

推荐答案

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

You can achieve what you want by defining two contexts that derive from a base context. One context defines its keys with DatabaseGeneratedOption.Identity, the other one with DatabaseGeneratedOption.None. The first one will be your regular application's context.

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

This is possible by virtue of Guid primary keys not being real identity columns. They're just columns with a default constraint, so they can be inserted without a value, or with a value without having to set identity_insert on.

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

To demonstrate that this works I used a very simple class:

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)
    { }
}

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

I first run some code to create and seed the source database:

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);
}

最后,这是完成实际工作的代码:

Finally this is the code that does the actual job:

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();
}

现在在两个数据库中,您将看到两条具有相同键值的记录.

Now in both databases you'll see two records with identical key values.

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

You might wonder: why can't I use one context class, and construct it either with or without the Identity option? That's because EF builds the EDM model only once for a context type and stores it in the AppDomain. So the option you use first would determine which model EF will use for your context class.

这篇关于具有 DatabaseGeneratedOption.Identity Guid Id 的 EF6 实体强制插入我的 Id 值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:具有 DatabaseGeneratedOption.Identity Guid Id 的 EF6 实体强制插入我的 Id 值

基础教程推荐