DbContext.AttachRange() 在这种情况下如何工作

3 c# entity-framework-6 entity-framework-core asp.net-core-mvc

我看过一本书,里面有这样的代码:

\n\n
public class Order \n{\n   public int OrderID { get; set; }\n   public ICollection<CartLine> Lines { get; set; }\n   ...\n}\n\npublic class CartLine\n{\n   public int CartLineID { get; set; }\n   public Product Product { get; set; }\n   public int Quantity { get; set; }\n}\n\n//Product class is just a normal class that has properties such as ProductID, Name etc\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
public void SaveOrder(Order order)\n{\n   context.AttachRange(order.Lines.Select(l => l.Product));\n   if (order.OrderID == 0)\n   {\n       context.Orders.Add(order);\n   }\n   context.SaveChanges();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

书上说:

\n\n

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

\n\n

我很困惑,有两个问题:

\n\n

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

\n\n

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

\n\n
context.AttachRange(order.Lines.Select(l => l.Product));\ncontext.AttachRange(order.Lines);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

jpg*_*ssi 5

好吧,这会是一篇很长的文章:

第一个问题:

在实体框架(核心或“旧”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();
}
Run Code Online (Sandbox Code Playgroud)

在此方法中,您会收到一个Order包含Lines和 的实例Products。假设此方法是从某个 Web 应用程序调用的,这意味着您没有从数据库加载 Order 实体。这就是所谓的断开连接场景

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

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

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

然后你继续打电话SaveChanges,就会失败,就像书上告诉你的那样。为什么?想象一下所Products选择的是:

Id: 1, "Macbook Pro"
Id: 2, "Office Chair"
Run Code Online (Sandbox Code Playgroud)

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

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

第二个问题:

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

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

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

希望它能更清楚一点。我已经回答了一个类似的问题,其中提供了更多详细信息,因此也许阅读该问题也会有更多帮助: EF Core Modified Entity State 的行为如何?