none
Why is this method for refreshing EF cache not working? RRS feed

  • Question

  • Hello,

    Our good friend Zhanglong has recently helped me figure out a solution to a problem I've been struggling with.

    The link to our conversation is here:

    https://social.msdn.microsoft.com/Forums/en-US/9350893c-875f-4db9-94fd-e7ab5790dbee/updating-ef-cachecontext-when-database-changes?forum=adodotnetentityframework&prof=required

    The problem:

    We need a way of refreshing the EF cache after running a stored procedure that deletes a whole bunch of entities from the database. We also need a way of refreshing the EF cache without removing a whole bunch of new entities (state=Added) that are added from parsing a file.

    In detail:

    round 1:

    1) User uploads a file.
    2) Application parses the file and creates new Entities out of the data being parsed.
    3) Entities are added to the EF cache
    4) Once file is done being parsed, context.SaveChangesAsync() is called committing the new Entities to the database.

    round 2:

    1) User re-uploads a file.
    2) Application parses the file and creates new Entities out of the data being parsed.
    3) Entities are added to the EF cache
    4) The stored procedure is run to delete all the old data from the database (from the first file upload in round 1).
    5) Zhanglong's magic solution is used to update the EF cache to reflect the deleted Entities; but it also preserves the new Entities parsed from the file during the current round; this is so that they will be persisted to the database.
    6) Once file is done being parsed, context.SaveChangesAsync() is called committing the new Entities to the database.

    Here is Zhanglong's magic solution:

    var objContext = ((IObjectContextAdapter)Context).ObjectContext;

    var objects = (from entry in objContext.ObjectStateManager.GetObjectStateEntries(
                    EntityState.Deleted | EntityState.Modified | EntityState.Unchanged)
                    where entry.EntityKey != null
                    select entry.Entity);
    objContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.StoreWins, objects);

    I have no idea how this works, but it seemed to worked on a simple test a ran, but then more complex tests failed.

    To simplify, there are at least two kinds of Entities that are parsed from the file: nodes and deviations. These form a one-to-many relation, with one node having many deviations.

    The test I ran that succeeded was on nodes. Before Zhanglong's solution is run, I check the entity cache and I found both old nodes (unchanged) and new nodes (Add) in there. Then after Zhanglong's solution is run, I find that the old nodes are detached and the new nodes are Added. Letting it run to completion then checking the database shows that only the new nodes are there. This is exactly what I want.

    But then I ran another test on deviations. The test failed. The new deviations are not persisted to the database.

    The strange thing is, if I check for the presence of deviations under a new node during debugging (right after Zhanglong's solution is run), I find that it's there and its state is Added:

    ZhanglongIsGreat(); // run Zhanglong's solution
    Node n = Project.Nodes.First(); // grabs new node
    EntityState es = Context.Entry(n).State; // state is Added
    Deviation d = n.Deviations.First(); // grabs new deviation
    es = Context.Entry(d).State; // state is Added

    What's even more strange is that if I run this code then check the database, I do find this deviation, and ONLY this deviation, there... as if checking for the deviation in the EF cache and/or its state CAUSES it to be persisted to the database.

    I'm not sure how Zhanglong's solution works (I swear it's magic) but I think I need a better understanding of what's going on under the hood. Looking at Zhanglong's solution, does anyone know why it might not work for child entities or entities on the many side of a one-to-many relation? And also, why does checking for the entity or its state in the cache force it to be persisted to the database?

    Thanks.
    Tuesday, March 20, 2018 8:43 PM

All replies

  • Hi gib898,

    >> does anyone know why it might not work for child entities or entities on the many side of a one-to-many relation? And also, why does checking for the entity or its state in the cache force it to be persisted to the database?

    If you want to refresh all the entities, please dispose context and create a new one.

    If you want to refresh specify entity with DbContext class, you can use

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


    if you want to refresh collection navigation properties, please try the following extend method

    public static void ReloadNavigationProperty<TEntity, TElement>(
            this DbContext context, 
            TEntity entity, 
            Expression<Func<TEntity, ICollection<TElement>>> navigationProperty)
            where TEntity : class
            where TElement : class
        {
            context.Entry(entity).Collection<TElement>(navigationProperty).Query();
        }

    Best regards,

    Zhanlgong


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, March 21, 2018 2:36 AM
    Moderator
  • Hi Zhanglong,

    I tried the reload method like you suggested:

    context.Entry(entity).Reload();

    While this seems to update all deleted nodes to 'detached' and all deleted deviations under them to 'deleted', I get the following error when trying to save:

    "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."

    The link doesn't help since it says I need to reload the entities from the database, and in this case reloading is what's CAUSING the error.

    Here's the full code that I implemented:

    Node n = Project.Nodes.First(); Deviation d = n.Deviations.First(); EntityState nodeState = n.State; // shows 'unchanged' EntityState devState = d.State; // shows 'unchanged' // Parse new entities from file here // Call delete stored procedure: Context.DeleteProjectData(...); nodeState = n.State; // shows 'unchanged' devState = d.State; // shows 'unchanged' // Get all old entries (anything 'unchanged'): var oldEntries = ((DbContext)Context).ChangeTracker.Entries().Where(e => e.State == EntityState.Unchanged); foreach (DbEntityEntry entry in oldEntries) { ((DbContext)Context).Entry(entry.Entity).Reload(); } nodeState = n.State; // shows 'detached' devState = d.State; // shows 'deleted'

    Context.SaveChangedAsync(); // throws exception


    Wednesday, March 21, 2018 3:44 PM