Динамическое выражение не работает на динамических объектах - PullRequest
0 голосов
/ 31 декабря 2018

Я хочу динамически применять предикаты к списку динамических объектов.Мое решение работает хорошо, когда я использую реальные объекты, но оно не работает с динамическими объектами, и я не могу понять, в чем проблема.

Примечание: я искал Stackoverflow, ни один из подобных вопросов не используетсписок динамических объектов.

У меня есть список динамических объектов, подобных следующему коду.Список содержит два динамических объекта, которые имеют два свойства (Name, CreateDate).Я использовал класс 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 голосов
/ 02 января 2019

Почему бы просто не использовать динамические объекты вместо словаря.Следующий код работает как charm:

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 "Сары".

Обновление

Что ж, с небольшой работой над этим это решение сработало для меня.

Я использовал 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));
}
0 голосов
/ 05 января 2019

Мне удалось воспроизвести проблему (после вашего ОБНОВЛЕНИЯ 2 , который дал мне идею), изменив ExpandoObject пример кода

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 преобразуется в перегруженный оператор строки ==, который правильно сравнивает строки по значению. То же самое для типов значений, таких как int, DateTime и т. Д.).

0 голосов
/ 02 января 2019
    dynamic obj = new ExpandoObject();        
    dictionary.Add("Name", "John");
    dictionary.Add("CreateDate", DateTime.Now);

попробуйте вышеуказанный код.Преобразование не требуется, и ExpandoObject должен позволять добавлять или удалять динамические объекты.

...