什么会导致DbSet中的持久实体分离?

时间:2019-02-14 13:42:58

标签: c# entity-framework-core ef-core-2.2

我正在使用Entity Framework Core来检索已经存储在数据库中的实体,但是根据我的操作方式,即使我不使用AsNoTracking,有时也会以“已分离”状态进行检索。完全没有。

这些是用于对数据库建模的类:

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 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表中有三行,则输出将变为以下内容:

Detached
Detached
Detached
Unchanged
Unchanged
Unchanged

换句话说,如果检索到实体,则将其分离,然后将其传递到Wrapper构造函数中,但是如果只是定期对其进行检索,则会将其跟踪(处于“未更改”状态)。

据我了解,除非已使用AsNoTracking进行了明确检索,否则应该始终以跟踪状态检索已经持久存储在数据库中的实体,那么,什么会导致这种行为差异?以及如何固定以确保始终跟踪实体?

一些注意事项:

  • Wrapper类在这里显然毫无意义,但这只是我的真实程序中导致相同行为的更有意义构造的一个最小示例。
  • 翻转foreach循环的顺序(以便最后运行带有包装器的循环)会导致在两个循环中都跟踪实体,因此在这种情况下,第一个循环显然会对第二个循环产生副作用循环。
  • 扩展第一个foreach循环以遍历context.Thing.ToArray().Select(a => new Wrapper(a))(添加了ToArray)可以得到预期的结果(跟踪的实体),因此,这似乎与迭代方法有关-但是如何?

1 个答案:

答案 0 :(得分:2)

很显然,EF代码将Select(a => new Wrapper(a))解释为与Select(a => new { a.Id, a.Name } )相同。它看不到包装器存储了反向引用。

换句话说,它看到(认为)您正在立即转换该实体,因此它决定不对其进行跟踪。

已指定here,但是您必须了解new{}部分也由EF处理。而您的new Wrapper(a)不是。

您可以尝试a => new Wrapper() {Thing = a},对此我不确定100%。

  

...第一个循环显然对第二个循环有副作用。

是的,只要它们是同一连接的一部分。追踪器不会“忘记”实体。您可以阅读有关here的信息。

相关问题