Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        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]));
        }
Ejemplo n.º 3
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);
        }
Ejemplo n.º 4
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.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));
        }
Ejemplo n.º 5
0
        /// <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);
                    }
                }
            }
        }