/// <summary> /// Translates the Expression tree to a tree of UpdateMembers /// </summary> /// <param name="expression">The expression.</param> /// <returns></returns> public UpdateMember GetUpdateMembers(Expression <Func <IUpdateConfiguration <T>, object> > expression) { var initialNode = new UpdateMember(); currentMember = initialNode; Visit(expression); return(initialNode); }
protected override Expression VisitMethodCall(MethodCallExpression expression) { currentMethod = expression.Method.Name; // go left to right in the subtree (ignore first argument for now) for (int i = 1; i < expression.Arguments.Count; i++) { Visit(expression.Arguments[i]); } // go back up the tree and continue currentMember = currentMember.Parent; return(Visit(expression.Arguments[0])); }
public static List <string> GetIncludeStrings(UpdateMember root) { var list = new List <string>(); if (root.Members.Count == 0) { list.Add(root.IncludeString); } foreach (var member in root.Members) { list.AddRange(GetIncludeStrings(member)); } return(list); }
protected override Expression VisitMember(MemberExpression expression) { // Create new node for this item var newMember = new UpdateMember { Members = new Stack <UpdateMember>(), Parent = currentMember, Accessor = (PropertyInfo)expression.Member }; currentMember.Members.Push(newMember); previousMember = currentMember; currentMember = newMember; currentMember.IncludeString = previousMember.IncludeString != null ? previousMember.IncludeString + "." + currentMember.Accessor.Name : currentMember.Accessor.Name; // Chose if entity update or reference update and create expression switch (currentMethod) { case "OwnedEntity": currentMember.IsOwned = true; break; case "AssociatedEntity": currentMember.IsOwned = false; break; case "OwnedCollection": currentMember.IsOwned = true; currentMember.IsCollection = true; break; case "AssociatedCollection": currentMember.IsOwned = false; currentMember.IsCollection = true; break; default: throw new NotSupportedException("The method used in the update mapping is not supported"); } return(base.VisitMember(expression)); }
/// <summary> /// Updates a detached graph of entities by performing a diff comparison of object keys. /// Author: Brent McKendrick /// </summary> /// <param name="context">The database context to attach / detach.</param> /// <param name="dataStoreEntity">The entity (sub)graph as retrieved from the data store.</param> /// <param name="updatingEntity">The entity (sub)graph after it has been updated</param> private static void RecursiveGraphUpdate(DbContext context, object dataStoreEntity, object updatingEntity, UpdateMember member) { // Get item Type var itemType = member.Accessor.PropertyType.IsGenericType ? member.Accessor.PropertyType.GetGenericArguments().First() : member.Accessor.PropertyType; // Create Helpful Linq Methods Func <IEnumerable, IEnumerable> distinct = l => (IEnumerable)typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Distinct") && m.GetParameters().Count() == 1).MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); Func <IEnumerable, IEnumerable> toList = l => (IEnumerable)typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); Func <IEnumerable, object> singleOrDefault = l => typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("SingleOrDefault") && m.GetParameters().Count() == 1).MakeGenericMethod(new[] { itemType }).Invoke(null, new object[] { l }); // Get member's navigation property var memberNavProp = (member.Parent == null) ? member.IncludeString : member.IncludeString.Substring((member.Parent.IncludeString ?? "").Length).TrimStart('.'); if (member.IsCollection) { // We are dealing with a collection // Get distinctly the new itens var updateValues = (IEnumerable)member.Accessor.GetValue(updatingEntity, null); updateValues = updateValues != null?toList(distinct(updateValues)) : new List <object>(); // Get database values IEnumerable dbCollection; var actualEntityIsOnTheContext = updatingEntity == dataStoreEntity; if (actualEntityIsOnTheContext && context.Entry(updatingEntity).State != EntityState.Added) { dbCollection = member.IsOwned ? toList(context.Entry(updatingEntity).Collection(memberNavProp).Query()) : toList(((ObjectQuery)context.Entry(updatingEntity).Collection(memberNavProp).Query()).Execute(MergeOption.OverwriteChanges)); // Assure the entity's child collection wasn't refreshed when getting db values. member.Accessor.SetValue(updatingEntity, updateValues, null); } else { dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null); } Type dbCollectionType = dbCollection.GetType(); Type innerElementType; if (dbCollectionType.IsArray) { innerElementType = dbCollectionType.GetElementType(); } else if (dbCollectionType.IsGenericType) { innerElementType = dbCollectionType.GetGenericArguments()[0]; } else { throw new InvalidOperationException("GraphDiff required the collection to be either IEnumerable<T> or T[]"); } var keyFields = context.GetKeysFor(innerElementType); var dbHash = MapCollectionToDictionary(keyFields, dbCollection); // Iterate through the elements from the updated graph and try to match them against the db graph. List <object> additions = new List <object>(); foreach (object updateItem in updateValues) { var key = CreateHash(keyFields, updateItem); // try to find in db collection object dbItem; if (dbHash.TryGetValue(key, out dbItem)) { // If we own the collection if (member.IsOwned) { context.Entry(dbItem).CurrentValues.SetValues(updateItem); // match means we are updating AttachCyclicNavigationProperty(context, dataStoreEntity, updateItem); foreach (var childMember in member.Members) { RecursiveGraphUpdate(context, dbHash[key], updateItem, childMember); } } dbHash.Remove(key); // remove to leave only db removals in the collection } else { additions.Add(updateItem); } } // Removal of dbItem's left in the collection foreach (var dbItem in dbHash.Values) { // Removing dbItem, assure it's children will me deleted. foreach (var subMember in member.Members) { RecursiveGraphUpdate(context, dbItem, dbItem, subMember); } // Own the collection so remove it completely. if (member.IsOwned) { context.Set(ObjectContext.GetObjectType(dbItem.GetType())).Remove(dbItem); } dbCollection.GetType().GetMethod("Remove").Invoke(dbCollection, new[] { dbItem }); } // Add elements marked for addition foreach (object newItem in additions) { if (!member.IsOwned) { context.Set(ObjectContext.GetObjectType(newItem.GetType())).Attach(newItem); if (GraphDiffConfiguration.ReloadAssociatedEntitiesWhenAttached) { context.Entry(newItem).Reload(); } } // Otherwise we will add to object dbCollection.GetType().GetMethod("Add").Invoke(dbCollection, new[] { newItem }); AttachCyclicNavigationProperty(context, dataStoreEntity, newItem); } } else // not collection { var actualEntityIsOnTheContext = updatingEntity == dataStoreEntity; var dbvalue = (actualEntityIsOnTheContext && context.Entry(dataStoreEntity).State != EntityState.Added)// && member.IsOwned) ? singleOrDefault(context.Entry(updatingEntity).Reference(memberNavProp).Query()) : member.Accessor.GetValue(dataStoreEntity, null); var newvalue = member.Accessor.GetValue(updatingEntity, null); if (dbvalue == null && newvalue == null) // No value { return; } // If we own the collection then we need to update the entities otherwise simple relationship update if (!member.IsOwned) { if (dbvalue != null) { // sets the state of the associated entity as unchanged. // I need reload the dbValue to undo the changes made in it // (except when it is added state; in this case, we will add it). if (context.Entry(dbvalue).State != EntityState.Added) { context.Entry(dbvalue).Reload(); } context.Entry(dbvalue).State = EntityState.Unchanged; } if (newvalue == null) { member.Accessor.SetValue(dataStoreEntity, null, null); return; } if (dbvalue != null && newvalue != null) { var keyFields = context.GetKeysFor(ObjectContext.GetObjectType(newvalue.GetType())); var newKey = CreateHash(keyFields, newvalue); var updateKey = CreateHash(keyFields, dbvalue); if (newKey == updateKey) { return; // do nothing if the same key } } if (context.Entry(newvalue).State == EntityState.Detached) { context.Set(ObjectContext.GetObjectType(newvalue.GetType())).Attach(newvalue); } member.Accessor.SetValue(dataStoreEntity, newvalue, null); context.Entry(newvalue).State = EntityState.Unchanged; //if (GraphDiffConfiguration.ReloadAssociatedEntitiesWhenAttached) context.Entry(newvalue).Reload(); } else { if (dbvalue != null && newvalue != null) { // Check if the same key, if so then update values on the entity var keyFields = context.GetKeysFor(ObjectContext.GetObjectType(newvalue.GetType())); var newKey = CreateHash(keyFields, newvalue); var updateKey = CreateHash(keyFields, dbvalue); // perform update if the same if (updateKey == newKey) { context.Entry(dbvalue).CurrentValues.SetValues(newvalue); } else { member.Accessor.SetValue(dataStoreEntity, newvalue, null); } } else { member.Accessor.SetValue(dataStoreEntity, newvalue, null); } AttachCyclicNavigationProperty(context, dataStoreEntity, newvalue); // TODO foreach (var childMember in member.Members) { RecursiveGraphUpdate(context, dbvalue, newvalue, childMember); } } } }