Giter Club home page Giter Club logo

trackerdog's Introduction

Welcome to TrackerDog!

trackerdog build badge project logo

What is TrackerDog?

TrackerDog works on seamlessly turning your .NET objects into change-tracked objects. It can track a full object graph, including collection properties.

Also, since version 2.2.0, TrackerDog works on both full .NET Framework and .NET Standard-compliant platforms like .NET Core or Xamarin!

How to install it?

TrackerDog is distributed as a NuGet package called TrackerDog. Follow this link to get installation instructions.

Getting started

Follow up this tutorial to become object change tracking expert!

If you're looking for API reference, follow this link.

Why TrackerDog?

When implementing some design patterns like Unit of Work in a very seamless way, it is required that the whole Unit of Work could track object changes and produce new or updated object persistence actions in order to let a given peristence layer work on the so-called operations.

Most enterprise applications work with object-relational mappers (OR/M) like Entity Framework and NHibernate which, as full OR/M frameworks, they can track persistent object changes and generate an underlying SQL INSERT, UPDATE or DELETE under the hoods thanks to their built-in change tracking.

At the end of the day, these OR/M frameworks implement change tracking turning your POCO class instances into proxies that intercept your POCO property changes and once you call the commit-specific method of their built-in unit of work they can persist changes to the database without your intervention.

So, what happens when you don't use an OR/M or, even worse: what happens when you don't use an OR/M but you want to implement an unit of work capable of tracking your POCO changes?

A good example of implementing a change tracking can be an unit of work to provide an abstraction layer over a NoSQL driver like Mongo, Couch, Cassandra, Redis... Even when some of them have some kind of atomically-executed set of commands, they will not track changes in your application layer. Actually there should be more possible use cases, but one of most important use cases is implementing a true domain-driven design architecture ensuring that a domain unit of work can track changes either when you use a regular data mapper (for which you won't use TrackerDog) or you want to implement repositories that return change-trackable domain objects!

In summary, TrackerDog turns your object graphs into interceptable object proxies that share a common change tracker which let you check what has changed in your objects or even accept or undo changes to a given object graph.

trackerdog's People

Contributors

kakone avatar mfidemraizer 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

trackerdog's Issues

ContractEception

Basic POCO class with List property where T is also basic POCO class
Added parent POCO type to TrackerConfiguration as per docs.
Then call On AsTrackable() extension method and get ContractException (as below)
Occurs irrespective of whether property in question is virtual or not.
Using Ver 1.1.3 from NuGet

Exception message;
Assertion failed: parentObjectProperty.PropertyType.GetGenericTypeDefinition().IsAssignableFrom(collectionImplementationDetail.Key) Trackable collection implementation of type 'System.Collections.Generic.IList1, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' cannot be set to the target property 'Castle.Proxies.MyClass.MyList' with type 'System.Collections.Generic.List1[[UserQuery+myObject, query_fnvjno, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. This isn't supported because it might require a downcast. Please provide a collection change tracking configuration to work with the more concrete interface.

Read-only/write-only properties should be skipped from being tracked by default

@FranckDepoortere has identified this issue on #7

When you want to track changes of types (either from interfaces or classes) that you don't own the source code or maybe you've tons of them, and you just want to track them all, and some of these types may not have all properties virtual and with a getter and setter, TrackerDog will fail on this use case with an exception (actually I didn't know that it was throwing an exception).

I understand that any property that has only a getter or a setter should be skipped from any tracking logic within TrackerDog.

How to implement DTOs avoiding an explicit dependency to TrackerDog

@gtmacgregor has raised a question in issue #19:

I want to have a lightweight readonly version of the DTO which does not rely on external dependencies (TrackerDog) so to do this I have an interface which is readonly (no setters). The implementation of that interface contains internal setters for each property. Then I created a mutable implementation which derives from the readonly version exposing those internal setters as public. If I only had one implementation which was read write then I could not force the end users of the library to get a tracked version to make changes.

I've moved here to be able to close #19 and discuss his concerns here :)

Xamarin

Unable to install into my Xamarin PCL (profile 44) project.

metadata question

How do i compare the metadata from GetTrackableTypes() with the changeTracker.ChangedProperties? I am trying to get the full path of a property starting from A (i.e. A.B.C.Description).

var patch = new JsonPatchDocument<TModel>();
                var changeTracker = this.GetChangeTracker();
                var metadata = ChangeTrackingConfig.Value.GetTrackableType(typeof(TModel));
                foreach (var changedProperty in changeTracker.ChangedProperties)
                {
                    var property =
                        metadata.ObjectPaths.FirstOrDefault(t => t.Property.Name == changedProperty.PropertyName);
                    if (property != null)
                    {
                        patch.Operations.Add(new Operation<TModel>("replace",
                            $"/{property.Path.ToLower().Replace(".", "/")}",
                            null,
                            changedProperty.CurrentValue));
                    }
                    
                }
                return patch;

How to track just the list inside a class?

I am creating a custom DbContext class which requires a List of entities that I want to track. I want to attach tracker only to the list but the samples in documentation say that the tracker needs to be configured for the containing class as well. Here is how my code looks at the moment, which is not ideal.

    public class CustomDbContextBase<T> : IUnitOfWork where T : Entity
    {
        public CustomDbContextBase()
        {
            // no op: Required for change tracking.
        }

        protected CustomDbContextBase(string endpointUrl, string databaseId, string masterKey, string collectionId, IMediator mediator)
        {
             // Initialize change tracking
            var config = ObjectChangeTracking.CreateConfiguration();
            config.TrackThisType<CustomDbContextBase<T>>(t => t.IncludeProperty(a => a.TrackedEntities));
            var objectFactory = config.CreateTrackableObjectFactory();
            this.TrackedEntities = objectFactory.CreateOf<CustomDbContextBase<T>>().TrackedEntities;
        }

        public virtual IList<T> TrackedEntities { get; set; }
...
    }

How can I attach tracker to just the TrackedEntities list and not the CustomDbContextBase?

Cannot track dictionary property changes

In current version there's a bug. While TrackerDog can track changes of collection properties, when a property is IDictionary<TKey, TValue, it'll throw an exception.

Contradictory validation in TrackableCollectionConfiguration.ReplaceImplementation

It looks like the checks for IsGenericTypeDefinition and IsAssignableFrom contradict each other. The former ensures that an open generic type is provided for the interface, while the latter will only work with closed generic types.

Trying to replace an existing collection (that should be allowed) implementation demonstrates this:-

config.Collections.ReplaceImplementation(
    typeof(ICollection<>), 
    typeof(List<>), 
    typeof(TrackerDog.CollectionHandling.DefaultCollectionChangeInterceptor<>));

Class with read-only properties throwing exceptions cause problems

Copy-pasted from issue 14 from a comment of @FranckDepoortere

The factory cannot build the object because of this property. My explanation is below:

    public string ObjectBuilder
    {
        get
        {   // In my case, this exception can exist for differents reasons: 
            // we need to keep former implementation to stay compliant 
            // to a previous interface
            // Sometime, we dont have implemented the interface yet.

            throw new NotImplementedException();
        }
    }

DTO of graph change data

Please create a contract that will enable transmitting object-change data of a graph in a hierarchical, yet enumerable manner.

Any DTO would be sufficient, but to take it a step further it can be made complex. Here are some examples:

  • Create 2 contract types
    • Contract of change data from client to server
    • Contract of changes that were applied (such as IDs etc.) or ignored (invalid or orphaned entities etc.)
  • Minify DTO size
    • Omit unchanged fields
    • Do include ID fields and some others by naming convention / config, omit them when 0/null/new
    • Each row should have a unique ID to be recognize back in the client when there are issues
  • A means of swallowing returned data from server, and undo/throw/update according to what's received from server

Supplied to server (json?):

Each row should contain object, desired action, and new values if any.

  • Person [unchanged]
    • Id 1
    • FirstName [changed] (newvalue)
    • Phones
      • Phone [new]
      • Id 0
      • Phone
        • Id 1
        • Number [changed] new value
    • Phone [deleted]
      • Id 2
  • Person with phones [deleted]
    • Id 2

Server will perform
Returned from server [desired action]{result}(values?)

  • Person [unchanged][none]
    • Id 1
    • Phone [new]
      • Id {changed} (3)
    • Phone
      • Id 4
      • Number [changed]{rejected}
  • Person [deleted]{rejected}

Calling ContainsKey on a change-tracked dictionary throws an exception

When a property of type IDictionary<TKey, TValue> is called once it's tracked for changes, and I call ContainsKey method on the whole dictionary, TrackerDog will throw an exception.

This is because TrackerDog still doesn't support dictionary change tracking at all: it just supports detecting changes on the dictionary but it can't store which key value-pairs have changed, and there's no Castle DynamicProxy dictionary-specific interceptor for now.

Exception in PropertyHasChanged()

I'm getting

ArgumentException: Key was already added
in
System.Collections.Generic.Dictionary2.Insert(TKey key, TValue value, Boolean add) v System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func2 keySelector, Func2 elementSelector, IEqualityComparer`1 comparer)
v TrackerDog.ObjectChangeTrackingExtensions.GetPropertyTracking(Object some, String propertyName)

target.GetChangeTracker().ChangedProperties shows correctly changed property info.

I'm posting here to check if I'm doing something wrong.
Example and Source debugging will follow.

Get Root Object of Tracker Graph

I'm implementing the new Changed event in my application.

Using the example in our other discussions, if I do a.B.C.Text = "new text" the event fires.
And the event args TargetObject is C as expected.
What I need to do now is find C's root object i.e. a

I see there is Target property on the DeclaredObjectPropertyChangeTracking class but it is internal, so for now I've added an ugly hack to access this property via reflection so I can determine the root element. i.e.

var trackedGraphInstances = e.PropertyChangeTracking.Tracker.Select(t => t.GetType().GetProperty("TargetObject").GetValue(t));
var root = GetRoot(trackedGrahpInstances); //custom method to find root.

Can I request another mod ?
Options could be
Add a Parent property to the proxy (not sure I like this)
Add a GetRoot() to the tracker (bit limited in functionality)
Add a FindAncestor() to the proxy with various overloads for specifying a type, instance, and getting of the root ?

Let me know if you think this should be included in TrackerDog or if there is an alternative way that I'm overlooking.
Thanks

Enum properties

Is it possible to get this working with tracked enum properties?

public class A {
public enum AEnum {
One = 1,
Two = 2
}

public virtual string Name { get; set; }
public virtual AEnum AVal { get; set; }
}

When I try to track the A class (or instance of) it failed with a Contract Exception.

Thanks

Glenn

Class that inherits from IEnumerable

I would like to track changes of a class that inherits from IEnumerable. I made a minimal example (Keep in mind, the actual class is completely different). But essentially I get an System.InvalidOperationException.

Example code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using TrackerDog;

namespace TrackerDogTestMin
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");
            var config = ObjectChangeTracking.CreateConfiguration()
                .TrackThisType<AList>()
                .TrackThisType<AWrapper>()
                .TrackThisType<IEnumerable<A>>()
                .TrackThisType<A>()
                .TrackThisType<IList<A>>()
                .TrackThisType<List<A>>();
            var trackableObjectFactory = config.CreateTrackableObjectFactory();

            var wrapperProxy = trackableObjectFactory.CreateFrom(new AWrapper());

            var changeTracker = wrapperProxy.GetChangeTracker();
            changeTracker.Changed += (sender, e) => { Console.WriteLine("Changed!"); };
            wrapperProxy.AList.AddSomeStrings();

            wrapperProxy.UndoChanges();
        }
    }

    public class AWrapper
    {
        public virtual AList AList { get; set; } = new AList();
    }

    public class AList : IEnumerable<A>
    {
        public virtual IList<A> TheActualList { get; set; } = new List<A>();

        public IEnumerator<A> GetEnumerator()
        {
            return TheActualList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void AddSomeStrings()
        {
            TheActualList.Add(new A {Number = 1});
            TheActualList.Add(new A {Number = 2});
        }
    }

    public class A
    {
        public virtual int Number { get; set; }
    }
}

Exception:


System.InvalidOperationException
  HResult=0x80131509
  Message=Sequence contains no elements
  Source=System.Core
  StackTrace:
   at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\C37C6F15-3581-4F8C-99B2-44BE77015445\a1\223e5c27\Enumerable.cs:line 1593
   at TrackerDog.TrackableObjectFactoryInternal.CreateForCollection(Object some, IChangeTrackableObject parentObject, PropertyInfo parentObjectProperty) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\64\edb7fa8e\TrackableObjectFactoryInternal.cs:line 130
   at TrackerDog.Mixins.ChangeTrackableObjectMixin.ToTrackableCollection(PropertyInfo property, IChangeTrackableObject parentObject) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\b6\82d00e1e\ChangeTrackableObjectMixin.cs:line 56
   at TrackerDog.Mixins.ChangeTrackableObjectMixin.StartTracking(IChangeTrackableObject trackableObject, ObjectChangeTracker currentTracker) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\b6\82d00e1e\ChangeTrackableObjectMixin.cs:line 91
   at Castle.Proxies.AWrapperProxy.StartTracking(IChangeTrackableObject trackableObject, ObjectChangeTracker currentTracker)
   at TrackerDog.TrackableObjectFactoryInternal.Create(Object some, Type typeToTrack, ObjectChangeTracker reusedTracker, Object parentObject, PropertyInfo propertyToSet, Object[] constructorArguments) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\64\edb7fa8e\TrackableObjectFactoryInternal.cs:line 80
   at TrackerDog.TrackableObjectFactoryInternal.Create[TObject](TObject some, ObjectChangeTracker reusedTracker, Object parentObject, PropertyInfo propertyToSet, Object[] constructorArguments) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\64\edb7fa8e\TrackableObjectFactoryInternal.cs:line 107
   at TrackerDog.TrackableObjectFactoryInternal.CreateFrom[TObject](TObject some) in C:\Users\user\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\B1F0BF37-70BB-4610-8DED-7C663E33A2F2\64\edb7fa8e\TrackableObjectFactoryInternal.cs:line 167
   at TrackerDogTestMin.Program.Main(String[] args) in C:\DEV\properychangeproxy\properychangeproxy\TrackerDogTestMin\Program.cs:line 23



Loss of child proxy

Came across another one today.
The issue arises with the use of ToUntrackedEnumerable
Here is the test code that repo's the issue

void Main()
{
    TrackerDog.Configuration.TrackerDogConfiguration.TrackTheseTypes(Track.ThisType<A>(), Track.ThisType<B>(), Track.ThisType<C>());  
    var a = new A();
    var b = new B();
    var c = new C();
    a.B = b;
    b.CSet = new HashSet<C>();
    b.CSet.Add(new C() { MyProperty=10 });
    b.CSet.Add(new C() { MyProperty=20 });
    var aa = a.AsTrackable();
    aa.B.CSet.Dump(); //CSet is a proxy
    var listOfTrackables = new List<A>();
    listOfTrackables.Add(aa);
    var n = listOfTrackables.ToUntrackedEnumerable(typeof(IList<>));
    aa.B.CSet.Dump(); //CSet is no longer a proxy. It is just a plain HashSet<>
}

public class A 
{
    public virtual B B { get; set; }
}

public class B
{

    public virtual ISet<C> CSet { get; set; }
}

public class C 
{
    public virtual int MyProperty { get; set; }
}

Can you confirm your end ?

Creating objects

Is there a way to create trackable objects without using the object factory? I am building an SDK and will be using trackerdog internally. My problem is that if the users of my SDK creates the object without using the object factory then it will not work. Any idea?

Flattening changes to nested objects

Say I have a tracked class with a few untracked properties, a few tracked properties, and a few tracked collections of objects - which are themselves trackable:

public class Document
    {
        public string id { get; set; }
        public string Name { get; set; }
        public string DocumentText { get; set; }
        public virtual IList<MemberAttribute> Attributes { get; set; } = new List<MemberAttribute>();
        public virtual IList<Comment> Comments { get; set; } = new List<Comment>();
        public virtual string DenyReason { get; set; }
        public virtual string RejectReason { get; set; }
        public virtual User Submitter { get; set; }
        public bool RequiresAgent { get; set; } = false;
        public IList<DocumentChange> DocumentHistory { get; set; } = new List<DocumentChange>();
}

Multiple tracked fields, additions or subtractions to tracked collections, or changes to objects within tracked collections can occur at one time. I'm trying to flatten all these changes to the simplest representation possible, and add them to the IList<DocumentChange> you see here.

Right now, I'm just iterating through each value in document.GetChangeTracker().ChangedProperties and getting the OldValue and CurrentValue properties - and calling ToString() on them. This is fairly naive but works great for the simple properties (like strings). Of course, it fails on tracked collections (literally printing out Castle.IListProxy or whatever).

Therefore I hacked in something like, if (changedProperty.CurrentValue is IReadOnlyChangeTrackableCollection) { *make up a lame value, like "Collection Changed"* }.

I think I could check the ChangedProperties against each concrete type I care about, like: if (changedProperty.GetType() == typeof(IList<Comment>)), and then cast the CurrentValue to the type I need, so I have access to all the TrackerDog collection methods. But that could quickly get out of hand for very large tracked objects.

Does TrackerDog support the kind of reflection I'd need to get at the changed values within nested, trackable objects? If I wanted to know that the Document object changed, and specifically that a property changed on a single MemberAttribute above, for example, would TrackerDog be able to get that information?

Notification from Object Graph

Hey Matias,

Looking for a way to get notification from a proxied object graph when some property (or collection) deep in the graph changes.
I know I can use the change tracker to look for changes, but I'm looking for more of an event to subscribe to.
One idea I had was to implement a custom interceptor which would bubble any INorifyPropertyChanged or ICollectionChanged events up (via a custom delegate) but I cant work out how to inject the new interceptor.
Am I on the right track, or would you recommend a different approach ?

IndexOutOfRangeException

Basic POCO entity with properties.
Added POCO type to TrackerConfiguration as per docs.
Then call On AsTrackable() extension method and get IndexOutOfRangeException.
If comment out the byte[] property, then the exception disappears.
Using Ver 1.1.3 from NuGet

Additional Public Properties on Proxied object, needed ?

Got another one that cropped up today.

I notice that a proxy of a tracked object gets all the properties of the underlying object, but in the process of making the object Trackable, the proxy also gets 2 additional public properties. ChangeTracker and CollectionProperties
In my application I was throwing the underlying object into WPF PropertyGrid. Now when I pass the proxy instead of the underlying object I get to see the extra 2 public properties.

What are your thoughts about this ?

  1. Should TrackerDog be adding public properties to a proxied object ? Or should the proxied object only republish those properties that are public in the underlying object.
  2. Do they need to be public ? Will TrackerDog still work if they are internal ?
  3. Or should the proxy be viewed as a subclass and therefore perfectly valid, and thus the responsibility of the consuming application to manage ?

Fire Changed event if the object changes back to unchanged

Is there a reason the Changed event doesn't fire if an object was changed (it fires) and is then changed back?

We have a visual indicator on the screen that shows the user if there are changes, so on the Changed event I have some code that looks like "HasChanges = Tracker.ChangedProperties.Any();" which works when a property changes, but when it changes back (testing with a simple checkbox) it doesn't fire.

In ObjectChangeTracker i see that Changed is only invoked if HasChanged is true. Is there any harm in firing this regardless? Or is there a better way to achieve what I want?

NullReferenceException when creating proxy with constructor initialized properties

When I try to create a proxy from a class that assigns a value to a property from the constructor a NullReferenceException is thrown.

I know that calling virtual member functions from the constructor can cause problems, but the property is only virtual because it is necessary for proxy creation.

I have added a new test to ProxyCreationTest.cs that reproduces this issue.

Access is denied

I created a simple test example in Net.Core 5.0 and TrackerDog 2.2.4 (full code below).

I am getting an "Access Denied" exception when calling CreateFrom as follows;

var typeA2 = trackableObjectFactory.CreateFrom(typeA);

Test Project Console App

using System.Collections.Generic;
using System.Collections.Immutable;
using TrackerDog;
using TrackerDog.Configuration;

namespace DeltaTester
{
    class Program
    {

        public class TypeC
        {
            public string Name { get; set; }
        }

        public class TypeB
        {
            public string Name { get; set; }
            public TypeC TypeC { get; set; }
        }
       
        public class TypeA
        {
            public TypeA()
            {
                TypeBs = new List<TypeB>();
            }
            public string Name { get; set; }
            public List<TypeB> TypeBs { get; set; }
        }
        static void Main(string[] args)
        {

            IObjectChangeTrackingConfiguration config = ObjectChangeTracking.CreateConfiguration();
            config.TrackThisType<TypeA>();

            var typeA = new TypeA();

            typeA.Name = "test";

            ITrackableObjectFactory trackableObjectFactory = config.CreateTrackableObjectFactory();
            
            // after we rehydrate the changes
            var typeA2 = trackableObjectFactory.CreateFrom(typeA);

            typeA2.Name = "testa";
            typeA2.TypeBs.Add(new TypeB { Name = "b1", TypeC = new TypeC { Name = "c1" } });

            IObjectChangeTracker changeTracker = typeA2.GetChangeTracker();

            IImmutableSet<IObjectPropertyChangeTracking> changedProperties = changeTracker.ChangedProperties;

            // Also you can get unchanged properties
            IImmutableSet<IObjectPropertyChangeTracking> unchangedProperties = changeTracker.UnchangedProperties;


        }
    }
}

Stack Trace from exception;

at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
at System.Reflection.Emit.TypeBuilder.CreateTypeInfo()
at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.CreateType(TypeBuilder type)
at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.BuildType()
at Castle.DynamicProxy.Generators.ClassProxyWithTargetGenerator.GenerateType(String name, INamingScope namingScope)
at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
at Castle.DynamicProxy.Generators.ClassProxyWithTargetGenerator.GetGeneratedType()
at Castle.DynamicProxy.DefaultProxyBuilder.CreateClassProxyTypeWithTarget(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyTypeWithTarget(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyWithTarget(Type classToProxy, Type[] additionalInterfacesToProxy, Object target, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
at TrackerDog.TrackableObjectFactoryInternal.Create(Object some, Type typeToTrack, ObjectChangeTracker reusedTracker, Object parentObject, PropertyInfo propertyToSet, Object[] constructorArguments)
at TrackerDog.TrackableObjectFactoryInternal.Create[TObject](TObject some, ObjectChangeTracker reusedTracker, Object parentObject, PropertyInfo propertyToSet, Object[] constructorArguments)
at TrackerDog.TrackableObjectFactoryInternal.CreateFrom[TObject](TObject some)
at DeltaTester.Program.Main(String[] args) in C:\src\applications\DomainServices\DeltaTester\Program.cs:line 46

Same value, object should be in unchanged state!!

Hi,
...
IObjectChangeTracker changeTracker = user.GetChangeTracker();
...
initially user age is 0 // default value
//change it here
user.Age = 41;
//change it again back to the old value 0
user.Age = 0;
When I check for property changes:
changeTracker.ChangedProperties.Any() //<=should return false since it equals to the old value!

idem form the complex and collections properties!
can guide me ?

Performance

I added TrackerDog to our project to track entity objects and I discovered that CreateFrom consumes about 2-3 seconds on a solid developer machine (release build).

I created a small example and made some measurements. I still see similar results. Is it known than TrackerDog consumes quite a bit CPU time? Why is this? Or am I using it wrong?

Example Code:

        public class Class0
        {
            public virtual Class1 Class1 { get; set; }
        }

        public class Class1
        {
            public virtual ISet<Class2> Class2s { get; set; }
        }

        public class Class2
        {
            public int Number { get; set; }
        }

        [TestMethod]
        public void PerfTest()
        {
            var class0 = new Class0
            {
                Class1 = new Class1
                {
                    Class2s = new HashSet<Class2>
                    {
                        new Class2{Number = 1},
                        new Class2{Number = 2},
                        new Class2{Number = 3},
                        new Class2{Number = 4},
                        new Class2{Number = 5},
                        new Class2{Number = 6}
                    }
                }
            };
            var proxy = TrackableObjectFactory.CreateFrom(class0);
        }

Results:
image

image

How to support my custom Collection type?

Hi, I've just started using TrackerDog (nice work btw!) and having a bit of trouble getting it to support my custom ObservableCollectionEx class that my model classes use. Here's some demo code:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using TrackerDog;

class Program
    {
        static void Main(string[] args)
        {
            //Initialise
            var config = ObjectChangeTracking.CreateConfiguration().TrackThisTypeRecursive(typeof(Car));
            //config.Collections.AddOrUpdateImplementation<IList<object>, ObservableCollectionEx<object>, DefaultCollectionChangeInterceptor<object>>();
            var tracker = config.CreateTrackableObjectFactory();

            //Create a Car
            Car car = new Car();
            car.Passengers = new ObservableCollectionEx<Passenger>();
            car.Passengers.Add(new Passenger { Name = "Frank" });

            //Start tracking
            car = tracker.CreateFrom(car);
        }
    }

    public class ObservableCollectionEx<T> : ObservableCollection<T>
    {
        public ObservableCollectionEx() : base()
        {
        }

        public ObservableCollectionEx(List<T> list) : base(list)
        {
        }
        
        public ObservableCollectionEx(IEnumerable<T> collection) : base(collection)
        {
        }
    }

    public class Car 
    {
        public virtual ObservableCollectionEx<Passenger> Passengers { get; set; }
    }

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

When the car = tracker.CreateFrom(car) line runs, it throws the exception:

Object of type 'Castle.Proxies.IList1Proxy' cannot be converted to type 'ConsoleApp37.ObservableCollectionEx1[ConsoleApp37.Passenger]

Looking at your code, it seems TrackerDog is creating a proxy IList collection which it tries to assign to Car.Passengers and fails since their types differ. I tried using AddOrUpdateImplementation to map IList to my ObservableCollectionEx class, but it produces the same error.

I noticed in your documentation, you advised us to use collection interfaces (IList, ICollection etc) in our models rather than concrete collection types. Unfortunately the model classes in my actual project are all dependent on my ObservableCollectionEx class. So, I can't easily change this.

Is there a way to make TrackerDog work with my custom collection type?

Thanks

I want to be able to work with multiple tracking configurations

TrackerDog 1.x configures change tracking using a static configuration through TrackerDogConfiguration.

While this works for simple scenarios, it would be great that configuration wouldn't be static anymore and I could build a change-tracked object factory from a given instance of TrackerDog's configuration.

Also, it would be nice that the whole configuration and the change-tracked object factory could be interfaces to be able to unit-test code and also integrate TrackerDog on projects using either manual or automatic dependency injection.

Override of .Equals() on proxy needed ?

Now I'm not sure if this is a bug, or the way things should be.
If I run the following code (the .Dumps are because I've copied direct from Linqpad.)

    TrackerDog.Configuration.TrackerDogConfiguration.TrackTheseTypes(Track.ThisType<A>());  
    ObjectIDGenerator gen = new ObjectIDGenerator();
    var o = new A();
    var olist = new List<A>();
    var trackable = o.AsTrackable();
    olist.Add(trackable);
    var oarray = new A[1];
    olist.CopyTo(oarray);
    olist[0].Equals(oarray.First()).Dump("Are Equal");
    o.GetHashCode().Dump("o hashcode");
    oarray[0].GetHashCode().Dump("o hashcode from array");
    bool n;
    gen.GetId(o, out n).Dump("o ID");
    gen.GetId(oarray[0], out n).Dump("o from array ID");

I get

Are Equal
False
o hashcode
103653
o hashcode from array
103653
o ID
1
o from array ID
2

But if I comment out .AsTrackable() i get

Are Equal
True
o hashcode
15193904
o hashcode from array
15193904
o ID
1
o from array ID
1

Now I understand why, but the question is should the proxy return True on a shallow copy ?
Given that the now 2 different proxies are pointing to the same underlying object I would say they should. What do you think ?

Internal cloning, collection and configuration retrieval issues

I've found some issues when TrackerDog tries to clone an object while turning a change-trackable object into a non-trackable one. Since I'm using JSON.NET to clone objects, some of them might be part of a very extensive object graph with circular references.

In the other hand, there are problems when turning empty trackable collections to non-tracked ones.

Top-level property for any changes

Apologies for opening another issue so soon. Is there a property at the change tracker level that will indicate if any changes have been made on any of the objects in the object graph? I'm hoping to use this for a "needs to be saved" indication for backing a UI.

I saw this issue, which I think would accomplish what I need, but it involves configuring a separate event on each tracked object, which is a little cumbersome.

I didn't see anything in the documentation, but maybe I'm missing something. Right now I'm basically doing something like this

entities.SelectMany(e => e.GetChangeTracker().ChangedProperties).Any();

which could be slow if there are a lot of entities or a lot of changes.

Collection change tracking not working

@gtmacgregor has found a problem when trying to track changes on some collection property on issue #21

Let's move the discussion to this new issue.

Description:

I am having an issue with getting collection change info. I tried to follow the examples to get the IReadOnlyChangeTrackableCollection by:

IReadOnlyChangeTrackableCollection c = (IReadOnlyChangeTrackableCollection)entity.Contacts;

Contacts returns an IList but I get a runtime error when the above is executed:

Unable to cast object of type 'System.Collections.Generic.List`1[Contact]' to type 'TrackerDog.IReadOnlyChangeTrackableCollection'.

An I missing something?

Thanks

Glenn

And:

@mfidemraizer

I did some digging and found in your tests when you create a trackable WhateverParent from an existing WhateverParent in CollectionItemsArePreservedWhenTurningParentObjectIntoTrackable the list are also proxied. Meaning before the object is trackable List and List2 are just Lists but after the CreateFrom the tracked object List and List2 types are now IList Proxy. This is not happening for my object:

Customer { private IList<Contact> mContacts = new List<Contact>(); ... public virtual IList<Contact> Contacts { get { return mContacts;} set mContacts = value.ToList();} }

So when I create a trackable Customer the type of Contacts is still the list and not the IList proxy. All the objects in the list are proxy objects as I have configured that property to be tracked as well as the Contact object.

Thanks

Glenn

Configuration: using annotations

Hi,

I spent this morning to write the configuration to track my objects.
The pb is that I’ve got one hundred of classes to configure, and I had to specify all properties to track, because of lot of exceptions launched by the Castle proxy. So I’ve got 1000 properties to specify..
My configuration is far too long and difficult to maintain now.
It’s a nightmare to manage.

I think one thing could improve this pb: using annotations.
This principle is very convenient to manage Json serialization for example
Same principle with DataContracts:
https://msdn.microsoft.com/en-us/library/ms733127(v=vs.110).aspx

// Example to configure JSON Serialisation
public class Categorie : Common
{

    [JsonProperty]
    public decimal? Value { get; set; }


   [JsonIgnore]
    public override string Version
    {
        get { throw new Exception("Do not use this property anymore please"); }
        set { // nothing to do }
    }

}

The configuration is convenient in small projects with simple objects, but cannot be used in big projects and complex properties with lot of history, legacy, specific behaviours.

A new way to configure a trackable object could be like this:

[Trackable]
public class Categorie : Common
{

    [TrackableProperty]
    public decimal? Value { get; set; }


    [TrackableIgnore]
    public override string Version
    {
        get { throw new Exception("Do not use this property anymore please"); }
        set { }
    }

}

Please, let me know what you think about this.

Franck

How to work with data binding in WPF?

I read the documentation, but I don't quite understand how to use TrackerDog in WPF, in short my application is a Master-Detail view, I have a Master view which is ObservableCollection<MyModel>, the Detail view can edit SelectedMyModel When I close the application, I can be reminded of the unsaved changes one by one.

The effect of closing the window is similar to Win11's Notepad.
动画1

Is it possible to track existing objects?

I might have a misunderstanding of how to use TrackerDog. If I have an existing object, say

User user = new User();
user.Name = "John";
user.Age = 32;

and then I try to add it to be tracked

trackedUser = trackableObjectFactory.CreateFrom(user);

then the resulting trackedUser object I get is missing the values that were previously set on it. I.e., Name will be "" and Age will be 0.

Is this the intended behavior? Is there any way to start tracking an existing object without resetting it? It's a little confusing because it seems like even though I pass in the user object to CreateFrom, the User parameterless constructor is still called. So I'm not sure what the point of that method is if that's the case. It seems like it could just be replaced by CreateOf<T>.

Tracking Interfaces

The following seems natural

     void Main()
    {
        TrackerDogConfiguration.TrackTheseTypes(Track.ThisType<A>());
        var alph = new Alpha();
        var t = alph.AsTrackable();
        t.MyProperty = 100;
        var tracker = t.GetChangeTracker(); //Contract Excetption not a trackable object    
    }

    public interface A
    {
        int MyProperty { get; set; }
    }

    public class Alpha : A
    { 
        public virtual int MyProperty { get; set; }
        public virtual bool HisProperty {get ; set;}
    }

but produces the noted exception.
I think that MyProperty should be tracked, but not HisProperty.
Could it even be possible for var t = Tackable.Of<A>() and then get the assistance from a dependency injection container to create the proxy ?

Issues when a trackable object should be unwrapped to a non-trackable one

I've found issues when calling some.ToUntrackable is the whole object implements IDictionary<TKey, TValue> but it's not intended to be used as a dictionary but it's a DynamicObject like a custom ExpandoObject wannabe.

Since I've implemented unwrapping using JSON serialization (i.e. I'm cloning objects), JSON properties are set as dynamic properties while type's properties should have prioritized over dynamic ones.

WPF Binding "HasChanges"

Hi

First off nice work with this library.

I am wondering if it is possible to bind to a Property that will tell me if there are changes on a object so that it could be displayed in the UI?

Example:
Got an object "User" on a TabItem and I want to show a * or any symbol if the Object "HasChanges"

Thanks for any hint

How to handle circular references

Some objects may have circular references. How should we handle this? Maybe, throwing an exception if someone is registering such an object? Or any idea on this?

I need to be able to clear collection changes

I've came to an edge case.

I'm tracking changes for an ISet<T> and, for some project requirement, I need to clear it and re-add all items in order to re-hash them internally in an implementation like HashSet<T>.

Currently, if I call ICollection<T>.Clear`, items will be cleared, but tracking information will still say that I've removed some items, and I've added some of them. In my particular case, if I had 3 items, clearing the collection ended up in having 6 added items (because they're the same items with a different hash code).

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.