动态表达式不适用于动态对象

时间:2018-12-31 08:15:39

标签: c# dynamic lambda expression-trees predicate

我想将谓词动态应用于动态对象列表。当我使用实际对象时,我的解决方案运行良好,但不适用于动态对象,并且我无法弄清楚问题出在哪里。

注意:我搜索了Stackoverflow,没有类似的问题在使用动态对象列表。

我有一个动态对象列表,例如以下代码。该列表包含两个具有两个属性(NameCreateDate)的动态对象。我使用JsonConvert类来创建动态对象:

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();

您看到的lst是动态对象的列表,其中包含2个项目。 现在,我想过滤列表以获取名称为Jonh(p=> p.Name == "john")的项目

为此,我采用以下方法:

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 !

lst2应该包含1个项目,但它包含0个项目。但是,如果我将原始列表(lst)更改为具有Name属性(比如说List<Person>)的类型,则lst2正确就有1个项目。

更新: 即使当我使用ExpandoObject创建动态对象时,它仍然无法工作:

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

更新2: 正如评论中指出的,ExpandoObject实际上有效,而问题出在SqlDataReader上。这是我尝试过的方法(请参见以下代码中的不起作用注释):

            ...
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);
            }
            ...

3 个答案:

答案 0 :(得分:2)

通过更改ExpandoObject示例代码

,我能够重现该问题(在您给我一个想法的 UPDATE 2 之后)。
dictionary.Add("Name", "John");

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

避免进行常量string的内部访问,这会导致我们在动态表达式代码中遇到问题。

动态表达式类型为object,因此Expression.Equal解析为 object 运算符==,即ReferenceEquals。这就是为什么该示例使用常量字符串而不是使用运行时创建的字符串的原因。

这里您需要使用实际的属性类型。因此,只需将动态属性访问器的结果转换(Expression.Convert到期望的类型:

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

现在,引用pname表达式的表达式将使用正确的类型进行解析(在这种特殊情况下,Equal将解析为重载的字符串==运算符,该运算符可以按值正确比较字符串。与intDateTime等值类型相同)。

答案 1 :(得分:1)

    dynamic obj = new ExpandoObject();        
    dictionary.Add("Name", "John");
    dictionary.Add("CreateDate", DateTime.Now);

尝试上面的代码。不需要进行转换,并且ExpandoObject应该允许添加或删除动态对象。

答案 2 :(得分:1)

为什么不仅仅使用动态对象而不是字典。 以下代码的工作原理类似于魅力:

var lst = new List<dynamic>();

dynamic obj = new ExpandoObject();
obj.Name = "John";
obj.CreateDate = DateTime.Now;
lst.Add(obj);

obj = new ExpandoObject(); // re-instantiate the obj if you want to differentiate from the List itself
obj.Name = "Sara";
obj.CreateDate = DateTime.Now.AddMonths(-10);
lst.Add(obj);

foreach (var item in lst)
{
    Console.WriteLine($"{item.Name} - {item.CreateDate}");
}

您甚至可以动态过滤列表

Console.WriteLine(lst.Find(i=>i.Name == "John").Name);

希望有帮助。

编辑

每次添加时,您都需要重新实例化dynamic obj。如果您不这样做,则您的列表将只有2个“ Sara”。

更新

在此方面的一些工作之后,这个解决方案就对我有用了。

我使用JsonConvert.DeserializeObject<ExpandoObject>(...)代替了dynamic。然后编写了LookUp方法来检查元素。我认为您的代码的第一个问题是将序列化的对象反序列化为dynamic而不是ExpandoObject。进行更正之后,对于强制转换字典和获取键值导向的值来说,并不难。

这是我的代码:

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<ExpandoObject>(JsonConvert.SerializeObject(dict)));
dict.Clear();

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

var res = LookUp(lst, "Name", "Sara");

然后是LookUp方法

public static object LookUp(List<dynamic> lst, string propName, object value)
{
    return lst.FindAll(i =>
    {
        var dic = i as IDictionary<string, object>;
        return dic.Keys.Any(key => dic[key].ToString().Contains(value.ToString()));
    });
}

此外,如果您不想将其转换为字典,可以使用以下替代方法:

private static object GetProperty(dynamic target, string name)
{
    var site =
        CallSite<Func<CallSite, dynamic, object>>  
          .Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, name, target.GetType(),
                new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}));
    return site.Target(site, target);
}

public static object LookUpAlt(List<dynamic> lst, string propName, object value)
{
    return lst.FindAll(i => GetProperty(i, propName).Equals(value));
}
相关问题