什么会导致 DbSet 中的持久化实体分离?
What can cause persisted entities from a DbSet to be detached?
我正在使用 Entity Framework Core 检索已存储在数据库中的实体,但根据我的操作方式,它们有时会以 "Detached" 状态检索,即使我正在根本不使用 AsNoTracking
。
这些是用于对数据库建模的 classes:
class AppDbContext : DbContext
{
public DbSet<Thing> Thing { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("...");
}
}
class Thing
{
public int ThingId { get; set; }
public string Name { get; set; }
}
以下是class用于重现在分离状态下检索实体的场景:
class Wrapper
{
public Thing Thing { get; set; }
public Wrapper(Thing t)
{
Thing = t;
}
}
然后主程序执行以下操作:
foreach (var wrapper in context.Thing.Select(a => new Wrapper(a)))
{
Console.WriteLine(context.Entry(wrapper.Thing).State);
}
foreach (var thing in context.Thing.Select(a => a))
{
Console.WriteLine(context.Entry(thing).State);
}
假设Thing
table中有三行,输出变成如下:
Detached
Detached
Detached
Unchanged
Unchanged
Unchanged
换句话说,如果检索到实体,实体将被分离,然后传递到 Wrapper
构造函数,但如果只是定期检索,则会被跟踪(在 "Unchanged" 状态)。
据我了解,除非使用 AsNoTracking
显式检索,否则应始终以跟踪状态检索已经保存到数据库中的实体,那么是什么导致了这种行为差异?以及如何修复以确保始终跟踪实体?
一些注意事项:
Wrapper
class 在这里显然毫无意义,但它是我的真实程序中导致相同行为的更有意义的构造的最小示例。
- 翻转
foreach
循环的顺序(以便带有包装器的循环最后运行)导致在两个循环中跟踪实体,因此在这种情况下,第一个循环显然对第二个循环。
- 扩展第一个
foreach
循环以迭代 context.Thing.ToArray().Select(a => new Wrapper(a))
(添加了 ToArray
)给出了预期的结果(跟踪的实体),所以这似乎与方法有关迭代 - 但如何?
显然,EF 代码解释 Select(a => new Wrapper(a))
与 Select(a => new { a.Id, a.Name } )
相同。它看不到 Wrapper 存储反向引用。
换句话说,它看到(认为)您正在立即转换实体,所以它决定不跟踪它。
指定了here,但是你要明白new{}
部分也是EF处理的。而你的 new Wrapper(a)
不是。
你可以试试 a => new Wrapper() {Thing = a}
,我不是 100% 确定。
... the first loop clearly has a side effect on the second loop.
可以,只要它们属于同一连接即可。跟踪器不会 'forget' 个实体。您可以阅读有关 here 的内容。
我正在使用 Entity Framework Core 检索已存储在数据库中的实体,但根据我的操作方式,它们有时会以 "Detached" 状态检索,即使我正在根本不使用 AsNoTracking
。
这些是用于对数据库建模的 classes:
class AppDbContext : DbContext
{
public DbSet<Thing> Thing { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("...");
}
}
class Thing
{
public int ThingId { get; set; }
public string Name { get; set; }
}
以下是class用于重现在分离状态下检索实体的场景:
class Wrapper
{
public Thing Thing { get; set; }
public Wrapper(Thing t)
{
Thing = t;
}
}
然后主程序执行以下操作:
foreach (var wrapper in context.Thing.Select(a => new Wrapper(a)))
{
Console.WriteLine(context.Entry(wrapper.Thing).State);
}
foreach (var thing in context.Thing.Select(a => a))
{
Console.WriteLine(context.Entry(thing).State);
}
假设Thing
table中有三行,输出变成如下:
Detached
Detached
Detached
Unchanged
Unchanged
Unchanged
换句话说,如果检索到实体,实体将被分离,然后传递到 Wrapper
构造函数,但如果只是定期检索,则会被跟踪(在 "Unchanged" 状态)。
据我了解,除非使用 AsNoTracking
显式检索,否则应始终以跟踪状态检索已经保存到数据库中的实体,那么是什么导致了这种行为差异?以及如何修复以确保始终跟踪实体?
一些注意事项:
Wrapper
class 在这里显然毫无意义,但它是我的真实程序中导致相同行为的更有意义的构造的最小示例。- 翻转
foreach
循环的顺序(以便带有包装器的循环最后运行)导致在两个循环中跟踪实体,因此在这种情况下,第一个循环显然对第二个循环。 - 扩展第一个
foreach
循环以迭代context.Thing.ToArray().Select(a => new Wrapper(a))
(添加了ToArray
)给出了预期的结果(跟踪的实体),所以这似乎与方法有关迭代 - 但如何?
显然,EF 代码解释 Select(a => new Wrapper(a))
与 Select(a => new { a.Id, a.Name } )
相同。它看不到 Wrapper 存储反向引用。
换句话说,它看到(认为)您正在立即转换实体,所以它决定不跟踪它。
指定了here,但是你要明白new{}
部分也是EF处理的。而你的 new Wrapper(a)
不是。
你可以试试 a => new Wrapper() {Thing = a}
,我不是 100% 确定。
... the first loop clearly has a side effect on the second loop.
可以,只要它们属于同一连接即可。跟踪器不会 'forget' 个实体。您可以阅读有关 here 的内容。