Search code examples
c#dynamiclambdaexpression-treespredicate

Dynamic Expression not working on dynamic objects


I want to dynamically apply a predicates to a list of dynamic object. My solution is working well when I use actual objects but it does not work on dynamic objects and I can't figure out what is the problem.

Note: I searched Stackoverflow none of similar questions are using list of dynamic objects.

I have a list of dynamic objects like the following code. The list contains two dynamic object that have two properties (Name,CreateDate). I used JsonConvert class to create dynamic objects :

var lst = new List<dynamic>();

Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Name", "John");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();

dict.Add("Name", "sara");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();

As you see lst is a list of dynamic objects and have 2 items in it. Now I want to filter list to get the item with the name Jonh (p=> p.Name == "john")

To do this I had the following approach:

ParameterExpression pe = Expression.Parameter(typeof(object), "p");
CallSiteBinder name = Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(object),
                new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });

    var pname = Expression.Dynamic(name, typeof(object), pe);


    var right = Expression.Constant("John");
    Expression e2 = Expression.Equal(pname, right);
    var qu = Expression.Lambda<Func<dynamic, bool>>(e2, pe);


    var lst2 = lst.AsQueryable().Where(qu).ToList();// Count()==0 !

The lst2 should contain 1 item but it contains 0 items. But if I change the original list(lst) to a type that has a Name property (let's say List<Person>) it lst2 correctly have 1 item.

UPDATE: Even when I use ExpandoObject to create dynamic objects it still won't work :

        dynamic obj = new ExpandoObject();
        var dictionary = (IDictionary<string, object>)obj;
        dictionary.Add("Name", "John");
        dictionary.Add("CreateDate", DateTime.Now);

UPDATE 2: As pionted out in the comments ExpandoObject actually works and the problem is with SqlDataReader. Here are what I have tried (see Not working comments in the following code) :

            ...
List<dynamic> result = new List<dynamic>();
While(dr.Read()){
            dynamic obj = new ExpandoObject();
            var dictionary = (IDictionary<string, object>)obj;
            dictionary.Add("Name","John"); // <= this works fine
            // dictionary.Add("Name",dr["Name"]); // <= Not working
            // dictionary.Add("Name",dr["Name"].ToItsType()); // <= Not working
            // dictionary.Add("Name",dr["Name"].ToString()); // <= Not working
            dictionary.Add("CreateDate", DateTime.Now);
            result.Add(obj);
            }
            ...

Solution

  • I was able to reproduce the issue (after your UPDATE 2 which gave me the idea) by changing the ExpandoObject example code

    dictionary.Add("Name", "John");
    

    to

    dictionary.Add("Name", new string("John".ToCharArray()));
    

    to avoid constant string interning, which lead us to the issue in the dynamic expression code.

    The dynamic expression type is object, hence Expression.Equal resolves to object operator ==, i.e. ReferenceEquals. That's why the example is working with constant strings and not with runtime created strings.

    What you need here is to use actual property type. So simply cast (Expression.Convert) the result of the dynamic property accessor to the expected type:

    var pname = Expression.Convert(Expression.Dynamic(name, typeof(object), pe), typeof(string));
    

    Now the expressions which refer to pname expression will resolve with the correct type (in this particular case, Equal will resolve to the overloaded string == operator which correctly compares strings by value. Same for value types like int, DateTime etc.).