zzzprojects / Eval-Expression.NET

C# Eval Expression | Evaluate, Compile, and Execute C# code and expression at runtime.
https://eval-expression.net/
Other
459 stars 86 forks source link

SelectDynamic.Distinct with anonymous type doesn't work as intended #19

Closed efronberlian closed 6 years ago

efronberlian commented 7 years ago

I am trying to use anonymous types in selectdynamic so that I can use .Distinct() for multiple field/column/member. But it seems there is an anonymous type mismatch between the anonymous type created with and without eval context.

If this is an incorrect way of using it, do you have a different way of approaching this? In the real case, Entity will be a large class with numerous members. And the user will input the names of the members to query from.

It is also important for me to be able to grab the values from the result with anonymous type afterwards.

Thank you.

System.InvalidCastException : Unable to cast object of type '<>f__AnonymousType0`2[System.Int32,System.String]' to type '<>f__AnonymousType1`2[System.Int32,System.String]'.
   at System.Linq.Enumerable.<CastIterator>d__94`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at CEM.Crunch.Testing.CastingExtension.CastToType[T](IEnumerable`1 item, T exampleItem) in C:\Projects\CEM.Crunch4Beta\CEM.Crunch.Testing\Sandbox.cs:line 93
   at CEM.Crunch.Testing.Sandbox.Something() in C:\Projects\CEM.Crunch4Beta\CEM.Crunch.Testing\Sandbox.cs:line 82
public class Entity
    {
        public int i;
        public string s;

        public Entity(int i, string s)
        {
            this.i = i;
            this.s = s;
        }
    }
            var source = new List<Entity>()
            {
                new Entity(0,""),
                new Entity(1,"a"),
                new Entity(1,"a"),
                new Entity(1,"ab"),
            };

            var list = source
                .SelectDynamic(entity => "new { A = entity.i, B = entity.s }") // prototype of anonymous type
                .Distinct()
                .ToList();

            var castedList = list.Distinct().CastToType(new { A = 0, B = "" }).ToList();

            Console.Write(list.Count);
            Console.Write(castedList.Count);
    public static class CastingExtension
    {
        public static List<T> CastToType<T>(this IEnumerable<object> item, T exampleItem)
        {
            return item.Cast<T>().ToList();
        }
    }
JonathanMagnan commented 7 years ago

You are right.

The anonymous created by our library is a type created on runtime while the second anonymous type created in the CastToType method is a type created on compile time (The type is part of the dll).

Unfortunately, we cannot use type created in compile time since we don't know about them.

I'm not if this solution can work in your scenario but one way to achieve this is casting the list to a dynamic time and selecting yourself the value:

var list = new List<int>() {1, 2, 3};

var list2 = list.SelectDynamic(x => "new { A = x, B = x + 1").Distinct().ToList<dynamic>();
var castedList = list2.Select(x => new {A = x.A, B = x.B}).ToList();

Let me know if this solution can work or not.

Best Regards,

Jonathan

efronberlian commented 7 years ago

There is going to be up to millions of objects in the list, so I would rather not use any dynamic types, since from what I can understand there is a significant overhead when using dynamic types. Is there another way of doing this without anonymous type maybe?

Btw. I just realized another problem, I have 2 supposedly identical operations, but I am getting different outputs:

Thanks for the prompt reply.

           var source = new List<Entity>()
            {
                new Entity(0,""),
                new Entity(1,"a"),
                new Entity(1,"a"),
                new Entity(1,"ab"),
            };

            var list1 = source
                .SelectDynamic(entity => "new { A = entity.i, B = entity.s }")
                .Distinct()
                .ToList();
            Console.WriteLine(list1.Count); // Outputs 4

            var list2 = source
                .Select(entity => new { A = entity.i, B = entity.s })
                .Distinct()
                .ToList();
            Console.WriteLine(list2.Count); // Outputs 3
JonathanMagnan commented 7 years ago

Thank you for reporting.

Our anonymous doesn't implement probably the IComparable interface or something like this. Every entity is unique no matter the value.

Honestly, I'm very surprised by the Output 3 result... I would have expected 4 as our anonymous type.

var list3 = source.Distinct().ToList(); // Outputs 4

Best Regards,

Jonathan

JonathanMagnan commented 7 years ago

Hello @efronberlian ,

The v2.4.5 has been released.

We implement the Equals method for anonymous type created on our side.

Additionally, we worked on how to solve your problem without dynamic and I'm happy to provide you a solution:

// CREATE your type
var type = new { A = 0, B = "" };

// REGISTER the anonymous type previously created
EvalManager.DefaultContext.RegisterType(type.GetType());

// USE an alias since we doesn't support type starting by "<>" from "<>f__AnonymousType"
EvalManager.DefaultContext.RegisterAlias("MyType", type.GetType().FullName);

var source = new List<Entity2>()
{
    new Entity2(0,""),
    new Entity2(1,"a"),
    new Entity2(1,"a"),
    new Entity2(1,"ab"),
};

var list = source
    .SelectDynamic(entity => "new MyType(){ A = entity.i, B = entity.s }")
    .CastToType(type)
    .Distinct()
    .ToList();

The solution may need some additional work in some case.

Let me know if that works correctly on your side.

Best Regards,

Jonathan

JonathanMagnan commented 6 years ago

Hello @efronberlian ,

I will close this issue since the fix has been released.

Best Regards,

Jonathan