【问题标题】:how DbContext.AttachRange() works in this scenarioDbContext.AttachRange() 在这种情况下如何工作
【发布时间】:2019-07-22 05:42:33
【问题描述】:

我看到一本书有这样的代码:

public class Order 
{
   public int OrderID { get; set; }
   public ICollection<CartLine> Lines { get; set; }
   ...
}

public class CartLine
{
   public int CartLineID { get; set; }
   public Product Product { get; set; }
   public int Quantity { get; set; }
}

//Product class is just a normal class that has properties such as ProductID, Name etc

在订单库中,有一个 SaveOrder 方法:

public void SaveOrder(Order order)
{
   context.AttachRange(order.Lines.Select(l => l.Product));
   if (order.OrderID == 0)
   {
       context.Orders.Add(order);
   }
   context.SaveChanges();
}

书上说:

在数据库中存储 Order 对象时。当用户的购物车数据从会话存储中反序列化时,JSON 包会创建未知的新对象 Entity Framework Core,然后尝试将所有对象写入数据库。对于 Product 对象,这意味着 Entity Framework Core 尝试写入已经存储的对象,这会导致错误。为了避免这个问题,我通知 Entity Framework Core 对象存在并且不应该存储在数据库中,除非它们被修改

我很困惑,有两个问题:

Q1-为什么写已经存储的对象会报错,从底层数据库的角度来看,只是一条更新SQL语句,将所有列修改为当前值?我知道通过更改会做不必要的工作什么都没有并重写所有内容,但它不应该在数据库级别引发任何错误?

Q2-为什么我们不对CartLine 做同样的事情:

context.AttachRange(order.Lines.Select(l => l.Product));
context.AttachRange(order.Lines);

防止CartLine对象像我们对Product对象那样存储在数据库中?

【问题讨论】:

    标签: c# entity-framework-6 asp.net-core-mvc entity-framework-core


    【解决方案1】:

    好的,这将是一个很长的:

    第一个问题:

    在实体框架(核心或“旧”6)中,有“更改跟踪”的概念。 DbContext 类能够跟踪您对数据所做的所有更改,然后通过 SQL 语句(INSERT、UPDATE、DELETE)将其应用到数据库中。要了解它为什么会在您的情况下引发错误,您首先需要了解 DbContext / 更改跟踪的实际工作原理。举个例子吧:

    public void SaveOrder(Order order)
    {
       context.AttachRange(order.Lines.Select(l => l.Product));
       if (order.OrderID == 0)
       {
           context.Orders.Add(order);
       }
       context.SaveChanges();
    }
    

    在此方法中,您会收到一个 Order 实例,其中包含 Lines 和 Products。假设这个方法是从某个 Web 应用程序调用的,这意味着您没有从 DB 加载 Order 实体。这就是所谓的Disconected Scenario

    在您的DbContext 不知道它们的存在的意义上,它是“断开的”。当您执行context.AttachRange 时,您实际上是在告诉 EF:我在这里控制,并且我 100% 确定这些实体已经存在于数据库中。现在请注意它们!,

    让我们再次使用您的代码:假设它是一个新的Order(因此它将输入您的如果存在)并且您删除了代码的context.AttachRange 部分。一旦代码到达Add 和SaveChanges,这些事情就会在DbContext 内部发生:

    1. DetectChanges 方法将被调用
    2. 它将尝试在其当前图中查找所有实体Order, Lines and Products
    3. 如果没有找到它们,它们将作为要插入的新记录添加到“待定更改”中

    然后你继续打电话给SaveChanges,这本书告诉你它会失败。为什么?想象一下,被选中的Products 是:

    Id: 1, "Macbook Pro"
    Id: 2, "Office Chair"
    

    当DbContext 查看实体但不知道它们时,它会将它们添加到状态为Added 的待处理更改中。当您调用SaveChanges 时,它会根据这些产品在模型中的当前状态发出INSERT 语句。由于 Id 的 1 and 2 已存在于数据库中,因此操作失败,主键违规。

    这就是为什么在这种情况下你必须调用Attach(或AttachRange)。这有效地告诉 EF 实体存在于数据库中,它不应尝试再次插入它们。它们将以Unchanged 的状态添加到上下文中。 Attach 通常用于之前没有从 dbContext 加载实体的情况。

    第二个问题:

    这对我来说很难访问,因为我不知道该级别的上下文/模型,但这是我的猜测:

    您不需要对Cartline 执行此操作,因为对于每个订单,您可能都想插入新的Order line。就像在亚马逊买东西一样。您将产品放入购物车,它会生成一个Order,然后是Order Lines,构成该订单的东西。

    如果您随后要更新现有订单并向其中添加更多商品,那么您将遇到同样的问题。您必须先加载现有的CartLines,然后再将它们保存到数据库中,或者像这里一样调用Attach。

    希望它更清楚一点。我已经回答了一个类似的问题,我提供了更多细节,所以也许阅读这也有更多帮助: How does EF Core Modified Entity State behave?

    【讨论】:

    • 感谢您简洁的回答。现在我明白了Q1。所以对于第二季度,你的猜测是正确的,每个订单都需要将新的订单行插入数据库,但是由于新创建的产品的 id 是 0 ,所以 EF 不能更聪明一点,所以当遇到 id > 0,它知道用户正在尝试修改数据库中确实存在的对象,因此我们可以摆脱 Attach(或 AttachRange)?
    • 是的,有一种方法可以在没有 Attach 的情况下做到这一点。我没有进一步讨论以避免使答案过于复杂,因为现在这是一个新问题:)。通常,如果实体具有自动生成的键(例如 id 是自动递增的),则只有设置 id 才能让 EF 知道它是一个 UPDATE。但是..它会考虑实体上设置的任何其他东西。你可以阅读这篇文章,以更详细地了解这一切是如何工作的:docs.microsoft.com/en-us/ef/core/saving/…
    • 部分问题是对Add 的调用。它将标记然后被添加,因此PK冲突。当您混合插入/更新(新订单,但现有产品)时,您可以改用Update,EF 将根据 PK 值确定需要插入哪些。同样,这仅在 Id 类型是自动生成的值时才有效。
    猜你喜欢
    • 2021-05-25
    • 1970-01-01
    • 2018-06-16
    • 2017-06-21
    • 2018-01-19
    • 1970-01-01
    • 2021-12-06
    • 2020-12-27
    • 2021-08-31
    相关资源
    最近更新 更多