mehrandvd
mehrandvd

Reputation: 9116

EntityFramework Core does not return fresh data

Using EntityFramework Core I have this code:

var theStudent = new Student();
theStudent.Title = "Mehran"
theStudent.Status = 1
mainDbContext.Set<Student>().Add(theStudent);
await mainDbContext.SaveChangesAsync();

// In reality data is changed by another program. To simulate it here I alter the data by another dbcontext and raw SQL
using (var utilDbContext = new MelkRadarDbContext())
{
    var command = " update dbo.Student set status=2 where Id=@p0";
    utilDbContext.Database.ExecuteSqlCommand(command, theStudent.Id);
}

var reloadedStudent = await mainDbContext.Set<Student>()
    .Where(s => s.Id == theStudent.Id)
    .FirstOrDefaultAsync();

 Assert.AreNotEqual(reloadedStudent, student);
 Assert.AreEqual(reloadedStudent.Status, 2);

Both of assertions are failed. It seems on the second call, mainDbContext still returns the old theStudent object as reloadedStudent, and doesn't load it from the database to get the fresh data. Why is that so? What should I do to get the fresh data on the database?

Upvotes: 6

Views: 1874

Answers (2)

Moha Dehghan
Moha Dehghan

Reputation: 18443

Short answer:

You have two options:

  1. [Not Recommended] Use the Reload or ReloadAsync method for every entity:

    await mainDbContext.Entry(theStudent).ReloadAsync();
    

    Since this must be called for every entity separately, it is very inefficient when you need to reload a bunch of entities.

  2. [Recommended] Create a new DbContext. This is the ultimate way to solve the problem of stale data.

Detials:

DbContexts are designed to be short lived. They implement the Unit Of Work pattern, so it is recommended to create a DbContext for every batch of related operation (a business transaction) - a user action for example (pressing the Save button). Although the common practice of having a single DbContext for each HTTP request (in context of a web application or web service) satisfies this, but sometimes you need to perform more than one "batch of operation" in a request. That's the time you need to consider creating more DbContexts.

The whole points of keeping a single DbContext for a series of operations, are caching, tracking, and lazy loading. Whenever you need to reload the data, it is obvious that you don't need those features from that place onward. So it makes sense to use a new DbContext.

A good question to answer is, why do you need fresh data in the first place? If you need to make critical decisions based on the data, and relying on stale data causes inconsistency in your data store, then even refreshing the entities does not help. In this situation, you need to use stronger mechanisms, like locks (database or otherwise) to protect against stale data.

Note: In Entity Framework 6 there is a Refresh method that can be used to refresh all the objects at once. This method is not available in Entity Framework Core, as it din't prove to be all that useful.

Upvotes: 3

Munzer
Munzer

Reputation: 2318

The DbContext provides a first-level cache for objects that it is asked to retrieve from the data store. Subsequent requests for the same object will return the cached object instead of executing another database request.

the object is cached in the main context, and isn't updated yet, because you used different context when you updated the object, you can use AsNoTracking to ignore caching before loading, but for your case, the object is initialized then attached to the context (cached), what you can do is detach EntityState the object so the context isn't tracking the object anymore, then you can load it again, I hope this will work for you.

   mainDbContext.Entry(theStudent).State = EntityState.Detached;
    var reloadedStudent = await mainDbContext.Set<Student>()
        .Where(s => s.Id == theStudent.Id)
        .FirstOrDefaultAsync();

Upvotes: 0

Related Questions