protected virtual EntityEntry Merge(object detached, object persisted, NavigationEntry parentNavigation, HashSet <object> visited) { _eventManager.OnEntityMerging(detached, persisted, parentNavigation); EntityEntry persistedEntry = _entryServices.FindEntry(persisted) ?? _dbContext.Entry(persisted); visited.Add(persistedEntry.Entity); bool modified = Copy(detached, persistedEntry); foreach (NavigationEntry navigationEntry in persistedEntry.Navigations) { bool owned = navigationEntry.Metadata.IsOwned(); bool associated = navigationEntry.Metadata.IsAssociated(); if (!(associated || owned)) { continue; } IEntityType navType = navigationEntry.Metadata.GetTargetType(); IClrPropertyGetter getter = navigationEntry.Metadata.GetGetter(); object detachedValue = getter.GetClrValue(detached); IEntityServices entityServices = _entityServicesFactory.GetEntityServices(navType); if (navigationEntry.Metadata.IsCollection()) { // a mutable list to store the result. IList mergedList = Activator.CreateInstance(typeof(List <>).MakeGenericType(navType.ClrType)) as IList; // create hash table for O(N) merge. Dictionary <KeyValue, object> dbTable = entityServices.CreateTable((IEnumerable)navigationEntry.CurrentValue); if (detachedValue != null) { foreach (var detachedItem in (IEnumerable)detachedValue) { object persistedItem; KeyValue entityKey = entityServices.GetKeyValue(detachedItem); if (dbTable.TryGetValue(entityKey, out persistedItem)) { mergedList.Add(owned ? Merge(detachedItem, persistedItem, navigationEntry, visited).Entity : persistedItem); dbTable.Remove(entityKey); // remove it from the table, to avoid deletion. } else { mergedList.Add(owned ? Add(detachedItem, navigationEntry, visited).Entity : Attach(detachedItem, navigationEntry).Entity); } } } // the rest of the items in the dbTable should be removed. foreach (var dbItem in dbTable) { Delete(dbItem.Value, navigationEntry, visited); } // let EF do the rest of the work. navigationEntry.CurrentValue = mergedList; } else { if (visited.Contains(navigationEntry.CurrentValue)) { continue; } if (entityServices.Equal(detachedValue, navigationEntry.CurrentValue)) { // merge owned references and do nothing for associated references. if (owned) { navigationEntry.CurrentValue = Merge(detachedValue, navigationEntry.CurrentValue, navigationEntry, visited).Entity; } } else { if (navigationEntry.CurrentValue != null) { if (owned) { Delete(navigationEntry.CurrentValue, navigationEntry, visited); } } if (detachedValue != null) { navigationEntry.CurrentValue = owned ? Add(detachedValue, navigationEntry, visited).Entity : Attach(detachedValue, navigationEntry).Entity; } else { // fix: if we use lazy loading we can delete correct value // for example: [FK] ItemId -> Item (is null) // ItemId = 1 (from RESTfull for example), Item = null var fk = navigationEntry.Metadata.ForeignKey?.Properties?.SingleOrDefault(); var data = fk?.GetGetter().GetClrValue(detached); if (data != null && data.GetType().IsPrimitive&& !data.GetType().IsDefaultValue(data)) { continue; } navigationEntry.CurrentValue = null; } } } } return(_eventManager.OnEntityMerged(detached, persistedEntry, modified, parentNavigation).EntityEntry); }
protected virtual EntityEntry Merge(object detached, object persisted, NavigationEntry parentNavigation, HashSet <object> visited) { var args = _eventManager.OnEntityMerging(detached, persisted, parentNavigation); EntityEntry persistedEntry = _entryServices.FindEntry(persisted); if (persistedEntry == null) { persistedEntry = _dbContext.Entry(persisted); } visited.Add(persistedEntry.Entity); bool modified = Copy(detached, persistedEntry); foreach (NavigationEntry navigationEntry in persistedEntry.Navigations) { bool owned = navigationEntry.Metadata.IsOwned(); bool associated = navigationEntry.Metadata.IsAssociated(); if (!(associated || owned)) { continue; } IEntityType navType = navigationEntry.Metadata.GetTargetType(); IClrPropertyGetter getter = navigationEntry.Metadata.GetGetter(); object detachedValue = getter.GetClrValue(detached); IEntityServices entityServices = _entityServicesFactory.GetEntityServices(navType); if (navigationEntry.Metadata.IsCollection()) { // a mutable list to store the result. IList mergedList = Activator.CreateInstance(typeof(List <>).MakeGenericType(navType.ClrType)) as IList; // create hash table for O(N) merge. Dictionary <KeyValue, object> dbTable = entityServices.CreateTable((IEnumerable)navigationEntry.CurrentValue); if (detachedValue != null) { foreach (object detachedItem in (IEnumerable)detachedValue) { object persistedItem; KeyValue entityKey = entityServices.GetKeyValue(detachedItem); if (dbTable.TryGetValue(entityKey, out persistedItem)) { if (owned) { mergedList.Add(Merge(detachedItem, persistedItem, navigationEntry, visited).Entity); } else { mergedList.Add(persistedItem); } dbTable.Remove(entityKey); // remove it from the table, to avoid deletion. } else { mergedList.Add(owned ? Add(detachedItem, navigationEntry, visited).Entity : Attach(detachedItem, navigationEntry).Entity); } } } // the rest of the items in the dbTable should be removed. foreach (var dbItem in dbTable) { Delete(dbItem.Value, navigationEntry, visited); } // let EF do the rest of the work. navigationEntry.CurrentValue = mergedList; } else { if (!visited.Contains(navigationEntry.CurrentValue)) // avoid stack overflow! (this might be also done checking if the property is dependent to parent) { if (entityServices.Equal(detachedValue, navigationEntry.CurrentValue)) { // merge owned references and do nothing for associated references. if (owned) { navigationEntry.CurrentValue = Merge(detachedValue, navigationEntry.CurrentValue, navigationEntry, visited).Entity; } } else { if (navigationEntry.CurrentValue != null) { if (owned) { Delete(navigationEntry.CurrentValue, navigationEntry, visited); } } if (detachedValue != null) { if (owned) { navigationEntry.CurrentValue = Add(detachedValue, navigationEntry, visited).Entity; } else { navigationEntry.CurrentValue = Attach(detachedValue, navigationEntry).Entity; } } else { navigationEntry.CurrentValue = null; } } } } } return(_eventManager.OnEntityMerged(detached, persistedEntry, modified, parentNavigation).EntityEntry); }