c# - 尝试通过属性默认值更改关系时出现意外的 InvalidOperationException
问题描述
在下面的示例代码中,执行以下操作时出现以下异常db.Entry(a).Collection(x => x.S).IsModified = true
:
System.InvalidOperationException:“无法跟踪实体类型“B”的实例,因为已在跟踪另一个具有键值“{Id:0}”的实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。
为什么不添加而不是附加 B 的实例?
奇怪的是,文档IsModified
没有指定InvalidOperationException
为可能的例外。无效的文档或错误?
我知道这段代码很奇怪,但我写它只是为了了解 ef core 在一些奇怪的 egde 情况下是如何工作的。我想要的是解释,而不是解决方法。
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
public class A
{
public int Id { get; set; }
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
}
public class B
{
public int Id { get; set; }
}
public class Db : DbContext {
private const string connectionString = @"Server=(localdb)\mssqllocaldb;Database=Apa;Trusted_Connection=True";
protected override void OnConfiguring(DbContextOptionsBuilder o)
{
o.UseSqlServer(connectionString);
o.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder m)
{
m.Entity<A>();
m.Entity<B>();
}
}
static void Main(string[] args)
{
using (var db = new Db()) {
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Add(new A { });
db.SaveChanges();
}
using (var db = new Db()) {
var a = db.Set<A>().Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
db.SaveChanges();
}
}
}
解决方案
提供的代码中的错误原因如下。
当您A
从数据库中创建实体时,它的属性S
会使用包含两条新记录的集合进行初始化B
。Id
这个新实体中的每一个B
都等于0
。
// This line of code reads entity from the database
// and creates new instance of object A from it.
var a = db.Set<A>().Single();
// When new entity A is created its field S initialized
// by a collection that contains two new instances of entity B.
// Property Id of each of these two B entities is equal to 0.
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
执行代码行后,实体var a = db.Set<A>().Single()
集合不包含来自数据库的实体,因为不使用延迟加载,也没有显式加载集合。Entity仅包含在 collection 初始化期间创建的新实体。S
A
B
DbContext Db
S
A
B
S
当您调用IsModifed = true
集合S
实体框架时,会尝试将这两个新实体添加B
到更改跟踪中。但它失败了,因为两个新B
实体都具有相同的Id = 0
:
// This line tries to add to change tracking two new B entities with the same Id = 0.
// As a result it fails.
db.Entry(a).Collection(x => x.S).IsModified = true;
您可以从堆栈跟踪中看到实体框架尝试将B
实体添加到IdentityMap
:
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(Object relatedEntity, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.set_IsModified(Boolean value)
并且错误消息还告诉它无法跟踪B
实体,Id = 0
因为已经跟踪B
了另一个具有相同的实体。Id
如何解决这个问题。
B
要解决此问题,您应该删除在初始化S
集合时创建实体的代码:
public ICollection<B> S { get; set; } = new List<B>();
S
相反,您应该在创建的地方填写集合A
。例如:
db.Add(new A {S = {new B(), new B()}});
如果您不使用延迟加载,则应显式加载S
集合以将其项目添加到更改跟踪中:
// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
为什么不添加而不是附加 B 的实例?
简而言之Detached
,由于它们具有状态,因此它们被附加了被添加。
执行代码行后
var a = db.Set<A>().Single();
创建的实体实例B
具有状态Detached
。可以使用以下代码进行验证:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
然后当你设置
db.Entry(a).Collection(x => x.S).IsModified = true;
EF 尝试添加B
实体以进行更改跟踪。从EFCore的源代码中,您可以看到这导致我们使用带有下一个参数值的方法InternalEntityEntry.SetPropertyModified :
property
- 我们的B
实体之一,changeState = true
,isModified = true
,isConceptualNull = false
,acceptChanges = true
.
带有此类参数的此方法将实体的状态更改Detached
B
为Modified
,然后尝试开始跟踪它们(参见第490 - 506 行)。因为B
实体现在具有状态Modified
,这导致它们被附加(未添加)。
推荐阅读
- java - 从控制器返回pdf并在春季显示在前端
- python - 我们可以将行和列> 1的数据框复制到Excel工作表中吗?
- ssms - SSMS 18 '<' 附近的语法不正确,需要 ID 或 QUOTED_ID
- python - 替换子字符串但跳过以前的出现
- java-8 - 从地图中自定义对象内的数组中获取双精度数组
- laravel - 如何将此查询转换为 Laravel ORM
- c# - SCIM 端点。从 Azure AD 测试时出错
- python-3.x - 在 keras 中连接两个嵌入层会引发 ValueError
- reactjs - React:如何从子组件更新数组中的对象
- flutter - 在警报框被解除后运行一个函数