Beispiel #1
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);
        }
Beispiel #2
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);
        }
Beispiel #3
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;
                    }
                }
            }
        }
Beispiel #4
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);
                    }
                }
            }
        }
        public void Child_Set_Modified_Props_Should_Not_Set_Parent_Siblings()
        {
            // NOTE: SetModifiedProperties should only set ModifiedProperties on
            // downstream 1-M entities.

            // Arrange
            const string propName = "UnitPrice";
            var          nw       = new MockNorthwind();
            var          order    = nw.Orders[0];
            var          customer = order.Customer;
            var          detail1  = order.OrderDetails[0];
            var          detail2  = order.OrderDetails[1];
            var          detail3  = order.OrderDetails[2];

            detail1.Order = order;
            detail2.Order = order;
            detail3.Order = order;

            var visitationHelper = new ObjectVisitationHelper(null);

            visitationHelper.TryVisit(order.OrderDetails);

            // Act
            detail1.SetModifiedProperties(new List <string> {
                propName
            }, visitationHelper.Clone());

            // Assert
            Assert.Null(order.ModifiedProperties);
            Assert.Null(customer.ModifiedProperties);
            Assert.Null(detail1.ModifiedProperties);
            Assert.Null(detail2.ModifiedProperties);
            Assert.Null(detail3.ModifiedProperties);
        }
Beispiel #6
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);
                }
            }
        }
        /// <summary>
        /// Insert item at specified index.
        /// </summary>
        /// <param name="index">Zero-based index at which item should be inserted</param>
        /// <param name="item">Item to insert</param>
        protected override void InsertItem(int index, TEntity item)
        {
            if (Tracking)
            {
                // Set entity identifier
                if (item is IIdentifiable)
                {
                    ((IIdentifiable)item).SetEntityIdentifier();
                }

                // Listen for property changes
                item.PropertyChanged += OnPropertyChanged;

                // Exclude this collection and Parent entity (used in M-M relationships)
                // from recusive algorithms: SetTracking and SetState.
                var visitationHelper = new ObjectVisitationHelper(Parent);
                visitationHelper.TryVisit(this);

                // Enable tracking on trackable properties
                item.SetTracking(Tracking, visitationHelper.Clone());

                // Mark item and trackable collection properties
                item.SetState(TrackingState.Added, visitationHelper.Clone());

                // Fire EntityChanged event
                if (EntityChanged != null)
                {
                    EntityChanged(this, EventArgs.Empty);
                }
            }
            base.InsertItem(index, item);
        }
        /// <summary>
        /// Recursively remove items marked as deleted.
        /// </summary>
        /// <param name="changeTracker">Change-tracking collection</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        public static void RemoveRestoredDeletes(this ITrackingCollection changeTracker, ObjectVisitationHelper visitationHelper = null)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

            // Iterate items in change-tracking collection
            var items = changeTracker as IList;

            if (items == null)
            {
                return;
            }
            var count = items.Count;

            for (int i = count - 1; i > -1; i--)
            {
                // Get trackable item
                var item = items[i] as ITrackable;
                if (item == null)
                {
                    continue;
                }

                // Prevent endless recursion
                if (visitationHelper.TryVisit(item))
                {
                    // Iterate entity properties
                    foreach (var navProp in item.GetNavigationProperties())
                    {
                        // Process 1-1 and M-1 properties
                        foreach (var refProp in navProp.AsReferenceProperty())
                        {
                            // Get changed ref prop
                            ITrackingCollection refChangeTracker = item.GetRefPropertyChangeTracker(refProp.Property.Name);

                            // Remove deletes on rep prop
                            if (refChangeTracker != null)
                            {
                                refChangeTracker.RemoveRestoredDeletes(visitationHelper);
                            }
                        }

                        // Process 1-M and M-M properties
                        foreach (var colProp in navProp.AsCollectionProperty <ITrackingCollection>())
                        {
                            colProp.EntityCollection.RemoveRestoredDeletes(visitationHelper);
                        }
                    }
                }

                // Remove item if marked as deleted
                if (item.TrackingState == TrackingState.Deleted)
                {
                    var isTracking = changeTracker.Tracking;
                    changeTracker.InternalTracking = false;
                    items.RemoveAt(i);
                    changeTracker.InternalTracking = isTracking;
                }
            }
        }
        /// <summary>
        /// See if there are changes in an object graph.
        /// </summary>
        /// <param name="item">Trackable object</param>
        /// <returns>True if there are changes in the object graph</returns>
        public static bool HasChanges(this ITrackable item)
        {
            var  visitationHelper = new ObjectVisitationHelper();
            bool hasChanges       = item.HasChanges(visitationHelper,
                                                    new Dictionary <ITrackable, bool>(ObjectReferenceEqualityComparer <ITrackable> .Default));

            return(hasChanges);
        }
Beispiel #10
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;
        }
Beispiel #11
0
        private static ObjectVisitationHelper CreateVisitationHelperWithIdMatching(DbContext dbContext)
        {
            var visitationHelper = new ObjectVisitationHelper(new IdMatcher()
            {
                DbContext = dbContext
            });

            return(visitationHelper);
        }
Beispiel #12
0
        /// <summary>
        /// Restore items marked as deleted.
        /// </summary>
        /// <param name="changeTracker">Change-tracking collection</param>
        /// <param name="visitationHelper">Circular reference checking helper</param>
        public static void RestoreDeletes(this ITrackingCollection changeTracker, ObjectVisitationHelper visitationHelper = null)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

            // Get cached deletes
            var removedDeletes = changeTracker.GetChanges(true).Cast <ITrackable>().ToList();

            // Restore deleted items
            if (removedDeletes.Any())
            {
                var isTracking = changeTracker.Tracking;
                changeTracker.Tracking = false;
                foreach (var delete in removedDeletes)
                {
                    var items = changeTracker as IList;
                    if (items != null && !items.Contains(delete))
                    {
                        items.Add(delete);
                    }
                }
                changeTracker.Tracking = isTracking;
            }

            foreach (var item in changeTracker.Cast <ITrackable>())
            {
                // Prevent endless recursion
                if (!visitationHelper.TryVisit(item))
                {
                    continue;
                }

                // Iterate entity properties
                foreach (var navProp in item.GetNavigationProperties())
                {
                    // Process 1-1 and M-1 properties
                    foreach (var refProp in navProp.AsReferenceProperty())
                    {
                        // Get changed ref prop
                        ITrackingCollection refChangeTracker = item.GetRefPropertyChangeTracker(refProp.Property.Name);

                        // Restore deletes on rep prop
                        if (refChangeTracker != null)
                        {
                            refChangeTracker.RestoreDeletes(visitationHelper);
                        }
                    }

                    // Process 1-M and M-M properties
                    foreach (var colProp in navProp.AsCollectionProperty <ITrackingCollection>())
                    {
                        colProp.EntityCollection.RestoreDeletes(visitationHelper);
                    }
                }
            }
        }
        public void Reset(IValidatableEntity validatableEntity = null)
        {
            _objectVisitationHelper.Reset();

            if (validatableEntity != null)
            {
                ObjectVisitationHelper.TryVisit(validatableEntity);
            }

            SetConfiguration(null);
        }
Beispiel #14
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;
        }
        public static string GetValidationResultsText(IValidatableEntity validatableEntity, IEnumerable <IValidationResult> customResults = null)
        {
            var returnValue           = new StringBuilder();
            var validationResultIndex = 0;

            customResults?.Concat(GetValidationResults(validatableEntity, ObjectVisitationHelper.CreateInstance())).Each(vr =>
            {
                returnValue.AppendFormat(ValidationResultFormat, vr.Description, validationResultIndex + 1, Environment.NewLine);
                validationResultIndex++;
            });

            return(returnValue.ToString());
        }
        /// <summary>
        /// For internal use.
        /// Turn change-tracking on and off with proper circular reference checking.
        /// </summary>
        public void SetTracking(bool value, ObjectVisitationHelper visitationHelper)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

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

            // Get notified when an item in the collection has changed
            foreach (TEntity item in this)
            {
                // Prevent endless recursion
                if (item == null)
                {
                    continue;
                }
                if (!visitationHelper.TryVisit(item))
                {
                    continue;
                }

                // Property change notification
                if (value)
                {
                    item.PropertyChanged += OnPropertyChanged;
                }
                else
                {
                    item.PropertyChanged -= OnPropertyChanged;
                }

                // Enable tracking on trackable collection properties
                item.SetTracking(value, visitationHelper);

                try
                {
                    if (item is IIdentifiable)
                    {
                        ((IIdentifiable)item).SetEntityIdentifier();
                    }
                }
                catch (Exception ex)
                {
                    // to catch stackoverflow
                }
                // Set entity identifier
            }
            _tracking = value;
        }
Beispiel #17
0
        /// <summary>
        /// Remove item at specified index.
        /// </summary>
        /// <param name="index">Zero-based index at which item should be removed</param>
        protected override void RemoveItem(int index)
        {
            // Mark existing item as deleted, stop listening for property changes,
            // then fire EntityChanged event, and cache item.
            if (Tracking)
            {
                // Get item by index
                TEntity item = Items[index];

                // Exclude this collection and Parent entity (used in M-M relationships)
                // from recusive algorithms: SetModifiedProperties, SetTracking and SetState.
                var visitationHelper = new ObjectVisitationHelper(Parent);
                visitationHelper.TryVisit(this);

                // Remove modified properties
                item.ModifiedProperties = null;
                item.SetModifiedProperties(null, visitationHelper.Clone());

                // Stop listening for property changes
                item.PropertyChanged -= OnPropertyChanged;

                // Disable tracking on trackable properties
                item.SetTracking(false, visitationHelper.Clone(), true);

                // Mark item and trackable collection properties
                bool manyToManyAdded = Parent != null && item.TrackingState == TrackingState.Added;
                item.SetState(TrackingState.Deleted, visitationHelper.Clone());

                // Fire EntityChanged event
                if (EntityChanged != null)
                {
                    EntityChanged(this, EventArgs.Empty);
                }

                // Cache deleted item if not added or already cached
                if (item.TrackingState != TrackingState.Added &&
                    !manyToManyAdded &&
                    !_deletedEntities.Contains(item))
                {
                    _deletedEntities.Add(item);
                }
            }
            base.RemoveItem(index);
        }
Beispiel #18
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);
                }
            }
        }
Beispiel #19
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;
                }
            }
        }
Beispiel #20
0
        // This method assumes entity and contract have same shape
        protected void SetContractFields(object entity, object contract, ObjectVisitationHelper visitationHelper)
        {
            var isEntitySingular   = typeof(IBedrockEntity).GetTypeInfo().IsAssignableFrom(entity.GetType());
            var isEntityCollection = typeof(IEnumerable <IBedrockEntity>).GetTypeInfo().IsAssignableFrom(entity.GetType());

            var isContractSingular   = typeof(IBedrockEntity).GetTypeInfo().IsAssignableFrom(contract.GetType());
            var isContractCollection = typeof(IEnumerable <IBedrockEntity>).GetTypeInfo().IsAssignableFrom(contract.GetType());

            if (isEntitySingular && isContractSingular)
            {
                SetContractFieldsInternal(entity, contract, visitationHelper);
            }
            else if (isEntityCollection && isContractCollection)
            {
                var entities     = entity as IEnumerable <IBedrockEntity>;
                var contracts    = contract as IEnumerable <IBedrockEntity>;
                var contractList = new List <IBedrockEntity>(contracts);

                entities.Each((e, i) => SetContractFieldsInternal(e, contractList[i], visitationHelper));
            }
        }
Beispiel #21
0
        /// <summary>
        /// For internal use.
        /// Turn change-tracking on and off with proper circular reference checking.
        /// </summary>
        public void SetTracking(bool value, ObjectVisitationHelper visitationHelper, bool oneToManyOnly, EventHandler entityChanged = null)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

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

            // Get notified when an item in the collection has changed
            foreach (TEntity item in this)
            {
                // Prevent endless recursion
                if (!visitationHelper.TryVisit(item))
                {
                    continue;
                }

                // Property change notification
                if (value)
                {
                    item.PropertyChanged += OnPropertyChanged;
                }
                else
                {
                    item.PropertyChanged -= OnPropertyChanged;
                }

                // Enable tracking on trackable collection properties
                item.SetTracking(value, visitationHelper, oneToManyOnly, entityChanged);

                // Set entity identifier
                if (item is IIdentifiable)
                {
                    ((IIdentifiable)item).SetEntityIdentifier();
                }
            }
            _tracking = value;
        }
Beispiel #22
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);
        }
Beispiel #23
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 void MergeChanges(this ITrackingCollection originalChangeTracker,
                                         IEnumerable <ITrackable> updatedItems, ObjectVisitationHelper visitationHelper, bool isTrackableRef = false)
        {
            ObjectVisitationHelper.EnsureCreated(ref visitationHelper);

            // Process each updated item
            foreach (var updatedItem in updatedItems)
            {
                // Prevent endless recursion
                if (!visitationHelper.TryVisit(updatedItem))
                {
                    continue;
                }

                // Get matching orig item
                var origItem = originalChangeTracker.Cast <ITrackable>()
                               .GetEquatableItem(updatedItem, isTrackableRef);
                if (origItem == null)
                {
                    continue;
                }

                // Back fill entity identity on trackable ref
                if (isTrackableRef)
                {
                    var origItemIdentifiable = (IIdentifiable)origItem;
                    origItemIdentifiable.SetEntityIdentifier();
                    ((IIdentifiable)updatedItem).SetEntityIdentifier(origItemIdentifiable);
                }

                // Iterate entity properties
                foreach (var navProp in updatedItem.GetNavigationProperties())
                {
                    // Set to 1-1 and M-1 properties
                    foreach (var refProp in navProp.AsReferenceProperty())
                    {
                        ITrackingCollection refPropChangeTracker = origItem.GetRefPropertyChangeTracker(navProp.Property.Name);
                        if (refPropChangeTracker != null)
                        {
                            refPropChangeTracker.MergeChanges(new[] { refProp.EntityReference }, visitationHelper, true);
                        }
                    }

                    // Set 1-M and M-M properties
                    foreach (var colProp in navProp.AsCollectionProperty())
                    {
                        var origItemsChangeTracker = origItem.GetEntityCollectionProperty <ITrackingCollection>(navProp.Property).EntityCollection;
                        if (origItemsChangeTracker != null)
                        {
                            // Merge changes into trackable children
                            origItemsChangeTracker.MergeChanges(colProp.EntityCollection, visitationHelper);
                        }
                    }
                }

                // Set properties on orig item
                origItem.SetEntityProperties(updatedItem, originalChangeTracker);

                // Accept changes
                origItem.AcceptChanges();
            }

            // Remove cached deletes
            originalChangeTracker.RemoveCachedDeletes();
        }
        public static IValidationResult[] GetValidationResults(IValidatableEntity validatableEntity, ObjectVisitationHelper visitationHelper)
        {
            var type         = validatableEntity.GetType();
            var nonValidated = SharedUtility.ApplicationContext.DomainGraphCache.GetPropertiesAttributes <NonValidated>(type);
            var brokenRules  = new List <IValidationResult>(validatableEntity.FieldValidationManager.Results);
            var children     = new List <PropertyInfo>(SharedUtility.ApplicationContext.DomainGraphCache.GetPropertiesOfType <IValidatableEntity>(type).Where(x => !nonValidated.ContainsKey(x)));

            children.AddRange(new List <PropertyInfo>(SharedUtility.ApplicationContext.DomainGraphCache.GetPropertiesOfType <IEnumerable <IValidatableEntity> >(type).Where(x => !nonValidated.ContainsKey(x))));

            visitationHelper.TryVisit(validatableEntity);

            foreach (var child in children)
            {
                var childValue = child.GetValue(validatableEntity, null);

                if (childValue is IEnumerable <IValidatableEntity> )
                {
                    var values = child.GetValue(validatableEntity, null) as IEnumerable <IValidatableEntity>;

                    foreach (var value in values)
                    {
                        if (value != null)
                        {
                            if (!visitationHelper.TryVisit(value))
                            {
                                continue;
                            }

                            brokenRules.AddRange(GetValidationResults(value, visitationHelper));
                        }
                    }
                }
                else
                {
                    var value = childValue as IValidatableEntity;

                    if (value != null)
                    {
                        if (!visitationHelper.TryVisit(value))
                        {
                            continue;
                        }

                        brokenRules.AddRange(GetValidationResults(value, visitationHelper));
                    }
                }
            }

            return(brokenRules.ToArray());
        }
Beispiel #26
0
        private static async Task LoadRelatedEntitiesAsync(this DbContext context,
                                                           IEnumerable <ITrackable> items, ITrackable parent, ObjectVisitationHelper visitationHelper,
                                                           CancellationToken cancellationToken, bool loadAll)
        {
            // Return if no items
            if (items == null)
            {
                return;
            }

            // Get selected items
            var selectedItems = loadAll ? items
                : items.Where(t => t.TrackingState == TrackingState.Added ||
                              (parent != null && parent.TrackingState == TrackingState.Added));
            var entities = selectedItems.Cast <object>().Where(i => !visitationHelper.IsVisited(i));

            // Collection 'items' can contain entities of different types (due to inheritance)
            // We collect a superset of all properties of all items of type ITrackable
            var allProps = (from entity in entities
                            from prop in entity.GetType().GetProperties()
                            where typeof(ITrackable).IsAssignableFrom(prop.PropertyType)
                            select prop).Distinct();

            // Populate related entities on all items
            foreach (var prop in allProps)
            {
                // Get related entities
                string propertyName = prop.Name;
                Type   propertyType = prop.PropertyType;
                IEnumerable <object> relatedEntities = await context.GetRelatedEntitiesAsync(entities,
                                                                                             prop.DeclaringType, propertyName, propertyType, cancellationToken).ConfigureAwait(false);

                // Continue if there are no related entities
                if (!relatedEntities.Any())
                {
                    continue;
                }

                // ObjectVisitationHelper serves here as an identity cache
                relatedEntities = relatedEntities.Select(e => visitationHelper.FindVisited(e) ?? e);

                // Set related entities
                context.SetRelatedEntities(entities, relatedEntities, prop,
                                           prop.DeclaringType, propertyName, propertyType);
            }

            // Recursively populate related entities on ref and child properties
            foreach (var item in items)
            {
                // Avoid endless recursion
                if (!visitationHelper.TryVisit(item))
                {
                    continue;
                }

                bool loadAllRelated = loadAll ||
                                      item.TrackingState == TrackingState.Added ||
                                      (parent != null && parent.TrackingState == TrackingState.Added);
                await context.LoadRelatedEntitiesOnPropertiesAsync(item, visitationHelper,
                                                                   cancellationToken, loadAllRelated).ConfigureAwait(false);
            }
        }
Beispiel #27
0
        private static void ApplyChanges(this DbContext context,
                                         ITrackable item, ITrackable parent, ObjectVisitationHelper visitationHelper,
                                         string propertyName, TrackingState?state = null)
        {
            // Prevent endless recursion
            // if (!Exists(context, item)) return;
            if (!visitationHelper.TryVisit(item))
            {
                return;
            }

            // Check for null args
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            // If M-M child, set relationship for added or deleted items
            if (parent != null && propertyName != null &&
                (context.IsRelatedProperty(parent.GetType(),
                                           propertyName, RelationshipType.ManyToMany)))
            {
                // If parent is added set tracking state to match
                var trackingState = item.TrackingState;
                if (parent.TrackingState == TrackingState.Added)
                {
                    trackingState = parent.TrackingState;
                }

                // If tracking state is added set entity to unchanged,
                // then add or delete relation.
                if (trackingState == TrackingState.Added ||
                    trackingState == TrackingState.Deleted)
                {
                    context.Entry(item).State = item.TrackingState == TrackingState.Modified
                        ? EntityState.Modified
                        : EntityState.Unchanged;
                    context.SetRelationshipState(item, parent, propertyName,
                                                 trackingState.ToEntityState());
                }
                else
                {
                    // Set entity state for modified or unchanged
                    if (item.TrackingState == TrackingState.Modified)
                    {
                        context.Entry(item).State = EntityState.Modified;
                    }
                    else if (item.TrackingState == TrackingState.Unchanged)
                    {
                        context.Entry(item).State = EntityState.Unchanged;
                    }
                }

                // Set state for child collections
                context.ApplyChangesOnProperties(item, visitationHelper);
                return;
            }

            // Exit if parent is added or deleted,
            // and it's not a M-1 relation
            if (parent != null &&
                (parent.TrackingState == TrackingState.Added ||
                 parent.TrackingState == TrackingState.Deleted) &&
                !context.IsRelatedProperty(parent.GetType(),
                                           propertyName, RelationshipType.ManyToOne))
            {
                context.ApplyChangesOnProperties(item, visitationHelper);
                return;
            }


            // If it is a M-1 relation and item state is deleted,
            // set to unchanged and exit
            if (parent != null &&
                (context.IsRelatedProperty(parent.GetType(),
                                           propertyName, RelationshipType.ManyToOne) &&
                 item.TrackingState == TrackingState.Deleted))
            {
                try
                {
                    context.Entry(item).State = EntityState.Unchanged;
                }
                catch (InvalidOperationException invalidOpEx)
                {
                    throw new InvalidOperationException(Constants.ExceptionMessages.DeletedWithAddedChildren, invalidOpEx);
                }
                return;
            }

            // Set state to Added on parent only
            if (item.TrackingState == TrackingState.Added &&
                (state == null || state == TrackingState.Added))
            {
                context.Entry(item).State = EntityState.Added;
                context.ApplyChangesOnProperties(item, visitationHelper);
                return;
            }

            // Set state to Deleted on children and parent
            if (item.TrackingState == TrackingState.Deleted &&
                (state == null || state == TrackingState.Deleted))
            {
                context.SetChanges(item, EntityState.Unchanged, visitationHelper.Clone()); // Clone to avoid interference
                context.SetChanges(item, EntityState.Deleted, visitationHelper);
                return;
            }

            // Set entity state
            if (state == null ||
                state == TrackingState.Unchanged ||
                state == TrackingState.Modified ||
                (state == TrackingState.Added &&
                 item.TrackingState != TrackingState.Deleted))
            {
                // Set added state for reference or child properties
                context.ApplyChangesOnProperties(item, visitationHelper.Clone(), TrackingState.Added); // Clone to avoid interference

                // Set modified properties
                if (item.TrackingState == TrackingState.Modified &&
                    (state == null || state == TrackingState.Modified) &&
                    item.ModifiedProperties != null &&
                    item.ModifiedProperties.Count > 0)
                {
                    // Mark modified properties
                    context.Entry(item).State = item.TrackingState.ToEntityState(); //EntityState.Unchanged;
                    foreach (var property in item.ModifiedProperties)
                    {
                        context.Entry(item).Property(property).IsModified = true;
                    }
                }
                else
                {
                    try
                    {
                        context.Entry(item).State = item.TrackingState.ToEntityState();
                    }
                    catch (Exception)
                    {
                        if (item.TrackingState != TrackingState.Unchanged)
                        {
                            throw;
                        }
                    }
                }

                // Set other state for reference or child properties
                context.ApplyChangesOnProperties(item, visitationHelper.Clone(), TrackingState.Unchanged); // Clone to avoid interference
                context.ApplyChangesOnProperties(item, visitationHelper.Clone(), TrackingState.Modified);  // Clone to avoid interference
                context.ApplyChangesOnProperties(item, visitationHelper, TrackingState.Deleted);
            }
        }
Beispiel #28
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);
                }
            }
        }
Beispiel #29
0
        private static void ApplyChangesOnCollectionProperties(TrackingState stateFilter, bool includeState,
                                                               EntityNavigationProperty navProp, EntityCollectionProperty <IList> colProp,
                                                               DbContext context, ITrackable item, ObjectVisitationHelper visitationHelper, TrackingState?state = null)
        {
            // Apply changes to 1-M and M-M properties filtering by tracking state
            var count = colProp.EntityCollection.Count;

            for (int i = count - 1; i > -1; i--)
            {
                var trackableChild = colProp.EntityCollection[i] as ITrackable;
                if (trackableChild != null)
                {
                    bool condition = includeState
                        ? trackableChild.TrackingState == stateFilter
                        : trackableChild.TrackingState != stateFilter;
                    if (condition)
                    {
                        context.ApplyChanges(trackableChild, item, visitationHelper, navProp.Property.Name, state);
                    }
                }
            }
        }
Beispiel #30
0
        // TODO:  Ensure entity and contract have same shape or do nothing; currently will fail otherwise
        private void SetContractFieldsInternal(object entity, object contract, ObjectVisitationHelper visitationHelper)
        {
            if (!visitationHelper.TryVisit(entity))
            {
                return;
            }

            SetFields(entity, contract);

            foreach (var propertyInfo in entity.GetType().GetTypeInfo().GetProperties())
            {
                if (propertyInfo.GetIndexParameters().Length > 0)
                {
                    continue;
                }

                var propertyValueEntity = propertyInfo.GetValue(entity, null);

                if (propertyValueEntity == null)
                {
                    continue;
                }

                var isEntitySingular   = typeof(IBedrockEntity).GetTypeInfo().IsAssignableFrom(propertyInfo.PropertyType);
                var isEntityCollection = typeof(IEnumerable <IBedrockEntity>).GetTypeInfo().IsAssignableFrom(propertyInfo.PropertyType);

                if (isEntitySingular)
                {
                    var contractPropertyInfo = contract.GetType().GetTypeInfo().GetProperties().FirstOrDefault(p => p.Name == propertyInfo.Name);

                    if (contractPropertyInfo != null)
                    {
                        var propertyValueContract = contractPropertyInfo.GetValue(contract, null);

                        if (propertyValueContract != null)
                        {
                            SetContractFieldsInternal(propertyValueEntity, propertyValueContract, visitationHelper);
                        }
                    }
                }
                else if (isEntityCollection)
                {
                    var entities                   = propertyValueEntity as IEnumerable <IBedrockEntity>;
                    var contractPropertyInfo       = contract.GetType().GetTypeInfo().GetProperties().FirstOrDefault(p => p.Name == propertyInfo.Name);
                    var contractPropertyCollection = contractPropertyInfo != null?contractPropertyInfo.GetValue(contract, null) as IEnumerable <IBedrockEntity> : null;

                    if (contractPropertyCollection == null || (entities.Count() != contractPropertyCollection.Count()))
                    {
                        continue;
                    }

                    entities.Each((e, i) =>
                    {
                        var propertyValueContract = contractPropertyCollection.ElementAt(i);

                        if (propertyValueContract != null)
                        {
                            SetContractFieldsInternal(e, propertyValueContract, visitationHelper);
                        }
                    });
                }
            }
        }