1

I'm using DbContext scope implementation. Basically it means that I relay on ambient DbContext approach. More information

I simplified my real code and removed all logic related to the context flow behavior...

using (var dbContextParentScope = ...)
{
    DbContext parentContext = dbContextParentScope.Get<ApplicationContext>();

    // that data will be in the DbContext cache
    var accounts = parentContext.Accounts.ToList();

    // this is always part of a separate method
    using (var dbContextChildScope = ...)
    {
        DbContext childContext = dbContextChildScope.Get<ApplicationContext>();

        var childAccounts = childContext.Accounts.ToList();
        childAccounts.ForEach(a => a.DisplayName = "New name");

        // after that operation parentContext data will be outdated
        childContext.SaveChanges();

        // this will reload updated entities in the parentContext
        dbContextChildScope.RefreshEntitiesInParentScope(childAccounts);
    }

    // without Refresh operation we will work with outdated data
    parentContext.SaveChanges();
}

This is exceptional case but sometimes we have to save important information outside main transaction.

Entity Framework has possibility to get record by key from DbContext:

if (((IObjectContextAdapter)childContext).ObjectContext.ObjectStateManager.TryGetObjectStateEntry(childAccounts[0], out ObjectStateEntry stateInChildScope))
{
    var key = stateInChildScope.EntityKey;

    if (((IObjectContextAdapter)parentContext).ObjectContext.ObjectStateManager.TryGetObjectStateEntry(key, out ObjectStateEntry stateInParentScope))
    {
        if (stateInParentScope.State == EntityState.Unchanged)
        {
            ((IObjectContextAdapter)parentContext).ObjectContext.Refresh(RefreshMode.StoreWins, stateInParentScope.Entity);
        }
    }
}

EF Core doesn't have such functionality but it has 'ChangeTracker'. It can return entries that DbContext is tracking:

parentContext.ChangeTracker.Entries().Where(w => w.State == EntityState.Unchanged).ToList()

But I want to reload only records that were updated in the childContext. So if I write something like this:

var updatedEntry = childContext.Entry(childAccounts[0]);
parentContext.Entry(updatedEntry.Entity).Reload();

EF Core thinks about updatedEntry like it is new and will throw System.InvalidOperationException:

The instance of entity type 'Account' cannot be tracked because another instance with the same key value for {'EntityId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

Also I found some internal functionality but I don't want to use internal EF Core code (except it is only way to make it works) because it can be changed/removed:

var entityType = parentContext.Model.FindEntityType(typeof(TEntityType));
var key = entityType.FindPrimaryKey();
var stateManager = parentContext.GetDependencies().StateManager;
var keysDictionary = key.Properties.ToDictionary(x => x.Name, x => x.PropertyInfo.GetValue(childAccounts[0]));
var entry = stateManager.TryGetEntry(key, keysDictionary.Values.ToArray());
parentContext.Entry(entry.Entity).Reload();

Does anyone know how can I update entities from parent context?

I hope for your help because I'm stuck here and I ran out of any ideas

Pavel
  • 1,015
  • 3
  • 13
  • 27
  • No public way so far. The internal functionality you found is similar to what I used here https://stackoverflow.com/questions/50748522/either-get-local-entity-or-attach-a-new-one/50749507#50749507, so encapsulate it in a custom method and update the implementation accordingly if/when EF Core code changed. – Ivan Stoev Mar 25 '19 at 19:10
  • Thanks @IvanStoev. You helped me again. I've noticed strange behavior related to the entity status which I can't explain. I've created stateManager from parentContext. TryGetEntry returns correct entry.Entity but entry.EntityState has incorrect value (Unchanged instead Modified). But parentContext.Entry(entry.Entity) returns correct State (Modified because I've changed entity in the parentContext). Have you seen information about it somewhere? – Pavel Mar 28 '19 at 17:12
  • 1
    Probably `parentContext.Entry` method calls internally `DetectChanges` first (see this v3.0 change https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#dbcontextentry-now-performs-a-local-detectchanges). If you need the correct State (in my code I needed just the entity), probably you should call `parentContext.ChangeTracker.DetectChanges();` somewhere. – Ivan Stoev Mar 28 '19 at 17:35
  • Thanks. `parentContext.ChangeTracker.DetectChanges();` works and helped to optimize my code. – Pavel Mar 28 '19 at 18:08

0 Answers0