private bool HasDownstreamChanges(ITrackingCollection refChangeTracker, ITrackable trackableRef, ITrackable item, EntityReferenceProperty refProp, bool hasDownstreamChanges) { // Get downstream changes IEnumerable <ITrackable> refPropItems = refChangeTracker.Cast <ITrackable>(); IEnumerable <ITrackable> refPropChanges = GetChanges(refPropItems); // Set flag for downstream changes bool hasLocalDownstreamChanges = refPropChanges.Any(t => t.TrackingState != TrackingState.Deleted) || trackableRef.TrackingState == TrackingState.Added || trackableRef.TrackingState == TrackingState.Modified; // Set ref prop to null if unchanged or deleted if (!hasLocalDownstreamChanges && (trackableRef.TrackingState == TrackingState.Unchanged || trackableRef.TrackingState == TrackingState.Deleted)) { EntityInfo(item).RefNavPropUnchanged.Add(refProp.Property); return(hasDownstreamChanges); } // prevent overwrite of hasDownstreamChanges when return from recursion hasDownstreamChanges |= hasLocalDownstreamChanges; return(hasDownstreamChanges); }
/// <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); } } } }
/// <summary> /// Get entities that have been added, modified or deleted, including trackable /// reference and child entities. /// </summary> /// <param name="items">Collection of ITrackable objects</param> /// <returns>Collection containing only added, modified or deleted entities</returns> private IEnumerable <ITrackable> GetChanges(IEnumerable <ITrackable> items) { // Prevent endless recursion by collection if (!visitationHelper.TryVisit(items)) { yield break; } // Prevent endless recursion by item items = items.Where(i => visitationHelper.TryVisit(i)).ToList(); // Iterate items in change-tracking collection foreach (ITrackable item in items) { // Downstream changes flag bool hasDownstreamChanges = false; // Iterate entity properties foreach (var navProp in item.GetNavigationProperties()) { // Process 1-1 and M-1 properties foreach (var refProp in navProp.AsReferenceProperty()) { ITrackable trackableRef = refProp.EntityReference; // if already visited and unchanged, set to null if (visitationHelper.IsVisited(trackableRef)) { if ((trackableRef.TrackingState == TrackingState.Unchanged || trackableRef.TrackingState == TrackingState.Deleted)) { EntityInfo(item).RefNavPropUnchanged.Add(refProp.Property); } continue; } // Get changed ref prop ITrackingCollection refChangeTracker = item.GetRefPropertyChangeTracker(refProp.Property.Name); if (refChangeTracker != null) { // Get downstream changes IEnumerable <ITrackable> refPropItems = refChangeTracker.Cast <ITrackable>(); IEnumerable <ITrackable> refPropChanges = GetChanges(refPropItems); // Set flag for downstream changes bool hasLocalDownstreamChanges = refPropChanges.Any(t => t.TrackingState != TrackingState.Deleted) || trackableRef.TrackingState == TrackingState.Added || trackableRef.TrackingState == TrackingState.Modified; // Set ref prop to null if unchanged or deleted if (!hasLocalDownstreamChanges && (trackableRef.TrackingState == TrackingState.Unchanged || trackableRef.TrackingState == TrackingState.Deleted)) { EntityInfo(item).RefNavPropUnchanged.Add(refProp.Property); continue; } // prevent overwrite of hasDownstreamChanges when return from recursion hasDownstreamChanges |= hasLocalDownstreamChanges; } } // Process 1-M and M-M properties foreach (var colProp in navProp.AsCollectionProperty <IList>()) { // Get changes on child collection var trackingItems = colProp.EntityCollection; if (trackingItems.Count > 0) { // Continue recursion if trackable hasn't been visited if (!visitationHelper.IsVisited(trackingItems)) { // Get changes on child collection var trackingCollChanges = new HashSet <ITrackable>( GetChanges(trackingItems.Cast <ITrackable>()), ObjectReferenceEqualityComparer <ITrackable> .Default); // Set flag for downstream changes hasDownstreamChanges |= trackingCollChanges.Any(); // Memorize only changed items of collection EntityInfo(item).ColNavPropChangedEntities[colProp.Property] = trackingCollChanges; } } } } // Return item if it has changes if (hasDownstreamChanges || item.TrackingState != TrackingState.Unchanged) { yield return(item); } } }
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(); }