Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flattening changes to nested objects #41

Open
rtroberts opened this issue May 17, 2018 · 21 comments
Open

Flattening changes to nested objects #41

rtroberts opened this issue May 17, 2018 · 21 comments
Assignees
Labels

Comments

@rtroberts
Copy link

rtroberts commented May 17, 2018

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?

@mfidemraizer mfidemraizer self-assigned this May 18, 2018
@mfidemraizer
Copy link
Owner

Hey! May you give your desired output?

I believe I understand the issue but, anyways, give me that detail.

Thank you in advance!

@rtroberts
Copy link
Author

Hey! Thanks for the quick reply.

If an object in a collection was added or removed, I probably need to do a custom serialization of that object - which shouldn't be too bad. I would probably just override the ToString method on MemberAttribute and Comment above, and I can get there with the if (changedProperty.CurrentValue is IReadOnlyChangeTrackableCollection) line I demonstrated above.

If an object has changed in these collections of objects, I'm not quite sure how to dig into that. I'd like to have access to the name of the changed property and the before/after values just as I do with non-collection properties of the original, 'parent' object.

Does that make sense? I'm not sure if I'm explaining this very well.

@mfidemraizer
Copy link
Owner

I'll try to reproduce your case to answer your question. Anyway, if I'm not mistaken, TrackerDog should be able to give you property changes from any association, even from tracked collections, but I want to confirm this myself.

@rtroberts
Copy link
Author

Thank you very much! If I can assist further in any way, or answer further questions, please let me know. I appreciate the help.

@mfidemraizer
Copy link
Owner

@rtroberts No problem!

So I've coded this sample:

public class A
    {
        public virtual IList<B> B { get; set; }
    }

    public class B
    {
        public virtual string Text { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var config = ObjectChangeTracking.CreateConfiguration();
            config.TrackThisType<A>();
            config.TrackThisType<B>();


            var trackableObjectFactory = config.CreateTrackableObjectFactory();

            var a = trackableObjectFactory.CreateOf<A>();

            var b = new B { Text = "hello world" };
            a.B.Add(b);
            b = a.B[0];

            b.Text = "byeeeeeeee";
            
            // Has 1 property changed: B because we added an instance of A
            var changeTrackerA = a.GetChangeTracker();

            // Has 1 property changed: Text because we changed its value
            var changeTrackerB = b.GetChangeTracker();
        }
    }

And you want that both changeTrackerA and changeTrackerB changed properties could be exposed in a flat change set. Is this the problem?

Believe or not, but I thought that it was behaving that way but I was absolutely mistaken about that.

Please confirm if this is the problem so I can work on implementing the change. It shouldn't hard to implement since I see that 1-n associations (collections) aren't sharing same object change tracker and this is an issue!

@rtroberts
Copy link
Author

That's exactly correct, yes. I expected changeTrackerA to include the change to the B.Text property, as well as the addition of a new B.

@mfidemraizer
Copy link
Owner

@rtroberts Ok! I'll take a look. I expect to give you a new TrackerDog version with this feature very soon (few days, give me 2-3 days)!

Thank you for identifying this problem ;)

@mfidemraizer
Copy link
Owner

Hey, I'll try to address it this weekend. I couldn't find a room to implement the fix during this week. Sorry.

@mfidemraizer
Copy link
Owner

@rtroberts I've located the line on which I can fix the problem: CollectionPropertyInterceptor (line 37)

When an item is being added to a given collection the whole item isn't turned into a change trackable object using collection parent object's change tracker. I believe that changing that line to call another existing overload giving the whole existing change tracker will give the desired output.

@rtroberts
Copy link
Author

How interesting! Thanks so much for finding this so quickly! You're a rockstar.

@mfidemraizer
Copy link
Owner

@rtroberts No problem! And not a rockstar: I'm the author :D I can easily identify issues. Anyway, thank you again.

I need to solve another issue: I'm on Linux since some months and, anyway, even when I was on Windows full time, I was developing in a Win10 VM. When I wanted to run that VM I found that it's broken (Win10 is in the "automatic repair loop"). I'll try to fix this problem today so tomorrow I can run VS2017 since TrackerDog is distributed for .NET 4.5 and .NET Standard.

@mfidemraizer
Copy link
Owner

@rtroberts No way: I can't fix the VM.

I believe that since full .NET Framework can use .NET Standard libraries, maybe it's time to distribute TrackerDog as just a .NET Standard library.

That is, I could continue improving it using VSCode either in Windows or Linux.

It shouldn't be an issue since the project has a .NET Standard build already.

Sorry for the delay, is this urgent?

@mfidemraizer
Copy link
Owner

@rtroberts I've already migrated the entire project to just build a .NET Standard library and now it's a VSCode workspace.

Definitively, I expect this week to provide the fix to your issue.

@rtroberts
Copy link
Author

Nice! Yeah, I'm personally on .NET Core, so moving to .NET Standard makes sense to me.

I appreciate your help on this! Thank you.

@mfidemraizer
Copy link
Owner

@rtroberts Ok! I'm still on this issue, I'm having troubles to find room to invest in it, but I hope I'll be able to give you the fix this week...!

@mfidemraizer
Copy link
Owner

@rtroberts For now, I've been able to implement the change for this case:

var a = Factory.CreateOf<A>()
a.B.Add(new C());

var c = a.B.First();
c.Text = "hello world"

C shares the same change tracker than A and B.

I need to fix the thing in yet another case: when a collection already contains items and they're converted into change-trackable objects. In that case, I've found that change tracker isn't being shared.

Let's see if this weekend I can share with you an assembly here so you may test the fix in your own project. If it works as you expect, we publish a new TrackerDog version! ;-)

@mfidemraizer
Copy link
Owner

@rtroberts Argh. I believe that right now should work but I've encountered a side-effect when implementing the solution.

Say you've a class A and it appears associated to many classes tracked by the same change tracker.

Currently, ObjectChangeTracker stores property change trackings in a dictionary where keys are PropertyInfo and values the change tracking itself (current value, old value, has changed?, ...).

What happens with that above described case? Only one property of one instance gets tracked, because when TrackerDog adds a tracking to say A.Text, there's an existing one from another instance... I'm not sure if I'm explaining the problem so you can understand it.

I need to find a way to store trackings of same property of many different instances of the same type. It shouldn't be hard... I believe that this is the last frontier!

@rtroberts
Copy link
Author

Sorry I've been out of touch! It's been crazy busy these past couple weeks.

What can I do to help?

@mfidemraizer
Copy link
Owner

@rtroberts Hey, are you in Gitter? So we can discuss some points in real time.

@rtroberts
Copy link
Author

I am not, but I could sign up if it would help!

@mfidemraizer
Copy link
Owner

mfidemraizer commented Jun 29, 2018

@rtroberts Yes, please! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants