【问题标题】:How do I clear tracked entities in entity framework如何清除实体框架中的跟踪实体
【发布时间】:2014-12-11 12:35:29
【问题描述】:

我正在运行一些在一大堆实体上运行的更正代码,随着它的进展速度降低,这是因为上下文中跟踪的实体数量随着每次迭代而增加,这可能需要很长时间,所以我正在保存更改在每次迭代结束时。每次迭代都是独立的,不会更改先前加载的实体。

我知道我可以关闭更改跟踪,但我不想这样做,因为它不是批量插入代码,而是加载实体并计算一些东西,如果数字不正确,请设置新数字并更新/删除/创建一些额外的实体。我知道我可以为每次迭代创建一个新的 DbContext 并且可能会比在同一实例中执行所有操作更快,但我认为可能有更好的方法。

所以问题是;有没有办法清除之前在 db 上下文中加载的实体?

【问题讨论】:

  • 您只需致电context.Entry(entity).State = EntityState.Detached,它将停止跟踪该特定实体。
  • 为什么不直接实例化一个新的上下文呢?除非您需要非常优化的代码,否则实际上并没有太大的开销。
  • 实体框架仅针对更改的实体访问数据库服务器,您对此没有性能问题。但您可以创建一个仅包含您使用的表的新上下文,以使其更快。
  • @IsThatSo 检测变化需要时间,我不担心 DbPerformance。
  • 您是否实际调试并跟踪过性能瓶颈,或者只是假设这一点?

标签: c# entity-framework


【解决方案1】:

EntityFramework Core 5.0 引入了一种清除任何跟踪更改的新方法。

_context.ChangeTracker.Clear();

https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.changetracker.clear?view=efcore-5.0

【讨论】:

  • 值得指出,这也是更好的答案性能。根据文档:“与分离每个被跟踪的实体相比,此方法应始终首选。分离实体是一个缓慢的过程,可能会产生副作用。这种方法在从上下文中清除所有被跟踪的实体方面效率更高。”
  • 嗨,它运作良好,谢谢。我解决了我的问题,我明白为什么谢谢。我还有一个问题,如果无法将记录添加到数据库,它应该写入日志。但事实并非如此。这是第一个问题,因为他试图添加所有跟踪的实体,我删除了问题记录,然后我只将日志记录添加到实体,然后保存得很好。但它仍然不会引发错误。我无法调试,因为它会转到 threadpool.cs 或类似的文件,如果我想调试更多它想下载一些文件,实际上我确实下载了许多关于线程的文件但什么也没有,我什么也没看到
  • 这应该是 2022 年公认的答案。
【解决方案2】:

您可以向DbContext 添加一个方法或使用 ChangeTracker 分离所有已添加、已修改和已删除实体的扩展方法:

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}

【讨论】:

  • 确保在“Where”之后调用“ToList”。否则,它会抛出 System.InvalidOperationException: 'Collection was modified;枚举操作可能无法执行。'
  • 在我的单元测试中,条目状态为“未修改”,可能是因为我使用了在测试方法结束时回滚的事务。这意味着我必须在不检查当前状态的情况下将跟踪条目状态设置为“已分离”,以便我的测试一次正确运行。我在回滚事务后立即调用了上面的代码,但是我明白了,回滚肯定意味着未修改状态。
  • (还有var entity 应该是var entry,因为它是条目而不是实际实体)
  • @DavidSherret 认为可能是这样!我发现了这一点,因为在我的一个测试应用程序中,循环浏览 1000 个项目并将现有代码标记为 Detached 大约需要 6000 毫秒。新的大约 15 毫秒 :)
  • 你不应该也使用 e.State == EntityState.Unchanged 吗?尽管实体未更改,但仍会在上下文中对其进行跟踪,并且是在 DetectChanges 期间考虑的实体集的一部分。例如。您添加新实体(它处于已添加状态),调用 SaveChanges 并且添加的实体现在状态为 Unchanged(它违反 UnitOfWork 模式,但操作询问:我在每次迭代结束时保存更改)。
【解决方案3】:

1.可能性:分离条目

dbContext.Entry(entity).State = EntityState.Detached;

当您分离条目时,更改跟踪器将停止跟踪它(并且应该会带来更好的性能)

见:http://msdn.microsoft.com/de-de/library/system.data.entitystate(v=vs.110).aspx

2。可能性:使用您自己的 Status 字段 + 断开连接的上下文

也许您想独立控制实体的状态,以便可以使用断开连接的图表。为实体状态添加一个属性,并在执行操作时将此状态转换为dbContext.Entry(entity).State(使用存储库来执行此操作)

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

示例见以下链接:https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html

【讨论】:

  • 我认为添加扩展方法并运行 ChangeTracker 中的所有实体并分离它们应该可以工作。
【解决方案4】:

我正在运行一个每分钟更新一次值的 Windows 服务,我也遇到了同样的问题。我尝试运行@DavidSherrets 解决方案,但几个小时后它也变慢了。我的解决方案是为每次新运行简单地创建一个这样的新上下文。简单但有效。

_dbContext = new DbContext();

【讨论】:

  • 这不是实现您目标的“简单但有效”的解决方案。这是唯一正确的。上下文应该尽可能少地存在,每 1 个事务 1 个上下文是最好的选择。
  • 同意@pwrigshihanomoronimo,上下文遵循UnitOfWork设计模式。正如 Martin Fowler 所定义的那样: > 维护受业务事务影响的对象列表并 > 协调更改的写入和并发问题的解决 > 问题。
  • 这似乎对我有用。我正在同步数据,在几百万行的表中大约有一半的事务(插入和更新)。所以我在一段时间(或多次操作)后与 OutOfMemoryException 作斗争。当我为每 X 个循环创建一个新的 DbContext 时,这个问题得到了解决,这是一个重新实例化上下文的自然位置。这可能会以更好的方式触发 GC,而不必考虑 EF 和长时间运行的操作中可能存在的内存泄漏。谢谢!
  • 不确定这是否适用于依赖注入
  • @MatsMagnem,你一定要试试github.com/borisdj/EFCore.BulkExtensions。我希望 github.com/dotnet/runtime/issues/28633 在 EF Core 5 时间范围内发布。
【解决方案5】:

【讨论】:

    【解决方案6】:

    我刚刚遇到了这个问题,最终偶然发现了一个更好的解决方案,适用于那些使用典型 .NET Core 依赖注入的人。您可以为每个操作使用作用域 DbContext。这将重置DbContext.ChangeTracker,这样SaveChangesAsync() 就不会因为过去的迭代检查实体而陷入困境。这是一个示例 ASP.NET Core Controller 方法:

        /// <summary>
        /// An endpoint that processes a batch of records.
        /// </summary>
        /// <param name="provider">The service provider to create scoped DbContexts.
        /// This is injected by DI per the FromServices attribute.</param>
        /// <param name="records">The batch of records.</param>
        public async Task<IActionResult> PostRecords(
            [FromServices] IServiceProvider provider,
            Record[] records)
        {
            // The service scope factory is used to create a scope per iteration
            var serviceScopeFactory =
                provider.GetRequiredService<IServiceScopeFactory>();
    
            foreach (var record in records)
            {
                // At the end of the using block, scope.Dispose() will be called,
                // release the DbContext so it can be disposed/reset
                using (var scope = serviceScopeFactory.CreateScope())
                {
                    var context = scope.ServiceProvider.GetService<MainDbContext>();
    
                    // Query and modify database records as needed
    
                    await context.SaveChangesAsync();
                }
            }
    
            return Ok();
        }
    

    鉴于 ASP.NET Core 项目通常使用 DbContextPool,这甚至不会创建/销毁 DbContext 对象。 (如果您有兴趣,DbContextPool 实际上会调用 DbContext.ResetState()DbContext.Resurrect(),但我不建议直接从您的代码中调用它们,因为它们可能会在未来的版本中发生变化。) https://github.com/aspnet/EntityFrameworkCore/blob/v2.2.1/src/EFCore/Internal/DbContextPool.cs#L157

    【讨论】:

      【解决方案7】:

      从 EF Core 3.0 开始,有一个内部 API 可以重置 ChangeTracker。不要在生产代码中使用它,我提到它是因为它可能有助于根据场景进行测试。

      ((IResettableService)ChangeTracker).ResetState();
      

      正如the code 上的评论所说;

      这是一个支持 Entity Framework Core 的内部 API 基础设施,并且不受与相同的兼容性标准的约束 公共 API。它可能会被更改或删除,恕不另行通知 发布。您应该只在极端情况下直接在代码中使用它 小心并知道这样做会导致应用程序失败 更新到新的 Entity Framework Core 版本时。

      【讨论】:

      • 虽然是一个很好的发现,但它似乎并没有真正清除状态,似乎只是创建一个新的然后重置那个但保持原来的状态。但实施可能在内部发生了变化。看看如何访问 StateManager 并清除那里的条目会很好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-23
      • 1970-01-01
      • 2017-06-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多