public void When_Clear_Collection_Should_Work() { IList <Order> orders = Helper.GetOrdersIList(); IList <Order> trackable = orders.AsTrackable(); trackable.Clear(); IChangeTrackableCollection <Order> trackableCollection = trackable.CastToIChangeTrackableCollection(); trackableCollection.IsChanged.Should().BeTrue(); trackableCollection.DeletedItems.Should().NotBeEmpty(); trackableCollection.UnchangedItems.Should().BeEmpty(); }
/// <summary> /// Turns given object and all associates to untrackable objects (i.e. POCO objects). /// </summary> /// <param name="some">The whole object to untrack</param> /// <returns>The untracked version of given object</returns> internal static object ToUntypedUntracked(this object some) { if (some == null) { return(some); } IChangeTrackableObject trackable = some as IChangeTrackableObject; if (trackable != null) { IProxyTargetAccessor proxyTargetAccessor = (IProxyTargetAccessor)trackable; object target = proxyTargetAccessor.DynProxyGetTarget(); object unwrapped = target.CloneIt(some.GetType().GetActualTypeIfTrackable()); ObjectChangeTracker changeTracker = (ObjectChangeTracker)trackable.GetChangeTracker(); foreach (KeyValuePair <string, ObjectPropertyChangeTracking> dynamicProperty in changeTracker.DynamicPropertyTrackings) { object propertyValueToSet; IChangeTrackableCollection trackableCollection = dynamicProperty.Value.CurrentValue as IChangeTrackableCollection; if (trackableCollection != null) { propertyValueToSet = ((IEnumerable)trackableCollection).ToUntrackedEnumerable(trackableCollection.GetType()); } else { propertyValueToSet = dynamicProperty.Value.CurrentValue.ToUntracked(); } unwrapped.SetDynamicMember ( dynamicProperty.Value.PropertyName, propertyValueToSet ); } Contract.Assert(() => !(unwrapped is IProxyTargetAccessor), "To convert a tracked object to untracked one the whole tracked object must be created from a pre-existing object"); return(unwrapped); } else { return(some); } }
protected override void InterceptMethod(IInvocation invocation, IHasParent withParent) { Type collectionType = invocation.Method.DeclaringType; if (collectionType.IsEnumerable() && invocation.Arguments.Length == 1) { IChangeTrackableCollection trackableCollection = (IChangeTrackableCollection)withParent; IChangeTrackableObject changedItem = invocation.Arguments[0] as IChangeTrackableObject ?? TrackableObjectFactory.CreateFrom(invocation.Arguments[0]) as IChangeTrackableObject; IEnumerable <object> currentItems; Type collectionItemType = withParent.GetCollectionItemType(); bool itemsAreKeyValuePair = collectionItemType.GetTypeInfo().IsGenericType&& collectionItemType == typeof(KeyValuePair <,>).MakeGenericType(collectionItemType.GenericTypeArguments); if (itemsAreKeyValuePair) { currentItems = ((IEnumerable)withParent).Cast <object>(); } else { currentItems = ((IEnumerable <object>)withParent).ToList(); } invocation.Arguments[0] = changedItem ?? invocation.Arguments[0]; invocation.Proceed(); KeyValuePair <Type, CollectionImplementation> implementation = Configuration.Collections.GetImplementation(collectionType); CollectionChangeContext changeContext = new CollectionChangeContext ( !itemsAreKeyValuePair ? (IEnumerable <object>)trackableCollection : Enumerable.Empty <object>(), currentItems, trackableCollection.ParentObjectProperty, trackableCollection.AddedItems, trackableCollection.RemovedItems ); if (!itemsAreKeyValuePair) { Activator.CreateInstance(implementation.Value.ChangeInterceptor.MakeGenericType(trackableCollection.GetCollectionItemType()), changeContext) .CallMethod(invocation.Method.Name, invocation.Arguments); } switch (changeContext.Change) { case CollectionChange.Add: trackableCollection.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, new[] { changedItem }); break; case CollectionChange.Remove: trackableCollection.RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, new[] { changedItem }); break; } } else { invocation.Proceed(); } }
public object CreateForCollection(object some, IChangeTrackableObject parentObject, PropertyInfo parentObjectProperty) { if (some.IsTrackable()) { return(some); } Type collectionType = Configuration.Collections .GetImplementation(parentObjectProperty.PropertyType).Key; Type collectionImplementation; if (parentObjectProperty.PropertyType.IsGenericType && parentObjectProperty.PropertyType == collectionType.MakeGenericType(parentObjectProperty.PropertyType.GenericTypeArguments)) { collectionImplementation = collectionType.MakeGenericType(parentObjectProperty.PropertyType.GenericTypeArguments); } else { collectionImplementation = parentObjectProperty.PropertyType.GetInterfaces().SingleOrDefault ( i => { return(i.IsGenericType && i == collectionType.MakeGenericType(i.GenericTypeArguments[0])); } ); } Contract.Assert(collectionImplementation != null); if (some == null) { some = Configuration.Collections .GetImplementation(parentObjectProperty.PropertyType).Value.Type .CreateInstanceWithGenericArgs(null, collectionImplementation.GenericTypeArguments[0]); } Contract.Assert(some != null, "Either if a collection object is provided or not, a proxied instance of the whole collection type must be created"); Type genericCollectionType = some.GetType().GetGenericArguments().Last(); bool canTrackCollectionType = Configuration.CanTrackType(genericCollectionType); ProxyGenerationOptions options = new ProxyGenerationOptions(new CollectionterceptionHook()); ChangeTrackableCollectionMixin changeTrackingMixin = new ChangeTrackableCollectionMixin(); changeTrackingMixin.GetChangeTrackingContext().Configuration = Configuration; options.AddMixinInstance(changeTrackingMixin); KeyValuePair <Type, CollectionImplementation> collectionImplementationDetail = Configuration.Collections.GetImplementation(parentObjectProperty.PropertyType); Contract.Assert(collectionImplementationDetail.Key.MakeGenericType(collectionImplementation.GenericTypeArguments).IsAssignableFrom(parentObjectProperty.PropertyType), $"Trackable collection implementation of type '{collectionImplementationDetail.Key.AssemblyQualifiedName}' cannot be set to the target property '{parentObjectProperty.DeclaringType.FullName}.{parentObjectProperty.Name}' with type '{parentObjectProperty.PropertyType.AssemblyQualifiedName}'. This isn't supported because it might require a downcast. Please provide a collection change tracking configuration to work with the more concrete interface."); object targetList; if (!canTrackCollectionType) { targetList = some; } else { targetList = collectionImplementationDetail.Value.Type.CreateInstanceWithGenericArgs ( new[] { typeof(EnumerableExtensions).GetMethod("MakeAllTrackable") .MakeGenericMethod(genericCollectionType) .Invoke(null, new[] { some, Configuration, this, parentObjectProperty, parentObject }) }, genericCollectionType ); } Contract.Assert(targetList != null, "List to proxy is mandatory"); IChangeTrackableCollection proxy = (IChangeTrackableCollection)ProxyGenerator.CreateInterfaceProxyWithTarget ( collectionImplementationDetail.Key.MakeGenericType(some.GetType().GetGenericArguments()), new[] { typeof(IChangeTrackableCollection), typeof(IReadOnlyChangeTrackableCollection) }, targetList, options, new CollectionPropertyInterceptor(Configuration, this) ); proxy.ParentObject = parentObject; proxy.ParentObjectProperty = parentObjectProperty; proxy.CollectionChanged += (sender, e) => parentObject.RaisePropertyChanged(parentObject, parentObjectProperty.Name); return(proxy); }