예제 #1
0
        // Recursively get entity states
        public static IEnumerable <EntityState> GetEntityStates(this DbContext context,
                                                                ITrackable item, EntityState?entityState = null,
                                                                ObjectVisitationHelper visitationHelper  = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item))
            {
                yield break;
            }

            foreach (var colProp in item.GetNavigationProperties().OfCollectionType())
            {
                foreach (ITrackable child in colProp.EntityCollection)
                {
                    foreach (var state in context.GetEntityStates(child, visitationHelper: visitationHelper))
                    {
                        if (entityState == null || state == entityState)
                        {
                            yield return(state);
                        }
                    }
                }
            }
            yield return(context.Entry(item).State);
        }
예제 #2
0
        /// <summary>
        /// Recursively enable or disable tracking on trackable entities in an object graph.
        /// </summary>
        /// <param name="item">Trackable object</param>
        /// <param name="enableTracking">Enable or disable change-tracking</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        /// <param name="oneToManyOnly">True if tracking should be set only for OneToMany relations</param>
        public static void SetTracking(this ITrackable item, bool enableTracking,
                                       ObjectVisitationHelper visitationHelper = null, bool oneToManyOnly = false)
        {
            // Iterator entity properties
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Skip if 1-M only
                if (!oneToManyOnly)
                {
                    // Set tracking on 1-1 and M-1 properties
                    foreach (var refProp in navProp.AsReferenceProperty())
                    {
                        // Get ref prop change tracker
                        ITrackingCollection refChangeTracker = item.GetRefPropertyChangeTracker(refProp.Property.Name);
                        if (refChangeTracker != null)
                        {
                            // Set tracking on ref prop change tracker
                            refChangeTracker.SetTracking(enableTracking, visitationHelper, oneToManyOnly);
                        }
                    }
                }

                // Set tracking on 1-M and M-M properties (if not 1-M only)
                foreach (var colProp in navProp.AsCollectionProperty <ITrackingCollection>())
                {
                    bool isOneToMany = !IsManyToManyChildCollection(colProp.EntityCollection);
                    if (!oneToManyOnly || isOneToMany)
                    {
                        colProp.EntityCollection.SetTracking(enableTracking, visitationHelper, oneToManyOnly);
                    }
                }
            }
        }
예제 #3
0
        // Recursively get modified properties
        public static IEnumerable <IEnumerable <string> > GetModifiedProperties
            (this ITrackable item, ObjectVisitationHelper visitationHelper = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item))
            {
                yield break;
            }

            foreach (var navProp in item.GetNavigationProperties())
            {
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    foreach (var modifiedProps in refProp.EntityReference.GetModifiedProperties(visitationHelper))
                    {
                        yield return(modifiedProps);
                    }
                }

                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    foreach (ITrackable child in colProp.EntityCollection)
                    {
                        foreach (var modifiedProps in child.GetModifiedProperties(visitationHelper))
                        {
                            yield return(modifiedProps);
                        }
                    }
                }
            }
            yield return(item.ModifiedProperties);
        }
예제 #4
0
        // Recursively set tracking state
        public static void SetTrackingState(this ITrackable item,
                                            TrackingState state, ObjectVisitationHelper visitationHelper = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item))
            {
                return;
            }

            foreach (var navProp in item.GetNavigationProperties())
            {
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    refProp.EntityReference.SetTrackingState(state, visitationHelper);
                    refProp.EntityReference.TrackingState = state;
                }

                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    foreach (ITrackable child in colProp.EntityCollection)
                    {
                        child.SetTrackingState(state, visitationHelper);
                        child.TrackingState = state;
                    }
                }
            }
        }
예제 #5
0
        /// <summary>
        /// Recursively enable or disable tracking on trackable entities in an object graph.
        /// </summary>
        /// <param name="item">Trackable object</param>
        /// <param name="enableTracking">Enable or disable change-tracking</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        public static void SetTracking(this ITrackable item,
                                       bool enableTracking, ObjectVisitationHelper visitationHelper = null)
        {
            // Iterator entity properties
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Set tracking on 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    // Get ref prop change tracker
                    ITrackingCollection refChangeTracker = item.GetRefPropertyChangeTracker(refProp.Property.Name);
                    if (refChangeTracker != null)
                    {
                        // Set tracking on ref prop change tracker
                        refChangeTracker.SetTracking(enableTracking, visitationHelper);
                    }
                }

                // Set tracking on 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty <ITrackingCollection>())
                {
                    colProp.EntityCollection.SetTracking(enableTracking, visitationHelper);
                }
            }
        }
예제 #6
0
 GetEntityReferenceProperty <TEntity>(this ITrackable entity, PropertyInfo property)
     where TEntity : class, ITrackable
 {
     return(entity.GetNavigationProperties(false)
            .Where(np => np.Property == property)
            .OfReferenceType <TEntity>()
            .Single());
 }
예제 #7
0
 GetEntityCollectionProperty <TEntityCollection>(this ITrackable entity,
                                                 PropertyInfo property)
     where TEntityCollection : class
 {
     return(entity.GetNavigationProperties(false)
            .Where(np => np.Property == property)
            .OfCollectionType <TEntityCollection>()
            .Single());
 }
예제 #8
0
        private static void SetChanges(this DbContext context,
                                       ITrackable item, EntityState state,
                                       ObjectVisitationHelper visitationHelper,
                                       ITrackable parent = null, string propertyName = null)
        {
            // Set state for child collections
            foreach (var navProp in item.GetNavigationProperties())
            {
                string propName = navProp.Property.Name;

                // Apply changes to 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    ITrackable trackableReference = refProp.EntityReference;
                    if (visitationHelper.IsVisited(trackableReference))
                    {
                        continue;
                    }

                    context.ApplyChanges(trackableReference, item, visitationHelper, propName);
                    if (context.IsRelatedProperty(item.GetType(), propName, RelationshipType.OneToOne))
                    {
                        context.SetChanges(trackableReference, state, visitationHelper, item, propName);
                    }
                }

                // Apply changes to 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    foreach (ITrackable trackableChild in colProp.EntityCollection.Reverse())
                    {
                        // Prevent endless recursion
                        if (visitationHelper.TryVisit(trackableChild))
                        {
                            // TRICKY: we have just visited the item
                            // As a side effect, ApplyChanges will never be called for it.
                            context.SetChanges(trackableChild, state, visitationHelper, item, propName);
                        }
                    }
                }
            }

            // If M-M child, set relationship for deleted items
            if (state == EntityState.Deleted && parent != null && propertyName != null &&
                (context.IsRelatedProperty(parent.GetType(),
                                           propertyName, RelationshipType.ManyToMany)))
            {
                context.Entry(item).State = item.TrackingState == TrackingState.Modified
                    ? EntityState.Modified
                    : EntityState.Unchanged;
                context.SetRelationshipState(item, parent, propertyName, EntityState.Deleted);
                return;
            }

            // Set entity state
            context.Entry(item).State = state;
        }
 private void FixUpParentReference(ITrackable child, ITrackable parent, bool isTracking)
 {
     foreach (var refProp in child.GetNavigationProperties()
              .OfReferenceType()
              .Where(rp => PortableReflectionHelper.Instance.IsAssignable(rp.Property.PropertyType, parent.GetType()))
              .Where(rp => !ReferenceEquals(rp.EntityReference, parent)))
     {
         Tracking = false;
         refProp.Property.SetValue(child, parent, null);
         Tracking = isTracking;
     }
 }
예제 #10
0
        /// <summary>
        /// Recursively set tracking state on trackable entities in an object graph.
        /// </summary>
        /// <param name="item">Trackable object</param>
        /// <param name="state">Change-tracking state of an entity</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        /// <param name="isManyToManyItem">True is an item is treated as part of an M-M collection</param>
        public static void SetState(this ITrackable item, TrackingState state, ObjectVisitationHelper visitationHelper,
                                    bool isManyToManyItem = false)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

            // Prevent endless recursion
            if (!visitationHelper.TryVisit(item))
            {
                return;
            }

            // Recurively set state for unchanged, added or deleted items,
            // but not for M-M child item
            if (!isManyToManyItem && state != TrackingState.Modified)
            {
                // Iterate entity properties
                foreach (var colProp in item.GetNavigationProperties().OfCollectionType <ITrackingCollection>())
                {
                    // Process 1-M and M-M properties
                    // Set state on child entities
                    bool isManyToManyChildCollection = IsManyToManyChildCollection(colProp.EntityCollection);
                    foreach (ITrackable trackableChild in colProp.EntityCollection)
                    {
                        trackableChild.SetState(state, visitationHelper,
                                                isManyToManyChildCollection);
                    }
                }
            }

            // Deleted items are treated a bit specially
            if (state == TrackingState.Deleted)
            {
                if (isManyToManyItem)
                {
                    // With M-M properties there is no way to tell if the related entity should be deleted
                    // or simply removed from the relationship, because it is an independent association.
                    // Therefore, deleted children are marked unchanged.
                    if (item.TrackingState != TrackingState.Modified)
                    {
                        item.TrackingState = TrackingState.Unchanged;
                    }
                    return;
                }
                // When deleting added item, set state to unchanged
                else if (item.TrackingState == TrackingState.Added)
                {
                    item.TrackingState = TrackingState.Unchanged;
                    return;
                }
            }

            item.TrackingState = state;
        }
예제 #11
0
        private static void AcceptChanges(this ITrackable item, ObjectVisitationHelper visitationHelper)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

            // Prevent endless recursion
            if (visitationHelper != null && !visitationHelper.TryVisit(item))
            {
                return;
            }

            // Set tracking state for child collections
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Apply changes to 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    refProp.EntityReference.AcceptChanges(visitationHelper);
                }

                // Apply changes to 1-M properties
                foreach (var colProp in navProp.AsCollectionProperty <IList>())
                {
                    var items = colProp.EntityCollection;
                    var count = items.Count;
                    for (int i = count - 1; i > -1; i--)
                    {
                        // Stop recursion if trackable hasn't been visited
                        var trackable = items[i] as ITrackable;
                        if (trackable != null)
                        {
                            if (trackable.TrackingState == TrackingState.Deleted)
                            {
                                // Remove items marked as deleted
                                items.RemoveAt(i);
                            }
                            else
                            {
                                // Recursively accept changes on trackable
                                trackable.AcceptChanges(visitationHelper);
                            }
                        }
                    }
                }
            }

            // Set tracking state and clear modified properties
            item.TrackingState      = TrackingState.Unchanged;
            item.ModifiedProperties = null;
        }
예제 #12
0
        private void FixUpParentReference(ITrackable child, ITrackable parent, bool isTracking)
        {
            foreach (var refProp in child.GetNavigationProperties()
                     .OfReferenceType()
#if SILVERLIGHT || NET40
                     .Where(rp => rp.Property.PropertyType.IsAssignableFrom(parent.GetType()))
#else
                     .Where(rp => rp.Property.PropertyType.GetTypeInfo().IsAssignableFrom(parent.GetType().GetTypeInfo()))
#endif
                     .Where(rp => !ReferenceEquals(rp.EntityReference, parent)))
            {
                Tracking = false;
                refProp.Property.SetValue(child, parent, null);
                Tracking = isTracking;
            }
        }
예제 #13
0
        private static void LoadRelatedEntitiesOnProperties(this DbContext context,
                                                            ITrackable item, ObjectVisitationHelper visitationHelper, bool loadAll)
        {
            // Recursively load related entities
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Apply changes to 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    context.LoadRelatedEntities(new[] { refProp.EntityReference }, item, visitationHelper, loadAll);
                }

                // Apply changes to 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    context.LoadRelatedEntities(colProp.EntityCollection, item, visitationHelper, loadAll);
                }
            }
        }
예제 #14
0
        /// <summary>
        /// Recursively set tracking state on trackable properties in an object graph.
        /// </summary>
        /// <param name="item">Trackable object</param>
        /// <param name="modified">Properties on an entity that have been modified</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        public static void SetModifiedProperties(this ITrackable item,
                                                 ICollection <string> modified, ObjectVisitationHelper visitationHelper = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item))
            {
                return;
            }

            // Iterate entity properties
            foreach (var colProp in item.GetNavigationProperties().OfCollectionType <ITrackingCollection>())
            {
                // Recursively set modified
                foreach (ITrackable child in colProp.EntityCollection)
                {
                    child.SetModifiedProperties(modified, visitationHelper);
                    child.ModifiedProperties = modified;
                }
            }
        }
예제 #15
0
        private async static Task LoadRelatedEntitiesOnPropertiesAsync(this DbContext context,
                                                                       ITrackable item, ObjectVisitationHelper visitationHelper, CancellationToken cancellationToken, bool loadAll)
        {
            // Recursively load related entities
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Apply changes to 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    await context.LoadRelatedEntitiesAsync(new[] { refProp.EntityReference }, item, visitationHelper,
                                                           cancellationToken, loadAll).ConfigureAwait(false);
                }

                // Apply changes to 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    await context.LoadRelatedEntitiesAsync(colProp.EntityCollection, item, visitationHelper,
                                                           cancellationToken, loadAll).ConfigureAwait(false);
                }
            }
        }
예제 #16
0
        // Recursively get entity states
        public static IEnumerable<EntityState> GetEntityStates(this DbContext context,
            ITrackable item, EntityState? entityState = null,
            ObjectVisitationHelper visitationHelper = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item)) yield break;

            foreach (var colProp in item.GetNavigationProperties().OfCollectionType())
            {
                foreach (ITrackable child in colProp.EntityCollection)
                {
                    foreach (var state in context.GetEntityStates(child, visitationHelper: visitationHelper))
                    {
                        if (entityState == null || state == entityState)
                            yield return state;
                    }
                }
            }
            yield return context.Entry(item).State;
        }
예제 #17
0
        // Recursively get tracking states
        public static IEnumerable <TrackingState> GetTrackingStates
            (this ITrackable item, TrackingState?trackingState = null,
            ObjectVisitationHelper visitationHelper            = null)
        {
            // Prevent endless recursion
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);
            if (!visitationHelper.TryVisit(item))
            {
                yield break;
            }

            foreach (var navProp in item.GetNavigationProperties())
            {
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    foreach (var state in refProp.EntityReference.GetTrackingStates(visitationHelper: visitationHelper))
                    {
                        if (trackingState == null || state == trackingState)
                        {
                            yield return(state);
                        }
                    }
                }

                foreach (var colProp in navProp.AsCollectionProperty())
                {
                    foreach (ITrackable child in colProp.EntityCollection)
                    {
                        foreach (var state in child.GetTrackingStates(visitationHelper: visitationHelper))
                        {
                            if (trackingState == null || state == trackingState)
                            {
                                yield return(state);
                            }
                        }
                    }
                }
            }
            yield return(item.TrackingState);
        }
예제 #18
0
        private static void ApplyChangesOnProperties(this DbContext context,
                                                     ITrackable item, ObjectVisitationHelper visitationHelper, TrackingState?state = null)
        {
            // Recursively apply changes
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Apply changes to 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    context.ApplyChanges(refProp.EntityReference, item, visitationHelper, navProp.Property.Name, state);
                }

                // Apply changes to 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty <IList>())
                {
                    // Apply changes on collection property for each state.
                    // Process added items first, then process others.
                    ApplyChangesOnCollectionProperties(TrackingState.Added, true,
                                                       navProp, colProp, context, item, visitationHelper, state);
                    ApplyChangesOnCollectionProperties(TrackingState.Added, false,
                                                       navProp, colProp, context, item, visitationHelper, state);
                }
            }
        }
        private static bool HasChanges(this ITrackable item,
                                       ObjectVisitationHelper visitationHelper,
                                       Dictionary <ITrackable, bool> cachedResults)
        {
            // Prevent endless recursion
            if (!visitationHelper.TryVisit(item))
            {
                bool result;
                if (cachedResults.TryGetValue(item, out result))
                {
                    return(result);
                }

                // if the circle closes and we reach again the item for which
                // we are currently determining "HasChanges" and so far we encounter
                // no changed items along the way, then just assume "no change".
                // However after inspection of other branches this result may still be corrected.
                return(false);
            }

            // See if item has changes
            bool itemHasChanges = item.TrackingState != TrackingState.Unchanged;

            if (itemHasChanges)
            {
                return(true);
            }

            // Recursively see if trackable properties have changes
            foreach (var navProp in item.GetNavigationProperties())
            {
                // Process 1-1 and M-1 properties
                foreach (var refProp in navProp.AsReferenceProperty())
                {
                    // See if ref prop has changes
                    bool refPropHasChanges = refProp.EntityReference.HasChanges(visitationHelper, cachedResults);
                    cachedResults[refProp.EntityReference] = refPropHasChanges;
                    if (refPropHasChanges)
                    {
                        return(true);
                    }
                }

                // Process 1-M and M-M properties
                foreach (var colProp in navProp.AsCollectionProperty <ITrackingCollection>())
                {
                    // See if there are any cached deletes
                    var cachedDeletes = colProp.EntityCollection.GetChanges(true);
                    if (cachedDeletes.Count > 0)
                    {
                        return(true);
                    }

                    // See if child entities have changes
                    foreach (ITrackable trackableChild in colProp.EntityCollection)
                    {
                        // Continue recursion
                        // REVISIT: shall we make a "shallow" scan in case of M-M?
                        if (trackableChild != null)
                        {
                            bool childHasChanges = trackableChild.HasChanges(visitationHelper, cachedResults);
                            cachedResults[trackableChild] = childHasChanges;
                            if (childHasChanges)
                            {
                                return(true);
                            }
                        }
                    }
                }
            }

            // Return false if there are no changes
            return(false);
        }
        private static void SetEntityProperties(this ITrackable targetItem, ITrackable sourceItem,
                                                ITrackingCollection changeTracker)
        {
            // List of 'prop.SetValue' actions
            var actions = new List <Action>();

            // Iterate simple properties
#if SILVERLIGHT || NET40
            foreach (var prop in targetItem.GetType().GetProperties().Where(p => p.CanWrite)
#else
            foreach (var prop in targetItem.GetType().GetTypeInfo().DeclaredProperties
                     .Where(p => p.CanWrite && !p.GetMethod.IsPrivate)
#endif
                     .Except(targetItem.GetNavigationProperties(false).Select(np => np.Property)))
            {
                // Skip tracking properties
                if (prop.Name == Constants.TrackingProperties.TrackingState ||
                    prop.Name == Constants.TrackingProperties.ModifiedProperties)
                {
                    continue;
                }

                // Get source item prop value
                object sourceValue = prop.GetValue(sourceItem, null);
                object targetValue = prop.GetValue(targetItem, null);

                // Continue if source is null or source and target equal
                if (sourceValue == null || sourceValue.Equals(targetValue))
                {
                    continue;
                }

                // Deferred 'SetValue'
                actions.Add(() => prop.SetValue(targetItem, sourceValue, null));
            }

            // Iterate entity reference properties (skip collections)
            foreach (var refProp in targetItem
                     .GetNavigationProperties(false)
                     .OfReferenceType()
                     .Where(np => np.Property.CanWrite))
            {
                ITrackable targetValue = refProp.EntityReference;

                // Skip non-null trackable
                if (targetValue != null)
                {
                    continue;
                }

                ITrackable sourceValue = sourceItem.GetEntityReferenceProperty(refProp.Property).EntityReference;

                // Continue if source is null
                if (sourceValue == null)
                {
                    continue;
                }

                // Deferred 'SetValue'
                actions.Add(() => refProp.Property.SetValue(targetItem, sourceValue, null));
            }

            // Nothing to do?
            if (!actions.Any())
            {
                return;
            }

            // Turn off change-tracking
            bool isTracking = changeTracker.Tracking;
            changeTracker.Tracking = false;

            // Set target item prop value
            foreach (var action in actions)
            {
                action();
            }

            // Reset change-tracking
            changeTracker.Tracking = isTracking;
        }