hazimdikenli
hazimdikenli

Reputation: 6029

How do I clear tracked entities in entity framework

I am running some correction code that runs over a big pile of entities, as it progress its speed decreases, that is because the number of tracked entities in the context increase with each iteration, it can take long so I am saving changes at the end of each iteration. Each iteration is independent and does not change the previously loaded entities.

I know I can turn off change tracking but I do not want to, because it is not a bulk insert code, but loading the entities and calculating a few things and if the numbers are not correct set the new numbers and update/delete/create some additional entities. I know I can create a new DbContext for each iteration and probably that would run faster than doing all in the same instance, but I am thinking that there might be a better way.

So my question: is there a way of clearing the entities previously loaded in the DbContext?

Upvotes: 179

Views: 166757

Answers (7)

ADM-IT
ADM-IT

Reputation: 4200

With Microsoft.EntityFrameworkCore v.8.0 and above you can clear tracked entities for the specific table. Very often I tend to use something like bellow:

When I do need to update the table in the following way

await _db.Set<Contacts>()
    .Where(x => x.FirstName == "John")
    .ExecuteUpdateAsync(x => x
        .SetProperty(p => p.Gender, "Male")
    );

The changes made via ExecuteUpdate or by pure SqlCommand do not reflect the tracked entities. So in this case it is recommended to clear those entities stored (cached) in memory just in case because they could contain outdated data.

According to microsoft docs: ExecuteUpdate and ExecuteDelete are a way to save data to the database without using EF's traditional change tracking and SaveChanges() method. Crucially, when ExecuteUpdate is invoked and all Blogs are updated in the database, EF's change tracker is not updated, and the tracked .NET instance still has its original rating value, from the point at which it was queried.

The way you can do that is by accessing the local tracked store. There are few options available depending on your need (Local, ReloadAsync, ChangeTracker.Clear).

// detach all the contacts from the cache
foreach (var entity in _db.Set<Contacts>().Local)
{
    var entry = _db.Entry(entity);
    if (entry.State != EntityState.Added)
        entry.State = EntityState.Detached;
}

// or alternatively reload those entities
foreach (var entity in _db.Set<Contacts>().Local)
{
    await _db.Entry(entity).ReloadAsync();
}

// or of course you can clear all the existing tracked entities
_db.ChangeTracker.Clear();

Upvotes: 0

David Sherret
David Sherret

Reputation: 106840

Nowadays, you can just call context.ChangeTracker.Clear(); if you're using EF Core 5.0+


Otherwise, you can add a method to your DbContext or an extension method that uses the ChangeTracker to detach all the Added, Modified, Deleted, and Changed entities:

public void DetachAllEntities()
{
    var undetachedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State != EntityState.Detached)
        .ToList();

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

Upvotes: 204

Stuart Hallows
Stuart Hallows

Reputation: 8953

From EF Core 3.0 there is an internal API that can reset the ChangeTracker. Do not use this in production code, I mention it as it may help someone in testing depending on the scenario.

((IResettableService)ChangeTracker).ResetState();

As the comment on the code says;

This is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release. You should only use it directly in your code with extreme caution and knowing that doing so can result in application failures when updating to a new Entity Framework Core release.

Upvotes: 2

j-petty
j-petty

Reputation: 3026

EntityFramework Core 5.0 introduced a new method to clear any tracked changes.

_context.ChangeTracker.Clear();

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

Upvotes: 160

Matt
Matt

Reputation: 729

I just ran into this issue, and eventually stumbled upon a better solution for those using the typical .NET Core dependency injection. You can use a scoped DbContext for each operation. That will reset DbContext.ChangeTracker so that SaveChangesAsync() won't get bogged down checking entities from past iterations. Here is an example ASP.NET Core Controller method:

    /// <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();
    }

Given that ASP.NET Core projects typically use DbContextPool, this doesn't even create/destroy the DbContext objects. (In case you were interested, DbContextPool actually calls DbContext.ResetState() and DbContext.Resurrect(), but I wouldn't recommend calling those directly from your code, as they will probably change in future releases.) https://github.com/aspnet/EntityFrameworkCore/blob/v2.2.1/src/EFCore/Internal/DbContextPool.cs#L157

Upvotes: 10

Ogglas
Ogglas

Reputation: 70184

I'm running a windows service that updates values every minute and I have had the same problem. I tried running @DavidSherrets solution but after a few hours this got slow as well. My solution was to simply create a new context like this for every new run. Simple but it works.

_dbContext = new DbContext();

Upvotes: 22

road242
road242

Reputation: 2532

1. Possibility: detach the entry

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

When you detach the entry the change tracker will stop tracking it (and should result in better performance)

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

2. Possibility: work with your own Status field + disconnected contexts

Maybe you want to control the status of your entity independently so you can use disconnected graphs. Add a property for the entity status and transform this status into the dbContext.Entry(entity).State when performing operations (use a repository to do this)

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

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

See following link for an example: https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html

Upvotes: 38

Related Questions