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

Dynamic Expression not working on dynamic objects

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

注意:我在 Whosebug 上搜索了 none 类似的问题都在使用 list of dynamic objects。

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

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 上。这是我尝试过的方法(请参阅以下代码中的 Not working 注释):

            ...
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);
            }
            ...
    dynamic obj = new ExpandoObject();        
    dictionary.Add("Name", "John");
    dictionary.Add("CreateDate", DateTime.Now);

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

为什么不直接使用动态对象而不是字典。 以下代码很有魅力:

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

我能够通过更改 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 等值类型相同)。