2

I have a DbSet which will have some of the entities loaded, not all. I want to retrieve the latest entity (including reloading it if required). However, I want to avoid hitting the database twice.

For a single entity - failed approach:

// Load entity
// If the entity is already in memory, this will not hit the database
// If the entity is not already loaded into memory, this will hit the database
var myCar = Context.Cars.FirstOrDefault(c => c.CarId == carId);

// Reload, in case the entity has changed in the database
// This will hit the database a second time, if the entity was NOT already loaded into memory
await Context.Entry(myCar).ReloadAsync().ConfigureAwait(false);

Convoluted approach:

// To retrieve from in-memory, use .Local:
var myCar = Context.Cars.Local.FirstOrDefault(c => c.CarId == carId);

if (myCar != null)
{
    // Entity already loaded into memory
    // It requires a reload
    await Context.Entry(myCar).ReloadAsync().ConfigureAwait(false);
}
else
{
    // Entity is not loaded into memory
    // Load entity from database (don't use .Local)
    myCar = Context.Cars.FirstOrDefault(c => c.CarId == carId);
}

This will only hit the db once, but is messy. I am looking for a single call that does this: "get the entity, ensuring it's the latest".

Edit for clarification:

The question is: How do I neatly and simply retrieve the contents of the database, in a clean and succinct manner, and ensure that the database is only hit once (ie: no redundant .Reload() to ensure I have the latest).

I know:

(a) If I have loaded the entity, there are definitely not any local changes. There may be changes from another user or process in the database. The question is "How do I hit the database once and only once" in order to get the changes from the other user. Calling FirstOrDefault is NOT loading the changes, hence I am calling Reload as well. (b) I may not have the entity in memory. I simply need to load the entity using FirstOrDefault. In which case, the Reload will hit the database again.

From the first answer:

The correct solution should be to ONLY call FirstOrDefault(), it should load the changes in the case of (a), or load the entity in case of (b).

My problem, however, now remains as follows:

I don't know why FirstOrDefault still isn't loading changes. Since I know that there are definitely no local changes (State == Unchanged), I would expect FirstOrDefault to load changes from the database, but it's not doing so. This is why I'm calling .Reload(), but instead I need to find out why FirstOrDefault is not loading the changes from the database, which will negate the need to call .Reload(). This will solve the issue.

Chris
  • 31
  • 7
  • Maybe just `context.ChageeTracker.Clear()`? Then you will get only fresh records. – Svyatoslav Danyliv Nov 13 '22 at 08:37
  • Does this answer your question? [what is the most reasonable way to find out if entity is attached to dbContext or not?](https://stackoverflow.com/questions/6033390/what-is-the-most-reasonable-way-to-find-out-if-entity-is-attached-to-dbcontext-o) – Progman Nov 13 '22 at 09:55
  • I think context.ChageeTracker.Clear() is a problem for me, as I don't necessarily know if other changes have been made, that I don't want to clear. I specifically only want to reload this particular entity, so I'd be worried about that. – Chris Nov 20 '22 at 03:26
  • The entity is definitely attached to the context. What I don't know, is whether another user has altered the item in the database. I want to ensure I hit the database once, and only once. – Chris Nov 20 '22 at 03:27

1 Answers1

2
// If the entity is already in memory, this will not hit the database
var myCar = Context.Cars.FirstOrDefault(c => c.CarId == carId);

This is not correct. The statement will hit the database, but EF doesn't overwrite changes. It only looks like the database wasn't hit.

That's because EF doesn't want to overwrite local changes unasked. The same Car instance could, after having been changed, be contained in the result of subsequent LINQ statements that would load it from the database (and sometimes not clearly visible, e.g. by lazy loading). The changes should still be saved.

That said, what I think you're after is a method that only reloads an entity object (hitting the database) if it's modified and does nothing if it's not. For example as a method in a DbContext class:

async Task ReloadIfModified<TEntity>(TEntity entityObject) where TEntity : class
{
    var entry = Entry(entityObject);
    if (entry.State != EntityState.Modified) return;
    await entry.ReloadAsync();
}

This assumes that deleted objects shouldn't be reloaded. If you want Detached (or even Added) objects to be reloaded you can change that in the method.

Note that this is not the same as Context.Cars.Find(1). The Find method doesn't hit the database if the entity object is already tracked, which is good, but it doesn't reload a modified object (which is not what you want here).

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • "This is not correct. The statement will hit the database, but EF doesn't overwrite changes. It only looks like the database wasn't hit." This answers the question. My understanding was incorrect, and explains why I couldn't find the answers I was looking for: I was asking the wrong questions. I've overcomplicated the issue, and confused Context.Cars.FirstOrDefault, with the call to .Local. .Local loads from memory, but without it always hits the DB. Thanks for pointing me in the right direction. – Chris Nov 20 '22 at 03:34
  • I've marked this as correct, however, I'm still hitting a problem where the call to Context.Cars.FirstOrDefault(c => c.CarId == carId) does NOT load the changes from the database. The entity is unchanged: Context.Entry(myCar).State == Unchanged. If I call Context.Entry(myCar).Reload(), the data loads correctly. I will need to discover why FirstOrDefault is not reflecting the changes from the database. Thanks again for your input. – Chris Nov 20 '22 at 04:04
  • I added some more explanation. – Gert Arnold Nov 20 '22 at 09:48