Sup*_*mum 10 c# language-lawyer entity-framework-core-3.1
在下面的示例代码中,我在执行以下操作时遇到以下异常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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
提供的代码中的错误原因如下。
当您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 {} };
Run Code Online (Sandbox Code Playgroud)
执行代码行后实体var a = db.Set<A>().Single()集合不包含来自数据库的实体,因为不使用延迟加载并且没有显式加载集合。实体仅包含在集合初始化期间创建的新实体。SABDbContext DbSABS
当您调用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;
Run Code Online (Sandbox Code Playgroud)
您可以从堆栈跟踪中看到实体框架尝试将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)
Run Code Online (Sandbox Code Playgroud)
并且错误消息还告诉它无法跟踪B实体,Id = 0因为已经跟踪了另一个B具有相同实体的实体Id。
如何解决这个问题。
要解决此问题,您应该删除B初始化S集合时创建实体的代码:
public ICollection<B> S { get; set; } = new List<B>();
Run Code Online (Sandbox Code Playgroud)
相反,您应该S在A创建的地方填充集合。例如:
db.Add(new A {S = {new B(), new B()}});
Run Code Online (Sandbox Code Playgroud)
如果您不使用延迟加载,则应显式加载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;
Run Code Online (Sandbox Code Playgroud)
为什么不添加而不是附加 B 的实例?
简而言之,它们是附加的,因为它们具有Detached状态,因此不会被添加。
执行一行代码后
var a = db.Set<A>().Single();
Run Code Online (Sandbox Code Playgroud)
创建的实体实例B具有 state Detached。可以使用下一个代码进行验证:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Run Code Online (Sandbox Code Playgroud)
然后当你设置
db.Entry(a).Collection(x => x.S).IsModified = true;
Run Code Online (Sandbox Code Playgroud)
EF 尝试添加B实体以更改跟踪。从EFCore 的源代码中,您可以看到这将我们引向具有下一个参数值的InternalEntityEntry.SetPropertyModified方法:
property- 我们的B实体之一,changeState = true,isModified = true,isConceptualNull = false,acceptChanges = true.带有此类参数的此方法将Detached B实体的状态更改为Modified,然后尝试开始跟踪它们(参见第490 - 506 行)。因为B实体现在有状态,Modified这导致它们被附加(未添加)。
| 归档时间: |
|
| 查看次数: |
682 次 |
| 最近记录: |