// 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); }
/// <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); } } } }
// 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); }
// 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; } } } }
/// <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); } } }
GetEntityReferenceProperty <TEntity>(this ITrackable entity, PropertyInfo property) where TEntity : class, ITrackable { return(entity.GetNavigationProperties(false) .Where(np => np.Property == property) .OfReferenceType <TEntity>() .Single()); }
GetEntityCollectionProperty <TEntityCollection>(this ITrackable entity, PropertyInfo property) where TEntityCollection : class { return(entity.GetNavigationProperties(false) .Where(np => np.Property == property) .OfCollectionType <TEntityCollection>() .Single()); }
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; } }
/// <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; }
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; }
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; } }
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); } } }
/// <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; } } }
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); } } }
// 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; }
// 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); }
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; }