如何清除实体框架中的跟踪实体


81

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

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

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


5
您只需致电context.Entry(entity).State = EntityState.Detached,它将停止跟踪该特定实体。
本·罗宾逊

2
您为什么不只实例化一个新的上下文?除非您需要非常优化的代码,否则实际上并没有太大的开销。
Adrian Nasui 2014年

实体框架仅对已更改的实体命中数据库服务器,因此您无需担心性能。但是您可以创建一个仅包含您要使用的表的新上下文,以使其更快。
伊斯梅特·阿尔坎

1
@IsThatSo因此,检测更改需要花费时间,我不担心DbPerformance。
hazimdikenli 2014年

您是否实际调试并跟踪了性能瓶颈,还是只是假设了这一点?
伊斯梅特·阿尔坎

Answers:


117

您可以将方法添加到您的方法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;
}

5
确保在“位置”之后调用“ ToList”。否则,将引发System.InvalidOperationException:'集合已被修改;枚举操作可能无法执行。”
mabead

6
在我的单元测试中,条目状态为“未修改”,可能是因为我使用了在测试方法结束时回滚的事务。这意味着我必须在不检查当前状态的情况下将跟踪条目的状态设置为“已分离”,这样我的测试才能一次正确运行。我回滚事务后立即调用上面的代码,但是我知道了,回滚肯定意味着未修改状态。
barbara.post

2
(并且也var entity应该确实var entry是条目而不是实际实体)
oatsoda

2
@DavidSherret认为可能是这样!我发现这个问题是因为在我的一个测试应用程序中,循环了1000个项目并标记为Detached用现有代码花费了大约6000ms。大约15ms的新:)
燕麦

3
您是否也应该使用e.State == EntityState.Unchanged?尽管实体不变,但仍会在上下文中对其进行跟踪,并且它是在DetectChanges中考虑的实体集的一部分。例如,您添加新实体(状态为“已添加”),调用SaveChanges,添加的实体现在状态为Unchanged(这与UnitOfWork模式相反,但是操作询问:我在每次迭代结束时保存更改)。
jahav

25

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中的所有实体上运行并将它们分离是可行的。
hazimdikenli 2014年

15

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

_dbContext = new DbContext();


5
这不是您目标的“简单但可行”的解决方案。这是唯一正确的。上下文应尽可能少地存在,每1个事务1个上下文是最佳选择。
Yegor Androsov '18

2
同意@pwrigshihanomoronimo,上下文遵循UnitOfWork设计模式。根据Martin Fowler的定义:>维护受业务交易影响的对象的列表,并且>协调更改的写出和并发的解决>问题。
米歇尔

这似乎对我有用。我正在同步数据,在具有几百万行的表中大约有一半的事务(插入和更新)。因此,过了一段时间(或许多操作),我在OutOfMemoryException中苦苦挣扎。当我每X个循环创建一个新的DbContext时,这个问题就解决了,这是重新实例化上下文的自然位置。这可能以更好的方式触发了GC,而不必考虑EF和长时间运行的操作中可能发生的内存泄漏。谢谢!
Mats Magnem

4

我只是遇到了这个问题,最终偶然发现了一个针对使用典型.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


0

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

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

就像对代码的注释所说的那样;

这是一个内部API,支持Entity Framework Core基础结构,并且不受与公共API相同的兼容性标准的约束。在任何版本中,可能会更改或删除它,恕不另行通知。您仅应非常谨慎地在代码中直接使用它,并且知道这样做会导致在更新到新的Entity Framework Core版本时导致应用程序失败。


-3

我的观点是,根据我的经验,EF或作为任何Orm在压力太大或模型复杂的情况下无法很好地工作。

如果您不想跟踪,我真的会说为什么甚至要执行orm?

如果速度是主要动力,那么没有什么比存储过程和良好的索引更好。

而且,如果您的查询始终是按id进行的,请考虑使用nosql或仅包含key和json的sql。这样可以避免类和表之间的阻抗问题。

对于您的情况,对我来说,以这种方式加载对象似乎很慢。实际上,在您的情况下,存储过程更好,因为可以避免通过网络传输数据,而sql则速度更快,并且经过优化,可以管理聚合以及类似的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.