DbContext.AttachRange() 在这种情况下如何工作
how DbContext.AttachRange() works in this scenario
我看到一本书有这样的代码:
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();
}
书上说:
在数据库中存储订单对象时。当用户的购物车数据从会话存储中反序列化时,JSON 包会创建新的未知对象
Entity Framework 核心,然后尝试将所有对象写入数据库。对于 Product 对象,这意味着 Entity Framework Core 尝试写入已存储的对象,这会导致错误。为了避免这个问题,我通知 Entity Framework Core 对象存在并且不应存储在数据库中,除非它们被修改
我很困惑,有两个问题:
Q1-为什么写入已经存储的对象会出错,从底层数据库的角度来看,它只是一个update SQL语句将所有列修改为当前值?我知道它通过不改变任何东西并重写所有东西来做不必要的工作,但它不应该在数据库级别抛出任何错误?
Q2-为什么我们不对 CartLine
做同样的事情:
context.AttachRange(order.Lines.Select(l => l.Product));
context.AttachRange(order.Lines);
防止CartLine
对象存储在数据库中就像我们对Product
对象所做的那样?
好的,所以这会很长:
第一个问题:
在Entity Framework(核心或"old"6)中,有"Change tracking"这个概念。 DbContext
class 能够跟踪您对数据所做的所有更改,然后通过 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();
}
在此方法中,您会收到一个包含 Lines
和 Products
的 Order
实例。假设此方法是从某个 Web 应用程序调用的,这意味着您 没有从数据库加载订单实体。 这就是所谓的 Disconected Scenario
它是 "disconnected",因为您的 DbContext
不知道它们的存在。当您执行 context.AttachRange
时,您实际上是在告诉 EF: 这里由我控制,而且我 100% 确定这些实体已经存在于数据库中。请暂时注意它们!,
让我们再次使用您的代码:假设它是一个新的 Order
(因此它将进入您的 if 那里)并且您删除了代码的 context.AttachRange
部分。一旦代码到达 Add
和 SaveChanges
,这些事情就会在 DbContext
:
内部发生
- 将调用
DetectChanges
方法
- 它将尝试在其当前图表
中找到所有实体Order, Lines and Products
- 如果找不到它们,它们将作为 要插入的新记录添加到 "pending changes"
然后你继续调用SaveChanges
,它会像书上告诉你的那样失败。为什么?假设选择的 Products
是:
Id: 1, "Macbook Pro"
Id: 2, "Office Chair"
当 DbContext
查看实体但不知道它们时,它会将它们添加到状态为 Added
的待定更改中。当您调用 SaveChanges
时,它会根据这些产品在模型中的当前状态为这些产品发出 INSERT
语句。由于 ID 的 1 and 2
已存在于数据库中,因此操作失败,主键冲突。
这就是为什么在这种情况下您必须调用 Attach
(或 AttachRange
)。这有效地告诉 EF 实体存在于数据库中,并且不应尝试再次插入它们。它们将以 Unchanged
的状态添加到上下文中。 Attach
通常用于您之前没有从 dbContext
加载实体的情况。
第二题:
这对我来说很难访问,因为我不知道那个级别的 context/model,但这是我的猜测:
您不需要对 Cartline
执行此操作,因为对于每个订单,您可能都想插入新的 Order line
。想想在亚马逊买东西。您将产品放入购物车,它会生成一个 Order
,然后是 Order Lines
,构成该订单的东西。
如果您随后要更新现有订单并向其中添加更多商品,那么您会 运行 进入同一期。在将现有 CartLines
保存到数据库之前,您必须先加载它们,或者像您在此处所做的那样调用 Attach
。
希望它更清楚一点。我已经回答了一个类似的问题,我在其中提供了更多细节,所以也许阅读它也有更多帮助:
我看到一本书有这样的代码:
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();
}
书上说:
在数据库中存储订单对象时。当用户的购物车数据从会话存储中反序列化时,JSON 包会创建新的未知对象 Entity Framework 核心,然后尝试将所有对象写入数据库。对于 Product 对象,这意味着 Entity Framework Core 尝试写入已存储的对象,这会导致错误。为了避免这个问题,我通知 Entity Framework Core 对象存在并且不应存储在数据库中,除非它们被修改
我很困惑,有两个问题:
Q1-为什么写入已经存储的对象会出错,从底层数据库的角度来看,它只是一个update SQL语句将所有列修改为当前值?我知道它通过不改变任何东西并重写所有东西来做不必要的工作,但它不应该在数据库级别抛出任何错误?
Q2-为什么我们不对 CartLine
做同样的事情:
context.AttachRange(order.Lines.Select(l => l.Product));
context.AttachRange(order.Lines);
防止CartLine
对象存储在数据库中就像我们对Product
对象所做的那样?
好的,所以这会很长:
第一个问题:
在Entity Framework(核心或"old"6)中,有"Change tracking"这个概念。 DbContext
class 能够跟踪您对数据所做的所有更改,然后通过 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();
}
在此方法中,您会收到一个包含 Lines
和 Products
的 Order
实例。假设此方法是从某个 Web 应用程序调用的,这意味着您 没有从数据库加载订单实体。 这就是所谓的 Disconected Scenario
它是 "disconnected",因为您的 DbContext
不知道它们的存在。当您执行 context.AttachRange
时,您实际上是在告诉 EF: 这里由我控制,而且我 100% 确定这些实体已经存在于数据库中。请暂时注意它们!,
让我们再次使用您的代码:假设它是一个新的 Order
(因此它将进入您的 if 那里)并且您删除了代码的 context.AttachRange
部分。一旦代码到达 Add
和 SaveChanges
,这些事情就会在 DbContext
:
- 将调用
DetectChanges
方法 - 它将尝试在其当前图表 中找到所有实体
- 如果找不到它们,它们将作为 要插入的新记录添加到 "pending changes"
Order, Lines and Products
然后你继续调用SaveChanges
,它会像书上告诉你的那样失败。为什么?假设选择的 Products
是:
Id: 1, "Macbook Pro"
Id: 2, "Office Chair"
当 DbContext
查看实体但不知道它们时,它会将它们添加到状态为 Added
的待定更改中。当您调用 SaveChanges
时,它会根据这些产品在模型中的当前状态为这些产品发出 INSERT
语句。由于 ID 的 1 and 2
已存在于数据库中,因此操作失败,主键冲突。
这就是为什么在这种情况下您必须调用 Attach
(或 AttachRange
)。这有效地告诉 EF 实体存在于数据库中,并且不应尝试再次插入它们。它们将以 Unchanged
的状态添加到上下文中。 Attach
通常用于您之前没有从 dbContext
加载实体的情况。
第二题:
这对我来说很难访问,因为我不知道那个级别的 context/model,但这是我的猜测:
您不需要对 Cartline
执行此操作,因为对于每个订单,您可能都想插入新的 Order line
。想想在亚马逊买东西。您将产品放入购物车,它会生成一个 Order
,然后是 Order Lines
,构成该订单的东西。
如果您随后要更新现有订单并向其中添加更多商品,那么您会 运行 进入同一期。在将现有 CartLines
保存到数据库之前,您必须先加载它们,或者像您在此处所做的那样调用 Attach
。
希望它更清楚一点。我已经回答了一个类似的问题,我在其中提供了更多细节,所以也许阅读它也有更多帮助: