force-net / DeepCloner

Fast object cloner for .NET
MIT License
518 stars 67 forks source link

What about clonning ExpressionTrees? #1

Closed Nadynho closed 6 years ago

Nadynho commented 7 years ago

Is there an option, or configuration to evaluate the expression trees before deep copying a property? e.g. LINQ statements as nested property underneath an object.

JSONConvert, when serializing, evaluates the properties, and, I would like to achieve the same thing.

force-net commented 7 years ago

Can you provide a sample? In current realization exact copy of object will be produced with all nested objects including expressions.

Nadynho commented 7 years ago

Hello force-net,

Yes, I can provide a sample, in a below code snippet.

I believe you know the concept of deffered evaluation, consider we have a class with a property on which we set a linq statement.

Car.Owners = DatabaseContext.Owners.Where(x => x.Owner = 1)

If I would evaluate the expression (ToList), then when I would execute the deep copy mechanism on the Car, it would work. If I am not evaluating the expression, I will receive following exception:

System.Runtime.InteropServices.InvalidComObjectException: An instance of the __ComObject type cannot be created unless the type has been obtained using GetTypeFromProgID or GetTypeFromCLSID.

To have an even better view of the problem, this might help:

at DeepObjectCloner_EntityWrapperWithoutRelationships`1_138(Object , DeepCloneState ) at Force.DeepCloner.Helpers.DeepClonerGenerator.CloneClassInternal(Object obj, DeepCloneState state) at DeepObjectCloner_RndvRq_8B2A17FB706BE7F40EEBE0BF239A40A0D17D9EDF42C1B81D653BA4A19487F8A4_137(Object , DeepCloneState ) at DeepObjectCloner_Entry_136(Entry , DeepCloneState )

Where RndvRq is part of an not evaluated lambda expression (deffered execution).

Nadynho commented 7 years ago

Also, I would like, if possible, to have the possibility to have a fallback if such an exception is thrown. But, I am not able to catch System.Runtime.InteropServices.InvalidComObjectException. Is there a way, in the framework to somehow provide the possibility to use a fallback if a deep copy operation fails?

Or maybe, can we use some kind of metadata to tell the framework how to evaluate certain types of expressions? (Like IQueryable using "ToList")

Later Edit:

I was able to catch the exception (InvalidComObjectException) and now I'm working for a fallback. But please let me know if you see a better aproach on this topic.

P.S.

The framework does its job really fast, good job! :)

force-net commented 7 years ago

Hello Nadynho,

I understood problem. It's not relate to expression trees itself, main problem with Entity Framework, which uses COM-resources. This is native resources and it copying can be problematic.

I'll try to investigate issue, is it better to create COM class in cloner, or I need to throw some exception to indicate this problem.

Or maybe, can we use some kind of metadata to tell the framework how to evaluate certain types of expressions? (Like IQueryable using "ToList") May be in some future versions. Currently, library implies exact copy of object, and materialization of IQueryable classes can lead to strange behavior (also, materialization of db queries causes real SQL execution and can be slow, also, it is not very correct place of code to execute SQL).

Nadynho commented 7 years ago

Hello Force-Net,

We believe it's not only related to EF, although, in my above scenario, you are right.

You can try this example when executing an "order by" on an expression, without evaluating the expression:

var type1 = typeof(object); var type2 = type1.Module.GetType("System.Collections.Generic.GenericComparer`1"); var constr = type2.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); var byteArray = constr.GetMethodBody().GetILAsByteArray(); var result = type2.Module.ResolveMethod(BitConverter.ToInt32(byteArray, 2));

A colleague of mine opened this thread on stackoverflow:

https://stackoverflow.com/questions/43975599/system-badimageformatexception-when-trying-to-resolve-constructor-of-system-coll

What about providing a way to execute the expression, this would solve both problems (the EF one and the one above)?

Thanks, Bogdan.

force-net commented 7 years ago

Sorry, I did not catch your sample. I see, this code is part of DeepClonerMsilHelper.IsConstructorDoNothing, but for this type this code should not be called (anyway, there are catch to swallow similar errors.

I found issue when I try to clone such code

var q = Enumerable.Range(1, 5).Reverse().AsQueryable().OrderBy(x => x);
var q2 = q.DeepClone();
q2.ToArray(); // error here

And I'll try to fix this problem in some time, but I need to understand another possible problems.

force-net commented 7 years ago

In last release, I make some improvements to cloning. Now, EF queries should work.

Nadynho commented 7 years ago

Thank you force-net, we will give it a try and let you know it the problem has been solved.

We really appreciate what you've done.

FlorinH1 commented 7 years ago

Hello force-net,

We have tested the latest release, but the initial issue is still there. Here are some screenshots from a debug session where you can see what type of object it tries to clone: https://drive.google.com/drive/folders/0ByRz_Se6kSt1YkJmeGQzRmRoRzg

The "Users" property on ojbect "obj" is constructed somewhat like this:

var dbUsers = Enumerable.ToList(from x in DBCONTEXT where x.User < 10)
obj.Users = dbUsers.OrderBy(user => user.Name)

Now, the Users property is an unevaluated Ordered Lambda expression, and when we try to clone "obj", we get the exception from the screenshots when it reaches property "Users".

DeepCloner tries to clone the "comparer" of the Linq.OrderedEnumerable (screenshot p2.png). I don't think that's necessary because I think the "comparer" is "static" and it uses the "keySelector" to order the results.

We usually get that exception when we have Ordered Lambdas on any object, not necessarily related to EF. As you can see in my stackoverflow post, https://stackoverflow.com/questions/1635497/orderby-descending-in-lambda-expression , if I make a simple application and try to resolve that method (which is generic) it always breaks, but if I invoke it's base class then it works (which is not generic if I remember correctly). So, somehow the issue is in .NET, and maybe you could implement some of these suggestions:

Thank you for your help and work!

force-net commented 7 years ago

I'll check this issue again. It is really strange problem, because IsConstructorDoNothing method catches all exceptions and object creation is switched to another logic in this case. I tried to reproduce this problem earlier and did not reproduce it. But, I'll try to do this again.

FlorinH1 commented 7 years ago

Hello,

I had another look at my example and indeed, it works. I haven't looked past the exception point and stopped there with the debug session, and didn't noticed that try/catch to see what happens after. Sorry for that! Now I did the test again with a simple ordered lambda over a collection and another test with an ordered enumerable which goes to the database and it works in both cases.

One thing I noticed with the second example is that it takes about 3 seconds to complete the cloning; I think it's because of too many exceptions thrown in IsConstructorDoNothing for those "uncloneable" types. I cloned the same object 3-4 times and it took about 1 second. I was hoping that only the first time to take that long (until it constructs the dynamic methods for those types), but I guess it's always creating those cloning dynamic methods and that they are not cached or some caches are missing in some cases. For a normal object or objects that contain lambdas over existing collections it takes under 100ms to clone the first time and after that it takes <0ms. For that object with ordered enumerable which goes to the DB, it took around 3 seconds the first time and around 1 second afterwards (maybe it depends on the complexity of the enumerable?).

But this performance issue in this case could be investigated in a future release. For now, with your latest release, it's important the cloning works with EF and lambdas / expressions. Thank you for your great work and help! We will let you know if we encounter any other issues.

force-net commented 6 years ago

Sorry for delay with new release. In 0.10.2 release constructor checking was improved to reduce these exceptions.

sekulicb commented 4 years ago

Hi. Just wanted to leave my feedback. The issue is still here. Every time I use collection.where or singleordefault etc...I get this error.

Also, since my current app is desktop oriented, I have InkCanvas controls which works with StrokeCollection type in C# and I cannot DeepCopy that collection without an error.

Thanks

sgf commented 9 months ago

System.Runtime.InteropServices.InvalidComObjectException:“An instance of the __ComObject type cannot be created unless the type has been obtained using GetTypeFromProgID or GetTypeFromCLSID.”

If possible, hope to be able to accurately display which field/attribute is problematic.