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); }
public ITrackingCollection <IRemoteRepositoryModel> GetRepositories(ITrackingCollection <IRemoteRepositoryModel> collection) { var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}", CacheIndex.RepoPrefix, user.Login)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetRepositories() .Select(RepositoryCacheItem.Create), item => { // this could blow up due to the collection being disposed somewhere else try { collection.RemoveItem(Create(item)); } catch (ObjectDisposedException) { } }, TimeSpan.FromMinutes(5), TimeSpan.FromDays(1)) ) .Select(Create) ); collection.Listen(source); return(collection); }
/// <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> /// Determine if an entity is a child of a many-to-many change-tracking collection property. /// </summary> /// <param name="changeTracker">Change-tracking collection</param> /// <returns></returns> public static bool IsManyToManyChildCollection(ITrackingCollection changeTracker) { // Entity is a M-M child if change-tracking collection has a non-null Parent property bool isManyToManyChild = changeTracker.Parent != null; return(isManyToManyChild); }
public void UpdateStateParametersTest() { var target = new object(); bool isInvoked = false; var oldState = EntityState.Unchanged; var newState = EntityState.Unchanged; _transitionManager.ChangeState = (item, state, entityState) => { isInvoked = true; state.ShouldEqual(oldState); entityState.ShouldEqual(newState); target.ShouldEqual(item); return(entityState); }; ITrackingCollection collection = Create(_transitionManager); oldState = EntityState.Detached; newState = EntityState.Added; collection.UpdateState(target, newState); isInvoked.ShouldBeTrue(); isInvoked = false; oldState = EntityState.Added; newState = EntityState.Modified; collection.UpdateState(target, newState); }
/// <summary> /// Gets a collection of Pull Requests. If you want to refresh existing data, pass a collection in /// </summary> /// <param name="repo"></param> /// <param name="collection"></param> /// <returns></returns> public ITrackingCollection <IPullRequestModel> GetPullRequests(ILocalRepositoryModel repo, ITrackingCollection <IPullRequestModel> collection) { // Since the api to list pull requests returns all the data for each pr, cache each pr in its own entry // and also cache an index that contains all the keys for each pr. This way we can fetch prs in bulk // but also individually without duplicating information. We store things in a custom observable collection // that checks whether an item is being updated (coming from the live stream after being retrieved from cache) // and replaces it instead of appending, so items get refreshed in-place as they come in. var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) .Select(PullRequestCacheItem.Create), item => { // this could blow up due to the collection being disposed somewhere else try { collection.RemoveItem(Create(item)); } catch (ObjectDisposedException) { } }, TimeSpan.Zero, TimeSpan.FromDays(7)) ) .Select(Create) ); collection.Listen(source); return(collection); }
/// <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); } } } }
/// <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; } } }
// Fired when an item in the collection has changed private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (Tracking) { var entity = sender as ITrackable; if (entity == null) { return; } // Enable tracking on reference properties #if SILVERLIGHT || NET40 var prop = entity.GetType().GetProperty(e.PropertyName); if (prop != null && typeof(ITrackable).IsAssignableFrom(prop.PropertyType)) #else var prop = entity.GetType().GetTypeInfo().GetDeclaredProperty(e.PropertyName); if (prop != null && typeof(ITrackable).GetTypeInfo().IsAssignableFrom(prop.PropertyType.GetTypeInfo())) #endif { ITrackingCollection refPropChangeTracker = entity.GetRefPropertyChangeTracker(e.PropertyName); if (refPropChangeTracker != null) { refPropChangeTracker.Tracking = Tracking; } return; } if (e.PropertyName != Constants.TrackingProperties.TrackingState && e.PropertyName != Constants.TrackingProperties.ModifiedProperties && !ExcludedProperties.Contains(e.PropertyName)) { // If unchanged mark item as modified, fire EntityChanged event if (entity.TrackingState == TrackingState.Unchanged) { entity.TrackingState = TrackingState.Modified; if (EntityChanged != null) { EntityChanged(this, EventArgs.Empty); } } // Add prop to modified props, and fire EntityChanged event if (entity.TrackingState == TrackingState.Unchanged || entity.TrackingState == TrackingState.Modified) { if (entity.ModifiedProperties == null) { entity.ModifiedProperties = new List <string>(); } if (!entity.ModifiedProperties.Contains(e.PropertyName)) { entity.ModifiedProperties.Add(e.PropertyName); } } } } }
/// <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); } } } }
// Fired when an item in the collection has changed private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (Tracking) { var entity = sender as ITrackable; if (entity == null) { return; } // Enable tracking on reference properties var prop = PortableReflectionHelper.Instance.GetProperty(entity.GetType(), e.PropertyName); if (prop != null && PortableReflectionHelper.Instance.IsAssignable(typeof(ITrackable), prop.PropertyType)) { ITrackingCollection refPropChangeTracker = entity.GetRefPropertyChangeTracker(e.PropertyName); if (refPropChangeTracker != null) { refPropChangeTracker.Tracking = Tracking; } return; } if (e.PropertyName != Constants.TrackingProperties.TrackingState && e.PropertyName != Constants.TrackingProperties.ModifiedProperties && !ExcludedProperties.Contains(e.PropertyName)) { // If unchanged mark item as modified, fire EntityChanged event if (entity.TrackingState == TrackingState.Unchanged) { entity.TrackingState = TrackingState.Modified; if (EntityChanged != null) { EntityChanged(this, EventArgs.Empty); } } // Add prop to modified props, and fire EntityChanged event if (entity.TrackingState == TrackingState.Unchanged || entity.TrackingState == TrackingState.Modified) { if (entity.ModifiedProperties == null) { entity.ModifiedProperties = new HashSet <string>(); } entity.ModifiedProperties.Add(e.PropertyName); } } } }
private static void SetHandler(this ITrackingCollection trackableCollection, bool handle, EventHandler entityChanged) { if (entityChanged == null) { return; } if (handle) { trackableCollection.EntityChanged += entityChanged; } else { trackableCollection.EntityChanged -= entityChanged; } }
public ProductWorkspaceViewModel(IRepository repository, IMessagePresenter messagePresenter, IToastPresenter toastPresenter) { Should.NotBeNull(repository, "repository"); Should.NotBeNull(messagePresenter, "messagePresenter"); Should.NotBeNull(toastPresenter, "toastPresenter"); _repository = repository; _messagePresenter = messagePresenter; _toastPresenter = toastPresenter; _trackingCollection = new TrackingCollection(new CompositeEqualityComparer().AddComparer(ProductModel.KeyComparer)); SaveChangesCommand = RelayCommandBase.FromAsyncHandler(SaveChanges, CanSaveChanges, this); AddProductCommand = RelayCommandBase.FromAsyncHandler(AddProduct); EditProductCommand = RelayCommandBase.FromAsyncHandler<ProductModel>(EditProduct, CanEditProduct, this); RemoveProductCommand = RelayCommandBase.FromAsyncHandler<ProductModel>(RemoveProduct, CanRemoveProduct, this); DisplayName = UiResources.ProductWorkspaceName; }
public ProductWorkspaceViewModel(IRepository repository, IMessagePresenter messagePresenter, IToastPresenter toastPresenter) { Should.NotBeNull(repository, "repository"); Should.NotBeNull(messagePresenter, "messagePresenter"); Should.NotBeNull(toastPresenter, "toastPresenter"); _repository = repository; _messagePresenter = messagePresenter; _toastPresenter = toastPresenter; _trackingCollection = new TrackingCollection(); SaveChangesCommand = new RelayCommand(SaveChanges, CanSaveChanges, this); AddProductCommand = new RelayCommand(AddProduct); EditProductCommand = new RelayCommand <ProductModel>(EditProduct, CanEditProduct, this); RemoveProductCommand = new RelayCommand <ProductModel>(RemoveProduct, CanRemoveProduct, this); DisplayName = UiResources.ProductWorkspaceName; }
public ProductWorkspaceViewModel(IRepository repository, IMessagePresenter messagePresenter, IToastPresenter toastPresenter) { Should.NotBeNull(repository, "repository"); Should.NotBeNull(messagePresenter, "messagePresenter"); Should.NotBeNull(toastPresenter, "toastPresenter"); _repository = repository; _messagePresenter = messagePresenter; _toastPresenter = toastPresenter; _trackingCollection = new TrackingCollection(new CompositeEqualityComparer().AddComparer(ProductModel.KeyComparer)); SaveChangesCommand = RelayCommandBase.FromAsyncHandler(SaveChanges, CanSaveChanges, this); AddProductCommand = RelayCommandBase.FromAsyncHandler(AddProduct); EditProductCommand = RelayCommandBase.FromAsyncHandler <ProductModel>(EditProduct, CanEditProduct, this); RemoveProductCommand = RelayCommandBase.FromAsyncHandler <ProductModel>(RemoveProduct, CanRemoveProduct, this); DisplayName = UiResources.ProductWorkspaceName; }
public async void LoadState(IDataContext state) { ITrackingCollection trackingCollection = state.GetData(StateConstant); if (trackingCollection == null) { return; } IList <IEntityStateEntry> changes = trackingCollection.GetChanges(); await _initializedTask; _trackingCollection.UpdateStates(changes); foreach (IEntityStateEntry change in changes) { var oldItem = (ProductModel)change.Entity; ProductModel currentItem = GridViewModel.OriginalItemsSource.FirstOrDefault(model => model.Id == oldItem.Id); switch (change.State) { case EntityState.Added: GridViewModel.OriginalItemsSource.Add(oldItem); break; case EntityState.Modified: if (currentItem != null) { GridViewModel.OriginalItemsSource.Remove(currentItem); } GridViewModel.OriginalItemsSource.Add(oldItem); break; case EntityState.Deleted: if (currentItem != null) { GridViewModel.OriginalItemsSource.Remove(currentItem); } break; } } this.OnPropertyChanged(() => v => v.HasChanges); }
/// <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); } } }
public ITrackingCollection<IRemoteRepositoryModel> GetRepositories(ITrackingCollection<IRemoteRepositoryModel> collection) { var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}", CacheIndex.RepoPrefix, user.Login)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetRepositories() .Select(RepositoryCacheItem.Create), item => { // this could blow up due to the collection being disposed somewhere else try { collection.RemoveItem(Create(item)); } catch (ObjectDisposedException) { } }, TimeSpan.FromMinutes(5), TimeSpan.FromDays(1)) ) .Select(Create) ); collection.Listen(source); return collection; }
/// <summary> /// Gets a collection of Pull Requests. If you want to refresh existing data, pass a collection in /// </summary> /// <param name="repo"></param> /// <param name="collection"></param> /// <returns></returns> public ITrackingCollection<IPullRequestModel> GetPullRequests(ILocalRepositoryModel repo, ITrackingCollection<IPullRequestModel> collection) { // Since the api to list pull requests returns all the data for each pr, cache each pr in its own entry // and also cache an index that contains all the keys for each pr. This way we can fetch prs in bulk // but also individually without duplicating information. We store things in a custom observable collection // that checks whether an item is being updated (coming from the live stream after being retrieved from cache) // and replaces it instead of appending, so items get refreshed in-place as they come in. var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) .Select(PullRequestCacheItem.Create), item => { // this could blow up due to the collection being disposed somewhere else try { collection.RemoveItem(Create(item)); } catch (ObjectDisposedException) { } }, TimeSpan.Zero, TimeSpan.FromDays(7)) ) .Select(Create) ); collection.Listen(source); return collection; }
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(); }
/// <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 = trackableRef.GetRefPropertyChangeTracker(refProp.Property.Name);//item.GetRefPropertyChangeTracker(refProp.Property.Name); if (refChangeTracker != null) { hasDownstreamChanges = HasDownstreamChanges(refChangeTracker, trackableRef, item, refProp, hasDownstreamChanges); } } // 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)) { hasDownstreamChanges = GetChangesOnChildCollection(trackingItems, hasDownstreamChanges, item, colProp); } } } } // Return item if it has changes if (hasDownstreamChanges || item.TrackingState != TrackingState.Unchanged) { yield return(item); } } }
public static ObservableCollection <T> CreateListenerCollection <T>(this ITrackingCollection <T> tcol, IList <T> stickieItemsOnTop = null) where T : ICopyable <T> { var col = new ObservableCollection <T>(); tcol.CollectionChanged += (s, e) => { var offset = 0; if (stickieItemsOnTop != null) { foreach (var item in stickieItemsOnTop) { if (col.Contains(item)) { offset++; } } } if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move) { for (int i = 0, oldIdx = e.OldStartingIndex, newIdx = e.NewStartingIndex; i < e.OldItems.Count; i++, oldIdx++, newIdx++) { col.Move(oldIdx + offset, newIdx + offset); } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { foreach (T item in e.NewItems) { col.Add(item); } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) { foreach (T item in e.OldItems) { col.Remove(item); } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) { for (int i = 0, idx = e.OldStartingIndex; i < e.OldItems.Count; i++, idx++) { col[idx + offset] = (T)e.NewItems[i]; } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { col.Clear(); if (stickieItemsOnTop != null) { foreach (var item in stickieItemsOnTop) { col.Add(item); } } } }; return(col); }
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; }