private Expression CreateIncludeExpression(UpdateMember previousMember, UpdateMember currentMember) { // Add First() for Include expression if previous was a collection if (previousMember.IsCollection) { var previousType = ((MemberExpression)previousMember.IncludeExpression).Type; var currentType = currentMember.Accessor.PropertyType; if (previousType.IsGenericType && previousType.GetGenericArguments().Length == 1) { var innerParam = Expression.Parameter(previousType.GetGenericArguments()[0]); return(Expression.Call( typeof(Enumerable), "Select", new[] { previousType.GetGenericArguments()[0], currentType }, previousMember.IncludeExpression, Expression.Lambda( Expression.Property(innerParam, currentMember.Accessor) , innerParam))); } else { throw new NotSupportedException("Only supports generic typed collections of IEnumerable<T>"); } } else { return(Expression.Property(previousMember.IncludeExpression, (PropertyInfo)currentMember.Accessor)); } }
/// <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() { IncludeExpression = Expression.Parameter(typeof(T), "p") }; currentMember = initialNode; Visit(expression); return(initialNode); }
private static void GetIncludeExpressions <T>(UpdateMember member, ParameterExpression parameter, List <Expression <Func <T, object> > > expressions) { if (!member.HasMembers()) { expressions.Add(Expression.Lambda <Func <T, object> >(member.IncludeExpression, parameter)); } else { foreach (var iMember in member.Members) { GetIncludeExpressions <T>(iMember, parameter, expressions); } } }
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])); }
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.IncludeExpression = CreateIncludeExpression(previousMember, currentMember); // 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) { if (member.IsCollection) { // We are dealing with a collection var updateValues = (IEnumerable)member.Accessor.GetValue(updatingEntity, null); var dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null); var keyFields = context.GetKeysFor(updateValues.GetType().GetGenericArguments()[0]); 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) { try { 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).State != System.Data.Entity.EntityState.Detached)) { context.Entry(dbItem).CurrentValues.SetValues(updateItem); // match means we are updating 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); } } catch { } } // Removal of dbItem's left in the collection foreach (var dbItem in dbHash.Values) { // Own the collection so remove it completely. if (member.IsOwned) { context.Set(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(newItem.GetType()).Attach(newItem); } // Otherwise we will add to object dbCollection.GetType().GetMethod("Add").Invoke(dbCollection, new[] { newItem }); } } else // not collection { var dbvalue = 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 && newvalue != null) { var keyFields = context.GetKeysFor(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 == System.Data.Entity.EntityState.Detached) { context.Set(newvalue.GetType()).Attach(newvalue); } member.Accessor.SetValue(dataStoreEntity, newvalue, null); context.Entry(newvalue).Reload(); // TODO would like to do this: context.Entry(newvalue).State = EntityState.Unchanged; // However it seems even though we are in an unchanged state EF will still update the database if the original values are different. } else { if (dbvalue != null && newvalue != null) { // Check if the same key, if so then update values on the entity var keyFields = context.GetKeysFor(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); context.Entry(dbvalue).State = System.Data.Entity.EntityState.Modified; } else { member.Accessor.SetValue(dataStoreEntity, newvalue, null); } } else { member.Accessor.SetValue(dataStoreEntity, newvalue, null); } // TODO foreach (var childMember in member.Members) { RecursiveGraphUpdate(context, dbvalue, newvalue, childMember); } } } }
public static List <Expression <Func <T, object> > > GetIncludeExpressions <T>(UpdateMember member) { var expressions = new List <Expression <Func <T, object> > >(); GetIncludeExpressions <T>(member, Expression.Parameter(typeof(T), "p"), expressions); return(expressions); }