问题描述
我正在尝试使用 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 值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!