【问题标题】:EF6 entity with DatabaseGeneratedOption.Identity Guid Id force insert my Id value具有 DatabaseGeneratedOption.Identity Guid Id 的 EF6 实体强制插入我的 Id 值
【发布时间】:2017-11-10 16:18:40
【问题描述】:

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

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

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

【问题讨论】:

  • 如果只是导出/导入东西,我不会使用 EF。这是 SQL 服务器吗?只需将表格内容导出为脚本并通过运行这些脚本重新导入?我想不出任何不需要为导入任务运行特殊版本的代码的简单方法——我猜你想避免这种情况?实际上,您想要 SET IDENTITY INSERT ON 的 EF 等效项,但我认为如果不更改实体模型内容,这将无法正常工作,这可能会使 EF 认为数据库需要升级...
  • 不幸的是,我必须使用 EF 来执行此操作,因为它可以与任何数据库软件一起使用(而不仅仅是我们正在使用的 SQL 服务器)。
  • 我希望这可能是一个简单的解决方案。我得到了我的预期:它在 sql 中进行了更新,但没有受影响的行。

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


【解决方案1】:

您可以通过定义从基本上下文派生的两个上下文来实现您想要的。一种上下文使用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 将用于您的上下文类的模型。

【讨论】:

  • 我无法使用它,因为我们需要将 Guid Id 导入到我们从中导出的同一数据库架构中。当我尝试对同一个数据库执行此操作时,它抛出了一个异常,指出数据库上下文已更改。所以我需要对 DatabaseGenerationOption.None 上下文进行迁移,这将使其与 DatabaseGenerationOption.Identity 上下文不兼容。
  • 是的,我懒洋洋地忽略了那个场景,但根据我的修改,你应该没问题。
猜你喜欢
  • 1970-01-01
  • 2021-05-17
  • 2014-09-09
  • 1970-01-01
  • 2020-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多