How to refresh an Entity Framework Core DBContext?
Asked Answered
S

9

61

When my table is updated by another party, the db context in dotnet core still return the old value, how can I force the Db context to refresh?

I've done research but I only found people use Reload method, which is not available in EF core, to force the context to refresh.

Some other solution suggests dispose the context after using, but I get error saying the DB context is created by dependency injection and I should not mess up with it.

Stereogram answered 13/9, 2017 at 19:6 Comment(4)
They are sharing the same context? After Saving Changes different context should share the same state.Salify
@Salify Unfortunately they do not share the same context.Stereogram
if you only need to read data then "AsNoTracking" could help you out. EF Core: learn.microsoft.com/en-us/ef/core/querying/tracking and this for more information: EF Cache Busting: codethug.com/2016/02/19/Entity-Framework-Cache-BustingArdell
The question doesn't show why you need this refresh in the first place. It nearly always indicates a deeper design flaw, usually in the area of context life cycle management. Nearly always the answer is: don't.Yonina
L
41

Dependency Injection and DbContext

You mention that when you try to recreate your DbContext, you get an error about the context being managed by your dependency injection (DI) system. There are two different styles of using a dependency injection system for object creation. The DI can either create a global singleton instance that is shared as a service between all consumers or it can create an instance per scope/unit of work (e.g., per request in a web server).

If your DI system is configured to create a single global shared instance of DbContext, then you will encounter various problems associated with long-lived DbContext.

  • DbContext, by design, never automatically removes objects from its cache because it is not designed to be long-lived. Thus, a long-lived DbContext will retain memory wastefully.
  • Your code will never see changes to items loaded into its cache without manually reloading each entity it loads.
  • DbContext only allows one query to run at any time and is not threadsafe. If you try to run multiple queries on a globally shared instance, it will throw DbConcurrencyException (at least on its async interface, not sure about its sync interface).

Thus, the best practice is to use a single DbContext per unit of work. Your DI system can help you with this by being configured to provide a fresh instance for each request your application processes within a scope. For example, ASP.NET Core’s Dependency Injection system supports scoping instances by request.

Refreshing a Single Entity

The easiest way to get fresh data is to create a new DbContext. However, within your unit of work, or within the constraints of the granularity of scoping provided by your DI system, you may trigger an external process which is supposed to modify your entity directly in the database. You may need to see that change before exiting your DI’s scope or completing your unit of work. In that case, you can force a reload by detaching your instance of the data object.

To do this, first get the EntityEntry<> for your object. This is an object which lets you manipulate DbContext’s internal cache for that object. You can then mark this entry detached by assigning EntitytState.Detached to its State property. I believe that this leaves the entry in the cache but causes the DbContext to remove and replace it when you actually load the entry in the future. What matters is that it causes a future load to return a freshly loaded entity instance to your code. For example:

var thing = context.Things.Find(id);
if (thing.ShouldBeSentToService) {
    TriggerExternalServiceAndWait(id);

    // Detach the object to remove it from context’s cache.
    context.Entities(thing).State = EntityState.Detached;

    // Then load it. We will get a new object with data
    // freshly loaded from the database.
    thing = context.Things.Find(id);
}
UseSomeOtherData(thing.DataWhichWasUpdated);
Luht answered 11/7, 2018 at 17:5 Comment(7)
Nice answer, I had used a short live context to solve the problem in that project. Thanks and hopefully this answer will help other people.Stereogram
Also worth noting that when you register a DbContext it also registers DbContextOptions<T> where T is your context type. So you can actually inject that instead (eg. DbContextOptions<RRStoreContext> rrContextOptions) and just create a brand new context as needed (using (var db = new RRContext(rrContextOptions)). You'll have to make a judgement whether this is best for compared to other answers, but sometimes it's the easiest way since they don't expose the reset functionality publically (it does exist and is used by the DbContext pooling feature).Pestle
Just for everyone getting here later on, the EntitityFrameworkCore change tracker allows reloading of a single entity: Entities(thing).Reload();Daughtry
So this half way helped me in EF 5.0.11. i did have to .Detached, then (item.SubItemInAnotherTable).ReloadAsync(), then ask for the item again with .FirstOrDefault()Chart
Correction, i had to remove ReloadAsync, it was causing a db error like this: 'System.InvalidOperationException: The instance of entity type 'DomainUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'Chart
@Chart So you’re confirming that my answer works as written? ;-)Luht
About detaching an entity to refresh it, it's worth noting that all related entities via navigations would need to be refreshed too manuallyMaritzamariupol
J
70

Oh, this issue had me in knots for days.

I'm using Visual Studio 2017 with .Net Core 2.1, and my EF Core code looked something like this:

//  1.  Load a [User] record from our database 
int chosenUserID = 12345;
User usr = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);

//  2. Call a web service, which updates that [User] record
HttpClient client = new HttpClient()
await client.PostAsync("http://someUrl", someContent);

//  3. Attempt to load an updated copy of the [User] record
User updatedUser = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);

At step 3, it would simply set "updatedUser" to the original version of the [User] record, rather than attempting to load in a fresh copy. So, if, after step 3, I modified that [User] record, I'd actually lose whatever settings the web service had applied to it.

I - eventually - found two solutions.

I could change the ChangeTracker settings. This worked, but I was concerned about the side-effects of doing this:

dbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;

Or, I could slip in the following command, before attempting to reload the [User] record...

await dbContext.Entry(usr).ReloadAsync();

This seems to force .Net Core to reload that [User] record, and life is good again.

I hope this is useful...

Tracking down, and fixing this bug took me days....

There's also an excellent article describing the various ways to get around this caching issue here.

Joyance answered 1/11, 2018 at 10:13 Comment(4)
I understand how frastrated it is. However here’s my suggestion and my current understanding, in the design of EF, the DbContext object is a implementation of unit of work pattern. It should be short lived, and not to be used by different translation. You can try to use another new DbContext to do the second operation. :)Stereogram
Almost a year late but with this solution using Reload, I encounter another error which is "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations." due to subsequent calls. Probably the ChangeTracker one is better in this case but I am not sure about the side effect, too.Hubble
Quoting from learn.microsoft.com/en-us/ef/core/querying/… : When the results are returned in a tracking query, EF Core will check if the entity is already in the context. If EF Core finds an existing entity, then the same instance is returned. EF Core won't overwrite current and original values of the entity's properties in the entry with the database values. So this shows why EF Core does not update an entity when querying it again from the database, which is weird imhoMaritzamariupol
@Hubble the side effects of disabling querytracking at runtime is that the following queries won't be tracked, but all the previously tracked entities should remain in the context. It's the same as calling AsNoTracking() at the end of the query. In every case I still recommend to use NoTrackingWithIdentityResolution instead because the above would return different instances for the same entity in a single query while this option would consolidate them firstMaritzamariupol
L
41

Dependency Injection and DbContext

You mention that when you try to recreate your DbContext, you get an error about the context being managed by your dependency injection (DI) system. There are two different styles of using a dependency injection system for object creation. The DI can either create a global singleton instance that is shared as a service between all consumers or it can create an instance per scope/unit of work (e.g., per request in a web server).

If your DI system is configured to create a single global shared instance of DbContext, then you will encounter various problems associated with long-lived DbContext.

  • DbContext, by design, never automatically removes objects from its cache because it is not designed to be long-lived. Thus, a long-lived DbContext will retain memory wastefully.
  • Your code will never see changes to items loaded into its cache without manually reloading each entity it loads.
  • DbContext only allows one query to run at any time and is not threadsafe. If you try to run multiple queries on a globally shared instance, it will throw DbConcurrencyException (at least on its async interface, not sure about its sync interface).

Thus, the best practice is to use a single DbContext per unit of work. Your DI system can help you with this by being configured to provide a fresh instance for each request your application processes within a scope. For example, ASP.NET Core’s Dependency Injection system supports scoping instances by request.

Refreshing a Single Entity

The easiest way to get fresh data is to create a new DbContext. However, within your unit of work, or within the constraints of the granularity of scoping provided by your DI system, you may trigger an external process which is supposed to modify your entity directly in the database. You may need to see that change before exiting your DI’s scope or completing your unit of work. In that case, you can force a reload by detaching your instance of the data object.

To do this, first get the EntityEntry<> for your object. This is an object which lets you manipulate DbContext’s internal cache for that object. You can then mark this entry detached by assigning EntitytState.Detached to its State property. I believe that this leaves the entry in the cache but causes the DbContext to remove and replace it when you actually load the entry in the future. What matters is that it causes a future load to return a freshly loaded entity instance to your code. For example:

var thing = context.Things.Find(id);
if (thing.ShouldBeSentToService) {
    TriggerExternalServiceAndWait(id);

    // Detach the object to remove it from context’s cache.
    context.Entities(thing).State = EntityState.Detached;

    // Then load it. We will get a new object with data
    // freshly loaded from the database.
    thing = context.Things.Find(id);
}
UseSomeOtherData(thing.DataWhichWasUpdated);
Luht answered 11/7, 2018 at 17:5 Comment(7)
Nice answer, I had used a short live context to solve the problem in that project. Thanks and hopefully this answer will help other people.Stereogram
Also worth noting that when you register a DbContext it also registers DbContextOptions<T> where T is your context type. So you can actually inject that instead (eg. DbContextOptions<RRStoreContext> rrContextOptions) and just create a brand new context as needed (using (var db = new RRContext(rrContextOptions)). You'll have to make a judgement whether this is best for compared to other answers, but sometimes it's the easiest way since they don't expose the reset functionality publically (it does exist and is used by the DbContext pooling feature).Pestle
Just for everyone getting here later on, the EntitityFrameworkCore change tracker allows reloading of a single entity: Entities(thing).Reload();Daughtry
So this half way helped me in EF 5.0.11. i did have to .Detached, then (item.SubItemInAnotherTable).ReloadAsync(), then ask for the item again with .FirstOrDefault()Chart
Correction, i had to remove ReloadAsync, it was causing a db error like this: 'System.InvalidOperationException: The instance of entity type 'DomainUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'Chart
@Chart So you’re confirming that my answer works as written? ;-)Luht
About detaching an entity to refresh it, it's worth noting that all related entities via navigations would need to be refreshed too manuallyMaritzamariupol
T
24

Reload and ReloadAsync has been available since Entity Framework Core 1.1

Examples:

//test.Name is test1
var test = dbContext.Tests.FirstOrDefault();
test.Name = "test2";

//test.Name is now test1 again
dbContext.Entry(test).Reload();

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.entityentry.reload?view=efcore-1.1

Takakotakakura answered 14/1, 2021 at 15:34 Comment(3)
Two other answers already mention these methods.Yonina
@GertArnold Very true, I decided to post an answer given that OP stated I only found people use Reload method, which is not available in EF core. and that I thought a short and complete example was missing.Takakotakakura
Great answer, don't suppose anyone knows if there is an option for doing a collection this way? Might be best in that scenario to Detach all those entities and rerun whatever query you have. Seems like EF could implement a Collection form of this but I haven't seen it.Graminivorous
S
10

With the release of .NET 5.0 and Entity Framework Core 5.0, the recommended pattern would be to use a DBContext factory. In Statup.cs I changed:

services.AddDbContext<MyDbContext>...

to

services.AddDbContextFactory<MyDbContext>...

All my repository classes use the same base class, here I create the context in the constructor:

protected BaseRepository(IDbContextFactory<MyDbContext> contextFactory)
{
    _context = contextFactory.CreateDbContext();
}

I use a very simple repository factory to ensure I get a fresh instance of the repository and dbcontext each time I need it:

using System;
using Data.Models.Entities;
using Microsoft.Extensions.DependencyInjection;

namespace Data.Repository
{
    public class RepositoryFactory : IRepositoryFactory
    {
        private readonly IServiceProvider _serviceProvider;

        public RepositoryFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        
        public IApplicationRepository BuildApplicationRepository()
        {
            var service = _serviceProvider.GetService<IApplicationRepository>();

            return service;
        }
    }
}

Using the patterns described solves the "DB context is created by dependency injection" error and negates the need for a Reload()/Refresh() method.

Siddons answered 5/6, 2021 at 20:39 Comment(0)
B
5

This simple sequence refreshes the whole context under EFCore 5.0:

public void Refresh()
{
    (Context as DbContext).Database.CloseConnection();
    (Context as DbContext).Database.OpenConnection();
}
Brigitte answered 23/3, 2021 at 14:28 Comment(2)
Any explanation on this? What is refreshed? Only the connection? Or "the whole context"? Including the change tracker?Camail
this will not work during testing with sqlite in memory dbProgeny
C
4

You would have to detach the entity from the context, or implement you own extension for .Reload()

Here's the .Reload() implementation. Source: https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core

public static TEntity Reload<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
    return context.Entry(entity).Reload();
}

public static TEntity Reload<TEntity>(this EntityEntry<TEntity> entry) where TEntity : class
{
    if (entry.State == EntityState.Detached)
    {
        return entry.Entity;
    }

    var context = entry.Context;
    var entity = entry.Entity;
    var keyValues = context.GetEntityKey(entity);

    entry.State = EntityState.Detached;

    var newEntity = context.Set<TEntity>().Find(keyValues);
    var newEntry = context.Entry(newEntity);

    foreach (var prop in newEntry.Metadata.GetProperties())
    {
        prop.GetSetter().SetClrValue(entity, 
        prop.GetGetter().GetClrValue(newEntity));
    }

    newEntry.State = EntityState.Detached;
    entry.State = EntityState.Unchanged;

    return entry.Entity;
}

Where GetEntityKey():

public static object[] GetEntityKey<T>(this DbContext context, T entity) where T : class
{
    var state = context.Entry(entity);
    var metadata = state.Metadata;
    var key = metadata.FindPrimaryKey();
    var props = key.Properties.ToArray();

    return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();
}
Chewy answered 13/9, 2017 at 19:14 Comment(3)
Hi, which Using do I need to use "GetEntityKey"? DbContext does not contain a definition for "GetEntityKey".Kahaleel
@CedricArnould, GetEntityKey is an extension method to extract the metadata of the primary key Id. I'll edit and add it to my answer.Chewy
Plus 1 For detach and re-find. Simple as that.Invitation
K
3

Here is my solution, I hope it will help you.

  • The detach function will detach all nested entities and array of entities.
  • The findByIdAsync will have an optional parameter to detach the entity and will reload it.

Repository

    public void Detach(TEntity entity)
    {
        foreach (var entry in _ctx.Entry(entity).Navigations)
        {
            if (entry.CurrentValue is IEnumerable<IEntity> children)
            {
                foreach (var child in children)
                {
                    _ctx.Entry(child).State = EntityState.Detached;
                }
            }
            else if (entry.CurrentValue is IEntity child)
            {
                _ctx.Entry(child).State = EntityState.Detached;
            }
        }
        _ctx.Entry(entity).State = EntityState.Detached;
    }

So for exemple with:

Classes:

public interface IEntity : IEntity<Guid>
{
}

public interface IEntity<TPrimaryKey>
{
    [JsonProperty("id")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    TPrimaryKey Id { get; set; }
}

public class Sample : IEntity
{
    public Guid Id { get; set; }
    public string Text { get; private set; }   
    public Guid? CreatedByUserId { get; set; }
    public virtual User CreatedByUser { get; set; }     
    public List<SampleItem> SampleItems { get; set; } = new List<SampleItem>();
}

public class SampleItem : IEntity
{
    public Guid Id { get; set; }
    public string Text { get; private set; }   
    public Guid? CreatedByUserId { get; set; }
    public virtual User CreatedByUser { get; set; }     
}

Manager

    public async Task<Sample> FindByIdAsync(Guid id, bool includeDeleted = false, bool forceRefresh = false)
    {
        var result = await GetAll()
            .Include(s => s.SampleItems)
            .IgnoreQueryFilters(includeDeleted)
            .FirstOrDefaultAsync(s => s.Id == id);

        if (forceRefresh)
        {
            _sampleRepository.Detach(result);
            return await FindByIdAsync(id, includeDeleted);
        }

        return result;
    }

Controller

    SampleManager.FindByIdAsync(id, forceRefresh: true);
Kahaleel answered 31/7, 2018 at 4:22 Comment(2)
If you want to completely reload your entity (with its child entities), then this is a great solution!Achondrite
where's IEntity?Lynnalynne
L
0

I had the problem that when I called the same method twice or more, the _context, once it had loaded orders and items (in case it entered the else), I could no longer make an orderItems filter.

order.orderItems collections contains all items

I tried Filtering on Include in EF Core but it didn't solve it so I found this solution:

First Program.cs refactoring:

builder.Services.AddDbContext<ApplicationDbContext>(options =>
           options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), b => b.EnableRetryOnFailure()));

By:

builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
           options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), b => b.EnableRetryOnFailure()));

Then:

public class OrderRepository : IOrderRepository, IOrderPayments
{
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
private readonly ApplicationDbContext _context;

public async Task<OrderDetailsDto> GetDetails(int orderId, List<string>? sellerIds)
{
    Order order = new();
    if (vendorIds != null) 
        using (var context = _contextFactory.CreateDbContext())
        {
            order = context.Orders
              .Where(x => x.OrderId == orderId)
              .Include(o => o.OrderItems.Where(x => vendorIds.Contains(x.SellerId)))
              .First();
        }
    else
        order = await _context.Orders
            .Include(x => x.Payments)
            .Include(x => x.OrderItems)
                .FirstAsync(x => x.OrderId == orderId);
}
...
//create orderDetailsDto object

return OrderDetailsDto
}

Reference from Microsoft DbContext Lifetime, Configuration, and Initialization

Lowkey answered 8/12, 2023 at 3:35 Comment(1)
That's exacty what this answer already said.Yonina
M
-4

Ideally the other party would notify you when an update is made. This could be achieved via a message queue.

Among others: Azure Service Bus, Rabbit MQ, 0MQ

Misbeliever answered 13/9, 2017 at 19:20 Comment(1)
The OP is asking about how to force an EntityFramework DbContext to reload data into a loaded entity. If you do var thing1 = myContext.Things.Find(1); /* something updates the database in the meantime */ var thing2 = myContext.Things.Find(1);, then thing2 == thing1 (same object instance) and the changes in the database will not be loaded into it because DbContext is using its cache instead of reloading the data.Luht

© 2022 - 2024 — McMap. All rights reserved.