Coder Social home page Coder Social logo

changetracking's Introduction

Build status NuGet Badge Test status

ChangeTracking

Track changes in your POCO objects, and in your collections. By using Castle Dynamic Proxy to create proxies of your classes at runtime, you can use your objects just like you used to, and just by calling the AsTrackable() extension method, you get automatic change tracking, and cancellation.

All trackable POCOs implement IChangeTrackable<T>, IRevertibleChangeTracking, IChangeTracking, IEditableObject and INotifyPropertyChanged.

And all trackable collections implement IChangeTrackableCollection<T>, IBindingList ICancelAddNew, INotifyCollectionChanged , IList<T>, IList, ICollection<T>, ICollection, IEnumerable<T> and IEnumerable

Installation

PM> Install-Package ChangeTracking

Example

To make an object trackable

using ChangeTracking;
//...
Order order = new Order 
{ 
    Id = 1,
    CustumerNumber = "Test",
    Address = new Address
    {
        AddressId = 1,
        City = "New York"
    },
    OrderDetails = new List<OrderDetail>
    {
        new OrderDetail
        {
            OrderDetailId = 1,
            ItemNo = "Item123"
        },
        new OrderDetail
        {
            OrderDetailId = 2,
            ItemNo = "Item369"
        }
    }
};
Order trackedOrder = order.AsTrackable();

And here is how you get to the tracked info.

var trackable = (IChangeTrackable<Order>)trackedOrder;
// same as
var trackable = trackedOrder.CastToIChangeTrackable();

And here is what's available on trackable.

//Can be Unchanged, Added, Changed, Deleted
ChangeStatus status = trackable.ChangeTrackingStatus;

//Will be true if ChangeTrackingStatus is not Unchanged
bool isChanged = trackable.IsChanged;

//Will retrieve the original value of a property
string originalCustNumber = trackable.GetOriginalValue(o => o.CustumerNumber);

//Will retrieve a copy of the original item
var originalOrder = trackable.GetOriginal();

//Calling RejectChanges will reject all the changes you made, reset all properties to their original values and set ChangeTrackingStatus to Unchanged
trackable.RejectChanges();

//Calling AcceptChanges will accept all the changes you made, clears the original values and set ChangeTrackingStatus to Unchanged
trackable.AcceptChanges();

//If ChangeTrackingStatus is Changed it returns all changed property names, if ChangeTrackingStatus is Added or Deleted it returns all properties
trackable.ChangedProperties;

By default complex properties and collection properties will be tracked (if it can be made trackable).

You can change the default by setting

ChangeTrackingFactory.Default.MakeCollectionPropertiesTrackable = false;

ChangeTrackingFactory.Default.MakeComplexPropertiesTrackable = false;

You can override the default when creating the trackable.

var trackable = order.AsTrackable(makeComplexPropertiesTrackable: false);

or

var trackable = order.AsTrackable(makeCollectionPropertiesTrackable: false);

And on a collection

var orders = new List<Order>{new Order { Id = 1, CustumerNumber = "Test" } };
IList<Order> trackableOrders = orders.AsTrackable();

And here is how you get to the tracked info.

var trackable = (IChangeTrackableCollection<Order>)trackableOrders;
// Same as
var trackable = trackableOrders.CastToIChangeTrackableCollection();

And here is what's available on trackable.

// Will be true if there are any changed items, added items or deleted items in the collection.
bool isChanged = trackable.IsChanged;

// Will return all items with ChangeTrackingStatus of Unchanged
IEnumerable<Order> unchangedOrders = trackable.UnchangedItems;
// Will return all items that were added to the collection - with ChangeTrackingStatus of Added
IEnumerable<Order> addedOrders = trackable.AddedItems;
// Will return all items with ChangeTrackingStatus of Changed
IEnumerable<Order> changedOrders = trackable.ChangedItems;
// Will return all items that were removed from the collection - with ChangeTrackingStatus of Deleted
IEnumerable<Order> deletedOrders = trackable.DeletedItems;

// Will Accept all the changes in the collection and its items, deleted items will be cleared and all items ChangeTrackingStatus will be Unchanged
trackable.AcceptChanges();

// Will Reject all the changes in the collection and its items, deleted items will be moved back to the collection, added items removed and all items ChangeTrackingStatus will be Unchanged
trackable.RejectChanges();

Exlude Properties

To exlude a property from being tracked, apply the DoNoTrack attribute to the property or to the the property class.

public class Order
{
    [DoNoTrack]
    public virtual Address Address { get; set; }

    //will not be tracked because the Lead class is marked [DoNotTrack].
    public virtual Lead Lead { get; set; }
}

[DoNoTrack]
public class Lead
{
    public virtual int LeadId { get; set; }
}

Requirements and restrictions

  • .net 4.5.2 and above
  • netstandard 2.0
For Plain objects
  • Your class must not be sealed and all members in your class must be public virtual

    public class Order
    {
        public virtual int Id { get; set; }
        public virtual string CustumerNumber { get; set; }
        public virtual Address  Address { get; set; }
        public virtual IList<OrderDetail> OrderDetails { get; set; }
    }
For Collections
  • You can only assign the created proxy to one of the implemented interfaces, i.e. ICollection<T>, IList<T> and IBindingList, and the AsTrackable<T>() will choose the correct extennsion method only if called on IList<T>, IList, ICollection<T> and ICollection.

    IList<Order> orders = new List<Order>().AsTrackable();

changetracking's People

Contributors

joelweiss avatar joshbouganim avatar patrickgalbraith avatar weitzhandler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

changetracking's Issues

Unable to sort trackable collection in DataGrid.

Normally, a WPF DataGrid will sort a collection by clicking on column headers. If I apply AsTrackable() to the collection, then sorting no longer works. The sort indicators on the column header still shows the direction of the sort, but the rows do not change to show the sort order.

Here's an example:

MainWindow.xaml:

<Window x:Class="ChangeTrackingTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <DataGrid CanUserAddRows="False" CanUserSortColumns="True" AutoGenerateColumns="False" ItemsSource="{Binding TestData}">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}" CanUserSort="True" Header="Name"/>
            <DataGridTextColumn Binding="{Binding Desc}" CanUserSort="True" Header="Description"/>
        </DataGrid.Columns>
    </DataGrid>
</Window>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.Windows;
using ChangeTracking;

namespace ChangeTrackingTest
{
    public class Data
    {
        public virtual string Name { get; set; }
        public virtual string Desc { get; set; }

        public Data() { }

        public Data(string name, string desc)
        {
            Name = name;
            Desc = desc;
        }
    }

    public class ViewModel
    {
        public IList<Data> TestData { get; set; }

        public ViewModel(IList<Data> data)
        {
            TestData = data;
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            //var data = GetData(); // This sorts but isn't Trackable
            var data = GetData().AsTrackable(); // This is Trackable but won't sort

            var viewModel = new ViewModel(data);
            DataContext = viewModel;

            InitializeComponent();
        }

        private static IList<Data> GetData()
        {
            return new List<Data>()
            {
                new Data("Train", "My train"),
                new Data("Car", "My car"),
                new Data("Plane", "My plane")
            };
        }
    }
}

Switch the comments on the var data statements in the MainWindow constructor to see that it is just AsTrackable() that breaks sorting.

Trackable Object CollectionChanged event not firing on Insert

Trackable Object CollectionChanged event not firing on Insert but it fired on Add

void Main()
{
	Order order = new Order
	{
		Id = 1,
		CustomerNumber = "Test",
		Address = new Address
		{
			AddressId = 1,
			City = "New York"
		},
		OrderDetails = new List<OrderDetail>
		{
			new OrderDetail
			{
				OrderDetailId = 1,
				ItemNo = "Item123"
			},
			new OrderDetail
			{
				OrderDetailId = 2,
				ItemNo = "Item369"
			}
		}
	};
	Order trackedOrder = order.AsTrackable();
	((INotifyCollectionChanged)trackedOrder.OrderDetails).CollectionChanged+=(s,e)=>
	{
		//not fired on insert 
	};
	var od = new OrderDetail{ItemNo="ItemTest", OrderDetailId=100};
	trackedOrder.OrderDetails.Add(od);  //CollectionChanged Fired
	trackedOrder.OrderDetails.Insert(0,od); //CollectionChanged not Fired
}

Nested TrackableCollection : Change One getting Multiple Notifications

Hi,
Take a look at the following LinqPad script.
Can you explain why I get notified multiple times (for Orderdetails) despite I made one change

void Main()
{
	Order order = new Order
	{
		Id = 1,
		CustomerNumber = "Test",
		OrderDetails = new List<OrderDetail>
		{
			new OrderDetail
			{
				OrderDetailId = 1,
				ItemNo = "Item123"
			},
			new OrderDetail
			{
				OrderDetailId = 2,
				ItemNo = "Item369"
			}
		}
	};

	var trackedOrder = order.AsTrackable();
	trackedOrder.CastToIChangeTrackable().PropertyChanged += (s, e) =>
	 {
		e.PropertyName.Dump();
	 };

	var first = trackedOrder.OrderDetails.FirstOrDefault();

	first.ItemNo = "test"; //<==== only one change
}

public class Order
{
	public virtual int Id { get; set; }
	public virtual string CustomerNumber { get; set; }
	public virtual decimal Total { get; set; }
	public virtual IList<OrderDetail> OrderDetails { get; set; }
}

public class OrderDetail
{
	public virtual int OrderDetailId { get; set; }
	public virtual string ItemNo { get; set; }
}

here the output when changing ItemNo

ChangeTrackingStatus
ChangedProperties
OrderDetails //1
OrderDetails //2
OrderDetails //3
OrderDetails //4

Thanks Sir for your time.

Support for internal constructors

I'm looking at using ChangeTracking in a project but currently I have entities that have required constructor args on my public API. I don't mind exposing an internal constructor for ChangeTracking, but I'd rather not allow it for general users of my library.

I've had a look and it does appear that by setting InternalsVisibleTo("DynamicProxyGenAssembly2") in your library so that the Castle DynamicProxy can see your constructors, and then swapping any instances of Activator.CreateInstance<T>() within the change tracking code to (T)Activator.CreateInstance(typeof(T), true); does allow all the unit tests to pass so means it can use just internal constructors.

Would it be acceptable to use CreateInstance signature that can use non public constructors? or can you foresee any issues in doing so?

PS I tried setting InternalsVisibleTo("ChangeTracking") but that doesn't seem to allow Activator.CreateInstance to see the internal constructor so I'm guessing my suggestion above would be required.

How to get the underlying entity object from entity proxy at runtime

How to get the underlying entity object from entity proxy at run time without the extra code added by proxy

pseudocode :

Order trackedOrder = order.AsTrackable();
//... do  something with trackedOrder
//..
//hard to serialize trackedOrder

what I want :

//getting the changed object without extra code 
var order_without_extra_code =  trackedOrder.GetUnderlyingObject() 

Serialize(order_without_extra_code)

The cast to the base type (Order) not working for me, still get the extra code:

Order order = (Ordre)trackedOrder;//not working

can you help ?
thanks in advance

PCL

Hi,

I'm trying to add the nuget package to my Xamarin PCL (profile 44), and I get the following error:

Could not install package 'Castle.Core 3.2.0'. You are trying to install this package into a project that targets '.NETPortable,Version=v4.6,Profile=Profile44', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

Anyway to make it work?

ListView in Xamarin.Forms doesn't refresh when the underlying collection is updated!

Hi,

public class MyOrder 
{
	public Order Order { get; set; }
	
	public MyOrder()
	{
		var _iniOrder = new Order(); 		//class that you use
		Order = _iniOrder.AsTrackable();
	}
}

XAML:

<ListView ItemsSource="{Binding MyOrder.Order.OrderDetails}" .../>

Adding item

public MyOrder Mo { get;set; }
Mo = new MyOrder();
...
Mo.Order.OrderDetails.Insert(0, new OrderDetail
{
	ItemNo = Guid.NewGuid().ToString(),
	OrderDetailId = new Random().Next(1, 100)
});	

ListView does not refresh when the underlying collection is updated(ie: Mo.Order.OrderDetails):

even the collection has data!

FYI : The code above works fine in wpf but doesn't work for Xamarin.Forms(Android/UWP)!

xf-android

wpf

is there an explanation? thanks in advance and thanks for sharing this!

Setting NULL throws NRE

Hello.
Am I doing something wrong?
Both tests are failed.

        [TestMethod]
        public void Change_Property_From_Null_To_Null_Should_Not_Throw()
        {
            var trackable = new Order { Id = 321,  }.AsTrackable();

            trackable.Invoking(o => o.Address = null).ShouldNotThrow<NullReferenceException>();
        }

        [TestMethod]
        public void Change_Property_From_Value_To_Null_Should_Not_Throw()
        {
            var trackable = new Order { Id = 321, Address = new Address() }.AsTrackable();

            trackable.Invoking(o => o.Address = null).ShouldNotThrow<NullReferenceException>();
        }

Add strong-name signing

Great project, but is there a chance you could strong-name sign it, please?
I currently can't add it into my strong-named project without manually signing it myself, which is a bit of a nuisance every time I upgrade.
Thanks.

Tracking child objects - Complex property tracking

Thanks for the library. This is probably a misunderstanding on my part or I'm missing something but I've noticed my nested objects do not get tracked even with complexpropertytracking set to true and all of their properties marked as virtual.

` ChangeTrackingFactory.Default.MakeCollectionPropertiesTrackable = true;
ChangeTrackingFactory.Default.MakeComplexPropertiesTrackable = true;

        var calReportInfo = new CalibrationReportInfo();
        calReportInfo.AdderCodes = new AdderCodes { Configuration = "Configuration here", Id = Guid.NewGuid(), IsShown = true };
        calReportInfo.AnalogDataPoints = new List<AnalogDataPoint> { new AnalogDataPoint { ConnectorType = ConnectorType.DB15, Id = Guid.NewGuid(), OutputDefinition = OutputDefinition.Primary, PinNumber = 1 } };
        calReportInfo.CalibratedBy = new User("", "FirstName", "LastName", "UserName");
        calReportInfo.CalibrationCompany = new CalibrationCompany { Id = Guid.NewGuid(), IsShown = true, Manufacturer = Manufacturer.Blank };
        calReportInfo.CalibrationCustomer = new CalibrationCustomer { Id = Guid.NewGuid(), IsShown = true, Name = "Name" };
        calReportInfo.CalibrationDate = DateTime.Now;
        calReportInfo.CalibrationDueMessage = "";
        calReportInfo.CalibrationGas = new Gas { Id = Guid.NewGuid(), IsShown = true, IsSelectable = true, Name = "GasName" };
        calReportInfo.CalibrationProcedure = new CalibrationProcedure { Id = Guid.NewGuid(), Name = "Name", Revision = 1 };
        calReportInfo.CalibrationTools = new List<CalibrationTool> { new CalibrationTool { Id = Guid.NewGuid(), Name = "ToolName", ToolType = ToolType.Flow } };
        calReportInfo.CrossReference = new PartNumber { Id = Guid.NewGuid(), IsShown = true, Value = "CrossRef" };
        calReportInfo.DigitalDataPoints = new List<DigitalDataPoint> { new DigitalDataPoint { Actual = 0, Id = Guid.NewGuid(), Indicated = 0, LineNumber = 0 } };
        
        var trackedOrder = calReportInfo.AsTrackable();
        var trackable = trackedOrder as IChangeTrackable<CalibrationReportInfo>;

        trackedOrder.CalibrationProcedure.Name = "Name2";
        trackedOrder.CrossReference.Value = "NEWVALUE";
        trackedOrder.CalibrationDate = DateTime.Now.Subtract(TimeSpan.FromDays(1));
        
        var status = trackable.ChangeTrackingStatus;
        bool isChanged = trackable.IsChanged;
        var originalValue = trackable.GetOriginalValue(o => o.CalibrationProcedure);
        var originalOrder = trackable.GetOriginal();
        var changed = trackable.ChangedProperties;`

Am I missing something or does something special have to be done to track these child objects that I'm not doing? The only thing that is marked as changed at the end is the CalibrationDate property.

Thanks

Get modified properties on an object?

I'm having a hard time getting the list of properties that were changed on a tracked object. Is this possible with the current API, or do I just have to manually get the properties again myself, iterate over the object, and compare the current values to the original values? Perhaps this would be a nice thing to add to the API?

My use case for this is that I would like to apply a series of transformations to a complex object, then boil that down to what objects/properties/lists have been added, removed, and modified, so I can pass that simplified diff to another component in my program.

Thanks for the hard work!
- Harrison

DoNoTrackAttribute is inaccessible due to its protection level

It says "DoNoTrackAttribute is inaccessible due to its protection level" whenever I try to use DoNoTrack attribute for my model properties in my project.

And I found out "internal sealed class DoNoTrackAttribute : Attribute", DoNoTrackAttribute is not public. Should it be public instead?

Proxy Creation fails for Non-Virtual readonly properties???

Hello,

I started to look at your library because it looks exactly what I will need to enable change tracking in my app. My app uses .NET Framework 4.7.2 and is a WPF+SQL Server+EntityFramework app

Now, first I got an error about not all my DataObject properties being virtual.
OK, I made them virtual.

The problem is my ListProperties are ObservableCollection, and not IList
Now I got an error saying property Count is not virtual?
In ObservableCollection => property Count DOES NOT HAVE A SETTER, only a getter.
Not to mention that I cannot edit it to be virtual even if I wanted to, as it belongs to Microsoft.

The error comes when I do trackable.AcceptChanges() and it has a property of type ObservableCollection

Now, my questions would be:

  1. Can I ignore ALL properties defined in the BaseClass of my DataObject and use ONLY properties defined in the DataObject class.
  2. Can I NOT get an exception for a Non-Virtual property? I don't care that it will not participate in the change tracking, I just want to be able to use the library

Related to 1) Is there any way by default to Exclude everything from hierarchy and let me to manually mark things that I want to? Similar to Json.OptIn functionality.

Thank you very much for your help.

FullStackTrace of the error ( cutting out my Main static method from my test console app )
My Object is called wc_package

at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
at ChangeTracking.ComplexPropertyInterceptor1.MakeAllPropertiesTrackable(Object proxy) in C:\projects\changetracking\Source\ChangeTracking\ComplexPropertyInterceptor.cs:line 176 at ChangeTracking.ComplexPropertyInterceptor1.ComplexPropertyTrackables(Object proxy) in C:\projects\changetracking\Source\ChangeTracking\ComplexPropertyInterceptor.cs:line 165
at ChangeTracking.ComplexPropertyInterceptor1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\ComplexPropertyInterceptor.cs:line 144 at Castle.DynamicProxy.AbstractInvocation.Proceed() at ChangeTracking.EditableObjectInterceptor1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\EditableObjectInterceptor.cs:line 87
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at ChangeTracking.ChangeTrackingInterceptor1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\ChangeTrackingInterceptor.cs:line 96 at Castle.DynamicProxy.AbstractInvocation.Proceed() at ChangeTracking.NotifyPropertyChangedInterceptor1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\NotifyPropertyChangedInterceptor.cs:line 68
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.wc_packageProxy.get_ComplexPropertyTrackables()
at ChangeTracking.Internal.Utils.GetChildren[TResult](Object proxy, List1 parents) in C:\projects\changetracking\Source\ChangeTracking\Internal\Utils.cs:line 12 at ChangeTracking.ChangeTrackingInterceptor1.AcceptChanges(Object proxy, List1 parents) in C:\projects\changetracking\Source\ChangeTracking\ChangeTrackingInterceptor.cs:line 300 at ChangeTracking.ChangeTrackingInterceptor1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\ChangeTrackingInterceptor.cs:line 148
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at ChangeTracking.NotifyPropertyChangedInterceptor`1.Intercept(IInvocation invocation) in C:\projects\changetracking\Source\ChangeTracking\NotifyPropertyChangedInterceptor.cs:line 83
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.wc_packageProxy.AcceptChanges()

Change tracking breaks with string lists

I ran into a problem with a string list on my model where it would try to track the strings, and it would then die. Is this a known issue, and can you replicate?

I've included a small example that also died in 2.0.13

    public class TestThing
    {
        public virtual IList<string> MyStrings { get; set; }

        public TestThing()
        {
            MyStrings = new List<string>();
        }
    }

// ----------------------------------------------------------------
            var test = new TestThing();
            test.MyStrings.Add("wat");
                

            test = test.AsTrackable();
            test.MyStrings = new List<string>() { "ok", "yea" };

Exception:

|System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: Property Chars is not virtual. Can't track classes with non-virtual properties.
   at ChangeTracking.ChangeTrackingProxyGenerationHook.NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
   at Castle.DynamicProxy.Contributors.MembersCollector.AcceptMethod(MethodInfo method, Boolean onlyVirtuals, IProxyGenerationHook hook)
   at Castle.DynamicProxy.Contributors.WrappedClassMembersCollector.GetMethodToGenerate(MethodInfo method, IProxyGenerationHook hook, Boolean isStandalone)
   at Castle.DynamicProxy.Contributors.MembersCollector.AddMethod(MethodInfo method, IProxyGenerationHook hook, Boolean isStandalone)
   at Castle.DynamicProxy.Contributors.MembersCollector.AddProperty(PropertyInfo property, IProxyGenerationHook hook)
   at Castle.DynamicProxy.Contributors.MembersCollector.CollectProperties(IProxyGenerationHook hook)
   at Castle.DynamicProxy.Contributors.MembersCollector.CollectMembersToProxy(IProxyGenerationHook hook)
   at Castle.DynamicProxy.Contributors.ClassProxyWithTargetTargetContributor.CollectElementsToProxyInternal(IProxyGenerationHook hook)+MoveNext()
   at Castle.DynamicProxy.Contributors.CompositeTypeContributor.CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
   at Castle.DynamicProxy.Generators.ClassProxyWithTargetGenerator.GenerateType(String name, INamingScope namingScope)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyWithTarget(Type classToProxy, Type[] additionalInterfacesToProxy, Object target, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
   at ChangeTracking.Core.AsTrackable[T](T target, ChangeStatus status, Action`1 notifyParentListItemCanceled, Boolean makeComplexPropertiesTrackable, Boolean makeCollectionPropertiesTrackable)
   at ChangeTracking.ChangeTrackingCollectionInterceptor`1..ctor(IList`1 target, Boolean makeComplexPropertiesTrackable, Boolean makeCollectionPropertiesTrackable)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at ChangeTracking.Core.AsTrackableCollectionChild(Type type, Object target, Boolean makeComplexPropertiesTrackable, Boolean makeCollectionPropertiesTrackable)
   at ChangeTracking.CollectionPropertyInterceptor`1.<>c__DisplayClass12_0.<GetGetterAction>b__0(IInvocation invocation, Dictionary`2 trackables, Boolean makeComplexPropertiesTrackable, Boolean makeCollectionPropertiesTrackable)
   at ChangeTracking.CollectionPropertyInterceptor`1.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at ChangeTracking.ComplexPropertyInterceptor`1.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at ChangeTracking.ChangeTrackingInterceptor`1.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at ChangeTracking.NotifyPropertyChangedInterceptor`1.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.TestThingProxy.get_MyStrings()
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at ChangeTracking.NotifyPropertyChangedInterceptor`1.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()

NullReferenceException when using BsonDeserializer.

Looking at other closed issues and the GetCurrent function, I still thought that this is a different issue. I have a MongoDB where I keep the object, I want to only update the elements that user has changed, rather simple. My first store works okay, but if I re-fetch the objects during the same run, the MongoDB (which uses BSON underneath) fails with NRE. I can reproduce this just by using serialize/deserialize functions, writing to a local file.

public class SomeObject
{
    public virtual string Uid { get; set; } = string.Empty;
};

var objects = new List<SomeObject> { new SomeObject() }.AsTrackable();
objects.First().Uid = "update";

var track = objects.CastToIChangeTrackableCollection();
if (track.IsChanged)
{
    // take changed and serialize it
    var changed = track.ChangedItems.First();
    using (var stream = File.Create("file.bson"))
    {
        using var writer = new BsonBinaryWriter(stream);
        BsonSerializer.Serialize(writer, changed);
    }

    // deserialize back
    var contents = File.ReadAllBytes("file.bson");
    var obj = BsonSerializer.Deserialize<SomeObject>(contents);
}

The last line throws NRE and here's the stack trace.

   at Castle.Proxies.SomeObjectProxy.set_Uid(String value)
   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize(IBsonSerializer serializer, BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.BsonSerializer.Deserialize[TNominalType](Stream stream, Action`1 configurator)
   at MongoDB.Bson.Serialization.BsonSerializer.Deserialize[TNominalType](Byte[] bytes, Action`1 configurator)
   at ArchiSymphony.ViewModels.SheetViewModel.<>c.<<get_LoadProjectFromExternal>b__2_0>d.MoveNext() in D:\wa\pls\ArchiSymphony\ArchiSymphony\ViewModels\SheetViewModel.cs:line 46

I don't think the issue is with proxy properties being serialized in the first place. As I explicitly control which properties on the POCO get stored in the DB, using BsonElementAttribute. I'll be honest, I don't understand why the SomeObjectProxy is being created here, as the Deserialize should return me the unproxied SomeObject instead, which I can then mark as trackable. Any hints as to what might be wrong?

Cheers,
Alex

Can we do changetracking for IList<int> or IList<String>

Seem like IList change tracking only support id T is a Class.
Try to track changes for IList and IList

for Int I am getting below error when I make the object AsTrackable()
I am getting "System.InvalidOperationException
HResult=0x80131509
Message=Only IList, List and ICollection are supported
Source=ChangeTracking"


for String I am getting the below error when I Add an Item to the IList
System.TypeLoadException
HResult=0x80131522
Message=Could not load type 'Castle.Proxies.StringProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' because the parent type is sealed."

Add ability to disable tracking on desired properties

I know in #18 it was asked if there was a way to ignore certain members, but I think it would be a handy feature, so I'm raising a new issue to discuss how this could be implemented. I'm happy to contribute, but I'm pretty new to the source so could do with some guidance on where / how this could be done.

I have 2 ideas, 1 being that you add an option to the AsTrackable to ignore non-virtual properties. This way one could just disable tracking by making it non-virtual. This does have a problem though if you have a base class you want to allow properties to be overridden on though so option 2 is to introduce a DoNoTrackChangesAttribute that can be added to properties to signify that this property shouldn't be tracked.

I think you could introduce both, but if you just wanted 1 solution that would work, I think the attribute is the better option.

As I say, I'd be happy to contribute this feature, I'd just like to ask for some guidance on where this would get added in the code (I'm still trying to figure things out).

Matt

Access to sourcecollection

Hi,

Im changetracking an ObservableCollection and depend on the Move method, but i cant see a way to access the originalcollection so i can do a move. Is there a way to get the proxy as the Original IList so i can cast and do the move.

Thank you

PropertyChanged event not fired if underliying properties was modified by a virtual method

Hi,
Let add two methods to the order class, one virtual and the other not virtual:

public class Order
{
	//...
	
	public virtual void VirtualModifier()
	{
		CustomerNumber = "test";
	}
	public void NonVirtualModifier()
	{
		CustomerNumber = "test";
	}
	
	//...
}

and add two tests :

[Fact]
public void Change_Property_Should_Raise_PropertyChanged_Event_if_non_virtual_method()
{
	var order = Helper.GetOrder();
	var trackable = order.AsTrackable();
	((INotifyPropertyChanged)trackable).MonitorEvents();

	trackable.NonVirtualModifier(); 

	trackable.ShouldRaisePropertyChangeFor(o => o.CustomerNumber);
}
[Fact]
public void Change_Property_Should_Raise_PropertyChanged_Event_if_method_virtual()
{
	var order = Helper.GetOrder();
	var trackable = order.AsTrackable();
	((INotifyPropertyChanged)trackable).MonitorEvents();

	trackable.VirtualModifier();

	trackable.ShouldRaisePropertyChangeFor(o => o.CustomerNumber);
}

the second test fails and the PropertyChange event not fired, the reason is the VirtualModifier is virtual
can you explain why ?

thanks

List(Of).AsTrackable()

Hi,

Can this be fixed, or I always need to declare with:
xToolModulesL = (New List(Of cTool_Module)).AsTrackable(makeComplexPropertiesTrackable:=True, makeCollectionPropertiesTrackable:=True)

image

Thanks.

CollectionChanged not firing when Inserting Items to Trackable Collection!!!

var order = new Order
{
	Id = 1,
	CustomerNumber = "Test",
	Address = new Address
	{
		AddressId = 1,
		City = "New York"
	},
	OrderDetails = new ObservableCollection<OrderDetail>
	{
		new OrderDetail
		{
			OrderDetailId = 1,
			ItemNo = "Item123"
		},
		new OrderDetail
		{
			OrderDetailId = 2,
			ItemNo = "Item369"
		}
	}
};

TrackableOrder = order.AsTrackable();

((INotifyCollectionChanged)TrackableOrder.OrderDetails).CollectionChanged += OnCollectionChanged;

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    //called on add but not On Insert
}

Adding items with Insert OnCollectionChanged never called thus the UI will not be notified

TrackableOrder.OrderDetails.Insert(0, new OrderDetail()
{
    ItemNo = $"{Guid.NewGuid()}",
    OrderDetailId = 100
});

Adding items with Add OnCollectionChanged called

TrackableOrder.OrderDetails.Add(new OrderDetail()
{
	ItemNo = $"{Guid.NewGuid()}",
	OrderDetailId = 200
});

Can you confirm this Sir @joelweiss ?

IComplexPropertyTrackable is internal

I want to construct a hierarchy of changes. If IComplexPropertyTrackable would be accessible it is easy to traverse hierarchy down through IChangeTrackables.

I don't suggest to build-in change hierarchy function, because it is more complex. There could be circular references and so on.
In my specific case, I know how tracked objects look like and could safely traverse hierarchy.

The same case is ICollectionPropertyTrackable if I can traverse collections.

InvalidCastException when using ObservableCollection

Change the OrderDetails type to ObservableCollection

public class Order
{
...
public Order()
{
   OrderDetails = new ObservableCollection<OrderDetail>();
}
...
public virtual ObservableCollection<OrderDetail> OrderDetails { get; set; }
...
}

Order order = new Order();
var trackable = order.AsTrackable();
trackable.OrderDetails.CastToIChangeTrackableCollection(); //Invalid cast exception

InvalidCastException: Unable to cast object of type 'System.Collections.ObjectModel.ObservableCollection1[UserQuery+OrderDetail]' to type 'ChangeTracking.IChangeTrackableCollection1[UserQuery+OrderDetail]'.

what I'm missing ?
thanks Sir.

Is it possible to track previous changes?

Hey, just wondering, is it possible to remember what was changed for the last AcceptChanges call? My use case would be to fire a post save event and be able to know what was changed in the last commit.

Receive exception "Property et is not virtual. Can't track class with non-virtual properties"

There is now "et" property on PetOwner so I think it must be truncating Pets or something. I ran into this problem before I believe when I didn't have a default constructor. I'm not sure what is causing this one.

The exception occurs on the following line

var trackablePetOwner = petOwner.AsTrackable();

Here is the full code

public class PetOwner
{
    public virtual IList<Pet> Pets { get; private set; }

    public PetOwner()
    {
        Pets = new List<Pet>();
    }

    public void AddPet(string name)
    {
        Pets.Add(new Pet {Name = name});
    }
}

public class Pet
{
    public virtual string Name{ get; set; }
}    

class Program
{
    static void Main(string[] args)
    {
        var petOwner = new PetOwner();
        var trackablePetOwner = petOwner.AsTrackable();

        trackablePetOwner.AddPet("Fido");

        foreach (var pet in trackablePetOwner.Pets.CastToIChangeTrackableCollection().AddedItems)
        {
            Console.WriteLine("Added: " + pet.Name);
        }

        Console.WriteLine("Done...");
        Console.ReadKey();

    }
}

.Net standard support

Is there going to be .net standard support for this library? I would love to use it on a xamarin project.

Global options

It would be helpful if there was a way to set the default value for makeComplexPropertiesTrackable and makeCollectionPropertiesTrackable globally.

Problems with DataBinding with BindingList

I found a very interesting bug which I don't fully understand why it happens but after long hours found a workaround.

I'm using Winforms and binding a control to my model. The model contains a binding list and a read-only property that curates a total of the items in the list. In order to update the UI, I attach a listener in the constructor to notify changes to the total property on every change to the binding list.

When I call AsTrackable(), binding the total field to the control will not update the UI when the list changes. The workaround is to use a BindingSource to wrap the whole trackable object.

Here's a workable example:

public class Order : INotifyPropertyChanged
{
    public virtual IList<Item> Items { get; } = new BindingList<Item>();
    public virtual int TotalPrice => Items.Sum(i => i.Price);

    public Order()
    {
        ((IBindingList) Items).ListChanged +=
                   (sender, args) => OnPropertyChanged("TotalPrice");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string prop)
    {
        var e = PropertyChanged;
        e?.Invoke(this, new PropertyChangedEventArgs(prop));
    }

}

public class Item
{
    public virtual int Price => 10;
}

public class Test
{

    public static void Main() 
    {

        var form = new Form();
        // to force the binding to calculate we must make the control visible
        form.Show();
            
        var order1 = new Order();
        var label1 = new Label();
        form.Controls.Add(label1);
        label1.DataBindings.Add("Text", order1, "TotalPrice");

        Debug.WriteLine($"label1.Text: {label1.Text}");
        order1.Items.Add(new Item());
        Debug.WriteLine($"label1.Text: {label1.Text}");

        var order2 = new Order().AsTrackable();
        var label2 = new Label();
        form.Controls.Add(label2);
        label2.DataBindings.Add("Text", order2, "TotalPrice");

        Debug.WriteLine($"label2.Text: {label2.Text}");
        order2.Items.Add(new Item());
        Debug.WriteLine($"label2.Text: {label2.Text}");


        var order3 = new Order().AsTrackable();
        var label3 = new Label();
        form.Controls.Add(label3);
        var source = new BindingSource(order3, null);
        label3.DataBindings.Add("Text", source, "TotalPrice");

        Debug.WriteLine($"label3.Text: {label3.Text}");
        order3.Items.Add(new Item());
        Debug.WriteLine($"label3.Text: {label3.Text}");

    }
}

Which outputs:

label1.Text: 0
label1.Text: 10
label2.Text: 0
label2.Text: 0
label3.Text: 0
label3.Text: 10

As you can see label2 isn't updating.

Can't create method to get interceptors

I was wanting to create a custom method (to move to separate class later) that gets the list of Interceptors to add to the proxy.

I created a method

private IInterceptor[] GetInterceptors<T>(ChangeStatus status, Action<T> notifyParentListItemCanceled, ChangeTrackingSettings changeTrackingSettings, Graph graph) where T : class
{
    var changeTrackingInterceptor = new ChangeTrackingInterceptor<T>(status);
    var notifyPropertyChangedInterceptor = new NotifyPropertyChangedInterceptor<T>(changeTrackingInterceptor);
    var editableObjectInterceptor = new EditableObjectInterceptor<T>(notifyParentListItemCanceled);
    var complexPropertyInterceptor = new ComplexPropertyInterceptor<T>(changeTrackingSettings, graph);
    var collectionPropertyInterceptor = new CollectionPropertyInterceptor<T>(changeTrackingSettings, graph);

    return new IInterceptor[]
        {
            notifyPropertyChangedInterceptor,
            changeTrackingInterceptor,
            editableObjectInterceptor,
            complexPropertyInterceptor,
            collectionPropertyInterceptor
         };
}

and modified the proxy create to

var interceptors = GetInterceptors(status, notifyParentListItemCanceled, changeTrackingSettings, graph);
    object proxy = _ProxyGenerator.CreateClassProxyWithTarget(typeof(T),
        new[] { typeof(IChangeTrackableInternal), typeof(IRevertibleChangeTrackingInternal), typeof(IChangeTrackable<T>), typeof(IChangeTrackingManager), typeof(IComplexPropertyTrackable), typeof(ICollectionPropertyTrackable), typeof(IEditableObjectInternal), typeof(INotifyPropertyChanged) },
        target,
        GetOptions(typeof(T)),
        interceptors
        );

This all appears to work until I run the unit test where upon nearly all the collection tests fail.
I have done a similar thing with the TrackableChild and this doesn't cause unit test failures.

I tried putting inserting the returned interceptors using array notation so the statement was basically the same but to no avail. If any of the interceptors were from the called method one gets the errors.

I'm a bit at my wits end with this problem so I'm hoping someone can show me the error of my ways.

I was hoping to add validation and mixin capability as well as the ability to use interfaces (the latter of which I have working) but have stumbled at this.

I'm also wanting to create a version that doesn't make changes to the target until AcceptChanges is called but this seems like a complete rewrite.

Thanks Sevek

INotifyPropertyChanged

I'd like to see IChangeTracking declaratively implementing INotifyPropertyChanged, this already happens implicitly which requires many redundant casts.

Also please have IChangeTrackableCollection implement INotifyCollectionChanged, that would also be very nice.

There is a bug when hava inheritance relationship

I changed the method in core.cs as follow:

    internal static T AsTrackable<T>(this T target, ChangeStatus status, Action<T> notifyParentListItemCanceled, bool makeComplexPropertiesTrackable, bool makeCollectionPropertiesTrackable) where T : class
    {
        //if T was ICollection<T> it would of gone to one of the other overloads
        if (target as ICollection != null)
        {
            throw new InvalidOperationException("Only IList<T>, List<T> and ICollection<T> are supported");
        }

        var type = target.GetType();
        object proxy = _ProxyGenerator.CreateClassProxyWithTarget(type,
            new[] { typeof(IChangeTrackableInternal), typeof(IChangeTrackable<T>), typeof(IChangeTrackingManager), typeof(IComplexPropertyTrackable), typeof(ICollectionPropertyTrackable), typeof(IEditableObject), typeof(System.ComponentModel.INotifyPropertyChanged) },
            target,
            _Options,
            new NotifyPropertyChangedInterceptor<T>(),
            new ChangeTrackingInterceptor<T>(status),
            new EditableObjectInterceptor<T>(notifyParentListItemCanceled),
            new ComplexPropertyInterceptor<T>(makeComplexPropertiesTrackable, makeCollectionPropertiesTrackable),
            new CollectionPropertyInterceptor<T>(makeComplexPropertiesTrackable, makeCollectionPropertiesTrackable));
        return (T)proxy;
    }

Configurability

Hello and thanks for this great project.

It would be nice to have a non-static version of this library exposed as interface (i.e. IChangeTrackingFactory) that exposes the AsTrackable, CastToIChangeTrackable, CastToIChangeTrackableCollection etc. methods.

This would enable control over the generated proxy, adding additional interceptors and some more.
Besides, it would then be allow DI of a pre-configured proxy-factory singleton via the IoC container in MVC or MVVM scenarios.

please implement inotify property change

please implement inotify property change.
so when binding to a grid it will automatic pick up changes(E.X. when undoing changes. the grid will not pickup changes
)

Cost much more memory

33328 Tracking objects will create 33328 Proxy , Costed much more memory .How about use Singleton mode ?
image

Not allowing primatives in a collection is a pain point

Something like this would be valuable.

IList<string> fooList = new List<string>();
IList<string> trackableFooList = fooList.AsTrackable();
trackableFooList.Add("Hi");
var trackable = trackableFooList.CastToIChangeTrackableCollection();

foreach (var addedItem in trackable.AddedItems)
{
    Console.WriteLine("ADDED: " + addedItem);
}

Console.ReadKey();

Items added to trackable collections from withing the proxy don't register in added/removed/changed items

This is a big one for us as we are doing DDD and a lot of the interactions with the object would be from inside the object itself.

See the following code. I would expect the output to be
Added: Fido
Added: Fluffy

But all that shows is
Added: Fluffy

public class PetOwner
{
public virtual IList Pets { get; private set; }

    public PetOwner()
    {
        Pets = new List<Pet>();
    }

    public virtual void AddPet(string name)
    {
        Pets.Add(new Pet {Name = name});
    }
}

public class Pet
{
    public virtual string Name{ get; set; }
}    

class Program
{
    static void Main(string[] args)
    {
        var petOwner = new PetOwner();
        var trackablePetOwner = petOwner.AsTrackable();


        // !!! Fido DOES NOT appear in AddedItems !!!
        trackablePetOwner.AddPet("Fido");


        // !!! Fluffy shows as added in the AddedItems collection !!!
        trackablePetOwner.Pets.Add(new Pet { Name = "Fluffy" });

        foreach (var pet in trackablePetOwner.Pets.CastToIChangeTrackableCollection().AddedItems)
        {
            Console.WriteLine("Added: " + pet.Name);
        }

        Console.WriteLine("Done...");
        Console.ReadKey();

    }
}

Trying to create a generic method to get a Dictionary of "UpdatedPropertyNames as Keys" and "UpdatedPropertyValue as Value".

I am trying to create a generic method PublishChangeTrackerMessage which can take 2 objects of similar type and find differences between those 2 objects.
Here is the class that I created. Any class that extends BaseModel and implements IModel could be passed to the method PublishChangeTrackerMessage.

I am able to create the dictionary of PropertyNames and the NewValue for simple classes. If my Class has complex Property i.e. in the below example Models1 class has ChildModels then want to create the dictionary of 'ChildModels' => [new list of all addedItems, ChangedItems, DeletedItems].

When I call CastToIChangeTrackableCollection I am getting Unable to cast object of type 'System.Collections.Generic.List1[System.Object]' to type 'ChangeTracking.IChangeTrackableCollection1[System.Object]'.

Due to Generic types and type conversions I am getting this Casting error. Is there a solution to create the changes elements Dictionary in a different way?

`using ChangeTracking;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
namespace ChangeTracker
{
public abstract class BaseModel : IModel
{
public virtual int Id { get; set; }

    public virtual DateTime UpdatedDateTime { get; set; }

    public void Update(BaseDbModel updatedModel)
    {
        this.Id = updatedModel.Id;
        this.UpdatedDateTime = DateTime.UtcNow;
    }
}
public interface IModel<T1>
{
    public void Update(T1 updatedModel);
}

public class Model1:BaseModel, IModel<Model1>
{
    public virtual string Name { get; set; }
    public virtual int[] intArray { get; set; }
    public virtual IList<ChildModel> childModels { get; set; }
    public void Update(Model1 updatedModel)
    {
        this.Name = updatedModel.Name;
        this.intArray = updatedModel.intArray;
        childModels.Add(new ChildModel() { Id = 4, ChildName = "Added" });
    }
}
public class ChildModel : BaseModel, IModel<Model1>
{
    public virtual string ChildName { get; set; }
}
public class ChangeTrackService<T> where T:IModel<T>
{
    // Output of this method I am expecting Dictionary of "UpdatedPropertyNames as Keys"
    // and "UpdatedPropertyValue as Value".
    // IF the Updated Property is Collection of BaseModel Type then the dictionary Value should have a list of  AddedItems, DeletedItems & ChangeItems
    public Dictionary<string, object> PublishChangeTrackerMessage(T currentModel, T updatedModel)
    {
        var trackable = currentModel.AsTrackable();
        trackable.Update(updatedModel);
        var changedProperties = trackable.CastToIChangeTrackable().ChangedProperties;
        Dictionary<string, object> changes = new Dictionary<string, object>();
        foreach (var changedProperty in changedProperties)
        {
            // if Change property is not a collection
            if (!TrackableObjectCollection(trackable, changedProperty))
            {
                changes.Add(changedProperty, GetPropValue(trackable, changedProperty));
            }
            else  // If its a Trackable collection  
            {
                var changedCollection = GetPropCollectionValue(trackable, changedProperty);
                List<object> collectionChanges = new List<object>();

                // ISSUE: Unable to cast object of type 'System.Collections.Generic.List`1[System.Object]' to type 'ChangeTracking.IChangeTrackableCollection`1[System.Object]'.
                // Due to Generic Type T I have to use GetValue which return object and need to do TypeCasting
                var trackableCollection = changedCollection.CastToIChangeTrackableCollection();
                IEnumerable<object> addedItems = trackableCollection.AddedItems;
                IEnumerable<object> deletedItems = trackableCollection.DeletedItems;
                IEnumerable<object> changedItems = trackableCollection.ChangedItems;
                foreach (var addedItem in addedItems)
                {
                   //Do something
                }
                changes.Add(changedProperty, collectionChanges);
            }
            return changes;
        }
    }
    public object GetPropValue(object src, string propName)
    {
        return src.GetType().GetProperty(propName).GetValue(src, null);
    }
    public IList<object> GetPropCollectionValue(object src, string propName)
    {
        return ((IList<object>)src.GetType().GetProperty(propName).GetValue(src, null)).Cast<object>().ToList();
    }
    public bool TrackableObjectCollection(object src, string propName)
    {
        var propertyType = src.GetType().GetProperty(propName).PropertyType;
        if (propertyType.IsAssignableFrom(typeof(string)))
        {
            return false;
        }
        if (propertyType.GetInterfaces().Any(t => t.IsAssignableFrom(typeof(IEnumerable<>).GetGenericTypeDefinition())))
        {
            // Check if the arugment is object BaseModel Type or not.
            return propertyType.GetGenericArguments().Any(at => at.GetInterfaces().Any(ai => ai.GetGenericArguments().Any(t => t.IsAssignableFrom(typeof(BaseModel)))));
        }
        return false;
    }

}

}
`

Complex Properties with Read-Only Properties

Tracking a class with a complex property does not work if the complex property has a read only property by itself. Here is the Code for this:

    public class TestSubClass {
        public virtual string Name { get; set; }

        // Adding the following Line causes an error by tracking the SubClass
        public virtual double SomeWhat => 10.5f;

        public override string ToString() => Name;

    }

    public class ErrorClass {
        public virtual TestSubClass Test { get; set; }
        public ErrorClass()  {
            Test = new TestSubClass();
        }
    }

    [TestMethod]
    public void CauseError() {
        var error = new ErrorClass().AsTrackable();
        error.Test.Name = "Changed it";
        Assert.IsTrue(error.CastToIChangeTrackable().IsChanged, "ChangeTracking for SubClass failed");

    }

You can also just add the line:
public virtual string Zip => "12345";

Into the Address class of the ChangeTracking.Tests Project

It only tracks list-like collections

I'm already working on a pull request to give you a possible implementation to support also ISet<T> implementations as a supported trackable collection.

Are you already working on this or do you plan to do it yourself?

In my case, I'll have to workaround modifying your library directly to support ISet<T>. I guess that tomorrow I'll be ready to share my implementation.

Philosofical question + practical issues

Hi,
I was trying out your library yesterday and I'm pretty impressed but I have some questions.

I was trying a simple scenario, fetch items from SharePoint, map them to simple POCO object, make it trackable via AsTrackable() so I know what properties have changed, accept the initial version of data via AcceptChanges() and then insert it to a tracked collection of this objects.

var items = op
		.GetItems();
var trackableStock = new TrackableStock();

foreach (var item in items)
{
	var model = new TrackableStockItem();
	var trackedModel = model.AsTrackable();
	trackedModel.Id = item.Id;
	trackedModel.Name = item["Title"].ToString();

	trackedModel.CastToIChangeTrackable().AcceptChanges();
	trackableStock.Items.Add(trackedModel);
}

The problem with this approach, which I find correct because when there has to be a parameterless constructor then there has to be a way to accept the initial data, is that tracked collection then does not recognize these items as added but as unchanged.

So say I want to create a new item then I'll create a new POCO object, change it to tracked POCO object, set the data and accept the changes and then push it into the collection so when I'm saving all items I know exactly which are new/added and which are changed.

I mean there should be another way how to track which objects are added then using "IsChanged" property, in my opinion.

Another thing is that the code is getting quite talkative but this is perhaps because I'm not sure what I'm doing.

public class TrackableStock
{
	public IList<TrackableStockItem> Items { get; } = new List<TrackableStockItem>().AsTrackable();
}

...

trackableStock.Items[0].Amount = 15; // this works fine
trackableStock.Items.Remove(trackableStock.Items[1]); // this works fine as well
var stockItem = new TrackableStockItem() { Name = "Newly created item" }.AsTrackable(); // I would love to see this method like AsTrackable(initialValues: true);
stockItem.CastToIChangeTrackable().AcceptChanges(); // this feels akward to me if I have to do it every single time, one can get use to it but still..
trackableStock.Items.Add(stockItem); // does not tracked properly when the object has accepted changes

Do I have to Cast.. it to trackable object everytime I want to accept changes and undo them? That's a lot of code noise.

I would probably create a factory class for new POCO instances for example Is.Tracked(); just to make it simpler. or perhaps an extension method for an Object class, so I could create NewTracked() object anywhere.

I'm using the latest beta NuGET.

Json serializing __interceptor properties

Hello, I love this proyect... makes things easier ๐Ÿฅ‡
The problem now is that when I try to serialize the proxy object it serializes the __interceptor attributes example:

"__interceptors": [
    {
      "$id": "3",
      "IsInitialized": true
    },
    {
      "$id": "4",
      "IsInitialized": true
    },
    {
      "$id": "5",
      "IsInitialized": true
    },
    {
      "$id": "6",
      "IsInitialized": true
    },
    {
      "$id": "7",
      "IsInitialized": true
    }
  ],

How can I either stop json from serializing this extra properties OR access the target object to serialize it. Thanks ๐Ÿ‘

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.