我们目前正在使用Entity Framework和SQLite。在我们的场景中,
1.多个工作线程
1.没有组成模式(由于治理原因)
1.我们正在为每个数据库事务创建一个新的DbContext
。
样本代码:
public int Update(IEnumerable<MyChanges> changes)
{
using (var dbContext = new MyDbContext())
{
dbContext.TypeChanges.UpdateRange(changes); // <-- Exception
return dbContext.SaveChanges();
}
}
“已跟踪实体”是共享成员示例,其形式为:ChangeA.Child
与ChangeB.Child
相同。这个子对象发生异常,它已经存在于数据库中,并且没有被修改。其他线程也可以使用这个子示例。
在这个非常小的函数中,我们得到一个异常:
System.InvalidOperationException:无法跟踪实体类型的示例,因为已在跟踪具有该键值的另一个示例。附加现有实体时,请确保仅附加一个具有给定键值的实体示例。
注意:主键是一个字符串值,不是自动生成的
我不明白出了什么问题。
我的猜测是:一个不同的线程当前也在跟踪该示例(这可能在我们的设置中),并且不允许在多个线程中跟踪该实体,即使它们使用MyDbContext
的不同示例。
但是如果这是不允许的,你怎么能让多个线程处理你的数据呢?数据结构始终是深层次的,并且彼此拥有。
问:有没有办法在多个线程(不同的上下文)中跟踪一个实体,让最后一个事务获胜?这对我们来说完全足够了。
谢谢你的每一个小提示。我在这一点上已经卡了很长时间了。我想,我完全误解了EF的一些基本概念。
最好的问候,裁缝
1.使用单例DbContext
锁定/监视器(工作,但速度慢,不是一个复杂的解决方案)
1.检查跟踪的实体(在我的示例中为空)
1.使用添加/附加-相同错误
2条答案
按热度按时间q9yhzks01#
这是在创建实体的DbContext范围之外传递实体的一个问题。此错误通常发生在将实体用作DTO/ViewModel并将其发送回Controller时,尤其是在处理对象图时。(实体包含相关实体)当使用实体时,引用就是一切,而序列化和重新组合等事情将打破这一点。使用
AsNoTracking()
加载数据也可能导致此问题,如果您要重新附加分离的实体图。让我们举一个例子,我们为一个客户加载两个订单,每个订单引用一个状态查找实体。
现在我们发送给客户,它的订单要更新。在幕后,我们会有代码,或者依赖EF来计算并大致执行以下操作:
Attach
Ofder ID 1Attach
状态“打开”Attach
Ofder ID 2Attach
状态“已发货”SaveChanges()
……一切似乎都很好。然后我们找到下一个客户,他们又有两个订单,但都是“打开”的:
Attach
Ofder ID 3Attach
状态“打开”Attach
Ofder ID 4Attach
状态“打开”如果我们看得更深一些,你可以看到发生了什么。当我们从EF读取数据时,在第一种情况下,我们会看到这样的内容:
在第二种情况下:
当EF加载多个订单指向同一相关实体的订单时,它们将指向对该跟踪实体的同一引用。您可以使用
object.ReferenceEquals(order3.Status, order4.Status);
验证这一点现在,当您试图反序列化实体或重新组合实体时(例如在控制器窗体POST操作中),问题就出现了。我们得到的订单3和4将是:
调用
object.ReferenceEquals(order3.Status, order4.Status);
将返回false
。我们附加了Order 3的Status,这样DbContext就跟踪了一个键为“Open”的状态,但是当我们附加Order 4的状态时,“Open”已经被跟踪了。当你想将实体附加到DbContext时,你应该总是先检查跟踪缓存,然后如果示例没有被跟踪则附加,或者如果它们被跟踪则替换对被跟踪实体的引用。所以看一下保存订单的例子,我们需要修改一下:
Attach
Ofder ID 3.Local
)的状态“打开”Attach
状态“打开”Attach
Ofder ID 4.Local
)的状态“打开”Attach
状态“打开”SaveChanges()
在代码中,类似于:
就我个人而言,我不建议将分离的实体发送到它们被读取的DbContext范围之外。虽然它可能看起来比创建专用的DTO/ViewModel更简单,但它会带来类似上述场景的复杂性,导致更大的不必要数据块被传递,导致重新组合不包含足够信息的问题,从而导致无意的编辑或错误,风险暴露太多关于系统内部的细节给第三方,由于错误或恶意行为者而导致意外操作的风险,并且如果使用延迟加载,则存在显著的性能问题的风险。
但是如果使用分离的实体,则需要在附加任何实体之前始终检查跟踪的引用并处理该场景。
ny6fqffe2#
DBContext不是线程安全的。如果你想在多个线程上处理同一个数据库,你必须创建多个DBContext示例。一个DBContext只能用于一个工作单元,例如:如果你点击UI上的一个按钮,会发生的一切(一个事务)。
如果你的函数需要多线程,你应该考虑并行计算,并在同一个线程上一次性将所有内容保存到数据库。如果数据不必保存在同一个事务中,只要在每次需要时启动一个新的DBContext即可。
如果这不是问题所在,你是否检查了 changes 中的每个实体是否都有不同的主键?