【问题标题】:How to handle a detached entity graph that has duplicate entities with the same PK in Entity Framework 6如何在实体框架 6 中处理具有相同 PK 的重复实体的分离实体图
【发布时间】:2016-03-09 08:39:18
【问题描述】:

例如,我正在创建发票图表..

图形是用 AnuglarJS 在客户端构建的,然后作为 JSON 发送到后端。在后端,我收到了一个看似完美的对象,由 Web Api 2 中的一些模型绑定魔法创建。

接下来我从 DbContext 加载一个管理实体并将分离的发票添加到它的集合导航属性:

Administration.Lines.Add(invoice);

当触发 DetectChanges 时,图中的每个实体都会获得一个状态为 EntityState.Added 的条目。

从图中我只想添加实体 Invoice 和 InvoiceLines,因为该图的其余部分没有改变。为了完成这项工作,我采用了在本地跟踪状态的方法,例如使用实现 State 属性的 BaseModel 接口(如编程实体框架:DbContext,第 4 章中所建议)。

这一切都非常好......直到例如,当我添加 2 行相同的项目时。出于说明目的,我相信模型绑定器从 JSON 创建对象有点像:

var invoice = new Invoice
{
    State = State.Added,
    Number = "SALE-001",
    Lines =
    {
        new InvoiceLine
        {
            State = State.Added,
            Price = 10,
            Quantity = 1,
            Item = new Item { Id = 1, Description = "Product" }
        },
        new InvoiceLine
        {
            State = State.Added,
            Price = 10,
            Quantity = 1,
            DiscountPercentage = 50,
            Item = new Item { Id = 1, Description = "Product" }
        },
    }
};

如您所见,这些物品共享相同的 PK,但实际上是不同的对象。因此,当触发 DetectChanges 时,我有 2 个具有相同 PK 的实体 Item 条目,当我尝试使用 InvalidOperationException 将已添加状态转换为未更改时,这一切都会爆炸:

保存或接受更改失败,因为多个“AutomapperWithEF.Data.Domain.Item”类型的实体具有相同的主键值。确保显式设置的主键值是唯一的。确保在数据库和实体框架模型中正确配置了数据库生成的主键。使用实体设计器进行数据库优先/模型优先配置。使用“HasDatabaseGeneratedOption”流式 API 或“DatabaseGeneratedAttribute”进行 Code First 配置。

EF 不应该以某种方式阻止这种情况的发生吗?通过相同类型和PK的相等比较?

编辑:

ChangeTracker.Entries 是触发器的 DbContext:

public class AppContext : DbContext
{
    public DbSet<Administration> Administrations { get; set; }
    public DbSet<Invoice> Invoices { get; set; }
    public DbSet<InvoiceLine> InvoiceLines { get; set; }
    public DbSet<Item> Items { get; set; }

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<BaseModel>())
        {
            BaseModel baseModel = entry.Entity;
            entry.State = ConvertState(baseModel.State);
        }

        return base.SaveChanges();
    }

    public static EntityState ConvertState(State state)
    {
        switch (state)
        {
            case State.Added:
                return EntityState.Added;
            case State.Modified:
                return EntityState.Modified;
            case State.Deleted:
                return EntityState.Detached;
            default:
                return EntityState.Unchanged;
        }
    }
}

一个例子来说明我的意思:https://github.com/svieillard/SampleEF

【问题讨论】:

  • 您能否展示将 Invoice 添加到上下文的代码、触发 DetectChanges 的代码以及将状态从已添加到未更改的代码以及尝试保存更改的代码?
  • 我在 GitHub 上添加了 DbContext 和一个指向 example project 的链接来说明问题。它没有客户端JSON发送到后端,但对象创建是必不可少的部分

标签: c# angularjs json entity-framework asp.net-web-api2


【解决方案1】:

您正在将一些 JSON 反序列化为 Invoice,该 Invoice 具有两个具有相同 ID 的 Item 对象。然后您将Add()Invoice 调用到上下文中,然后调用SaveChanges()

当您执行此操作时,EF 会看到您有两次该项目并引发异常。

即使您只有 1 个项目,EF 也会尝试根据 JSON 中的数据更新项目。你可能不想要那个。

所以我们要从上下文中删除项目。

解决方案 #1:从内存中的对象中删除项目并仅使用 ItemId 代替

foreach (var line in invoice.Lines)
{
    if (line != null && line.Item != null)
    {
        line.ItemId = line.Item.Id;
        db.Entry(line.Item).State = System.Data.Entity.EntityState.Detached;
    }
}

解决方案 #2 - 将您的 JSON 订单项更改为使用 ItemId 而不是 Item,像这样

{'state': 2, 'price': 10, 'quantity': 1, 'itemId': 1}

【讨论】:

  • 感谢您的支持!虽然您的两个建议都有效,但我使用 #1 来分离图中的相关实体
【解决方案2】:

您遇到此问题的原因是您正在创建两个不同的 Item 对象,为它们提供相同的键,并尝试将它们都添加到上下文中。

Item = new Item { Id = 1, Description = "Product" }

/// and later

Item = new Item { Id = 1, Description = "Product" }

调用.Add() 时会发生异常,因为在 EF 上下文中不能有两个具有相同键的相同类型的不同对象。

当我查看您的 github 存储库中的代码时,您似乎已经解决了这个问题。我所要做的就是注释掉您当前如何创建Items 并取消注释您的其他代码,结果如下:

var item = new Item { Id = 1, Description = "Product" };

// Invoice is added
var invoice = new Invoice
{
    State = State.Added,
    Number = "SALE-001",
    Lines =
    {
        new InvoiceLine
        {
            State = State.Added,
            Price = 10,
            Quantity = 1,
            // Item = new Item { Id = 1, Description = "Product" }
            Item = item
        },
        new InvoiceLine
        {
            State = State.Added,
            Price = 10,
            Quantity = 1,
            DiscountPercentage = 50,
            // Item = new Item { Id = 1, Description = "Product" }
            Item = item
        },
    }
};

这样,只有 1 个 Item 最终被添加到上下文中,所以 EF 很高兴。

当我像这样运行代码时,它可以正常工作而不会引发任何异常。

【讨论】:

  • 嗯,是的,这就是我所追求的。我的经验是,我可以很高兴.Add() 向管理部门开具发票。只有当我将第二个Item 的条目EntityStateAdded 更改为Unchanged 时,才会引发异常。 EF 允许在 ChangeTracker.Entries() 中具有相同 PK 的两个对象对我来说似乎很奇怪
  • 你能展示导致这个异常发生的代码吗?我没有看到异常。我也有点困惑,因为在上面的代码示例中,只有 1 个Item,所以当你提到“第二个Item”时,我对你的意思感到困惑。
  • 很抱歉,就像你说的,这解决了问题,但假设我无法控制对象的创建,那我该如何解决呢?
  • 你能举例说明你的意思吗?
  • 抱歉,耽搁了,我已经更新了我的 git repo:1. 接收 json,2. 反序列化到对象,3. 添加到管理,4. 保存更改 Item 使用相同的键,所以当我想将条目状态更新为未更改时,我得到了异常
猜你喜欢
  • 2010-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-23
相关资源
最近更新 更多