Dynamic Linq — выполнение запроса к объекту с элементами динамического типа.

Я пытаюсь использовать динамический запрос linq для извлечения IEnumerable‹T› из коллекции объектов (Linq to Object), каждый из объектов в коллекции имеет внутреннюю коллекцию с другим набором объектов, где хранятся данные, эти значения доступны через индексатор из внешней коллекции

Динамический запрос linq возвращает отфильтрованный набор, как и ожидалось, когда вы работаете со строго типизированными объектами, но мой объект хранит данные в элементе типа dynamic, см. пример ниже:

public class Data
{
    public Data(string name, dynamic value)
    {
        this.Name = name;
        this.Value = value;
    }

    public string Name { get; set; }
    public dynamic Value { get; set; }
}

public class DataItem : IEnumerable
{
    private List<Data> _collection;

    public DataItem()
    { _collection = new List<Data>(); }

    public dynamic this[string name]
    {
        get
        {
            Data d;
            if ((d = _collection.FirstOrDefault(i => i.Name == name)) == null)
                return (null);

            return (d.Value);
        }
    }

    public void Add(Data data)
    { _collection.Add(data); }

    public IEnumerator GetEnumerator()
    {
        return _collection.GetEnumerator();
    }
}

public class Program
{
    public void Example()
    {
        List<DataItem> repository = new List<DataItem>(){
            new DataItem() {
                new Data("Name", "Mike"),
                new Data("Age", 25),
                new Data("BirthDate", new DateTime(1987, 1, 5))
            },
            new DataItem() {
                new Data("Name", "Steve"),
                new Data("Age", 30),
                new Data("BirthDate", new DateTime(1982, 1, 10))
            }
        };

        IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"] == 30");
        if (result.Count() == 1)
            Console.WriteLine(result.Single()["Name"]);
    }

Когда я запускаю приведенный выше пример, я получаю: Оператор '==' несовместим с типами операндов 'Object' и 'Int32'

Являются ли члены динамические несовместимыми с запросами Dynamic Linq? Или есть другой способ построения выражений, которые будут правильно оцениваться при работе с элементами типа динамический.

Спасибо большое за вашу помощь.


person Darsegura    schedule 12.01.2012    source источник


Ответы (3)


Являются ли члены динамические несовместимыми с запросами Dynamic Linq? Или есть другой способ построения выражений, которые будут правильно оцениваться при работе с элементами типа динамический?

Оба могут работать вместе. Просто выполните преобразование в Int32, прежде чем выполнять сравнение следующим образом:

IEnumerable<DataItem> result = 
           repository.AsQueryable<DataItem>().Where("Int32(it[\"Age\"]) == 30");

Редактировать 1: При этом использование динамической привязки в связи с Linq в целом ограничено, поскольку динамические операции не разрешены в деревьях выражений. Рассмотрим следующий запрос Linq-To-Objects:

IEnumerable<DataItem> result = repository.AsQueryable().
                                                  Where(d => d["Age"] == 30);

Этот фрагмент кода не будет компилироваться по указанной выше причине.

Редактировать 2: В вашем случае (и в сочетании с Dynamic Linq) есть несколько способов обойти проблемы, упомянутые в Редактировании 1 и в исходном вопросе. Например:

// Variant 1: Using strings all the way
public void DynamicQueryExample(string property, dynamic val)
{
   List<DataItem> repository = new List<DataItem>(){
        new DataItem() {
            new Data("Name", "Mike"),
            new Data("Age", 25),
            new Data("BirthDate", new DateTime(1987, 1, 5))
        },
        new DataItem() {
            new Data("Name", "Steve"),
            new Data("Age", 30),
            new Data("BirthDate", new DateTime(1982, 1, 10))
        }
    };

    // Use string comparison all the time        
    string predicate = "it[\"{0}\"].ToString() == \"{1}\"";
    predicate = String.Format(whereClause , property, val.ToString());

    var result = repository.AsQueryable<DataItem>().Where(predicate);
    if (result.Count() == 1)
        Console.WriteLine(result.Single()["Name"]);
}

Program p = new Program();

p.DynamicQueryExample("Age", 30); // Prints "Steve"
p.DynamicQueryExample("BirthDate", new DateTime(1982, 1, 10)); // Prints "Steve"
p.DynamicQueryExample("Name", "Mike"); // Prints "Steve" (nah, just joking...)

or:

// Variant 2: Detecting the type at runtime.
public void DynamicQueryExample(string property, string val)
{
    List<DataItem> repository = new List<DataItem>(){
        new DataItem() {
            new Data("Name", "Mike"),
            new Data("Age", 25),
            new Data("BirthDate", new DateTime(1987, 1, 5))
        },
        new DataItem() {
            new Data("Name", "Steve"),
            new Data("Age", 30),
            new Data("BirthDate", new DateTime(1982, 1, 10))
        }
    };

    string whereClause = "{0}(it[\"{1}\"]) == {2}";


    // Discover the type at runtime (and convert accordingly)
    Type type = repository.First()[property].GetType();
    string stype = type.ToString();
    stype = stype.Substring(stype.LastIndexOf('.') + 1);

    if (type.Equals(typeof(string))) {
        // Need to surround formatting directive with ""
        whereClause = whereClause.Replace("{2}", "\"{2}\"");
    }
    string predicate = String.Format(whereClause, stype, property, val);

    var result = repository.AsQueryable<DataItem>().Where(predicate);
    if (result.Count() == 1)
        Console.WriteLine(result.Single()["Name"]);
}

var p = new Program();
p.DynamicQueryExample("Age", "30");
p.DynamicQueryExample("BirthDate", "DateTime(1982, 1, 10)");
p.DynamicQueryExample("Name", "Mike");
person afrischke    schedule 12.01.2012
comment
Тогда они не совсем «совместимы», не так ли? - person M.Babcock; 12.01.2012
comment
Спасибо, эти решения работают хорошо, когда мы заранее знаем тип значения во время выполнения, но как насчет того, когда запрос построен программно, мы действительно не будем знать, как преобразовать значения заранее. Есть ли другой способ построения выражения? - person Darsegura; 12.01.2012
comment
@ M.Babcock: Зависит от вашего определения «совместимости». - person afrischke; 12.01.2012
comment
Большое спасибо, оба варианта работают хорошо. Есть ли разница в производительности между обоими решениями? - person Darsegura; 12.01.2012
comment
Что, если val.ToString() оценивается как строка с " или `\` в ней? - person binki; 06.04.2015

Приведенный ниже код полезен для вас?

IEnumerable<DataItem> result = repository.AsQueryable<DataItem>().Where("it[\"Age\"].ToString() == \"30\"");

Но чтобы это работало, все ваши типы, которые могут быть назначены члену Value вашего класса Data, должны иметь полезную реализацию метода ToString.

person Patrick Koorevaar    schedule 12.01.2012

Вы пробовали it[\"Age\"].Equals(object(30))?

Такой, что:

IEnumerable<DataItem> result = 
    repository.AsQueryable<DataItem>().Where("it[\"Age\"].Equals(object(30))");

Редактировать: Обновлено, чтобы корректно приводить 30 к объекту.

person Seph    schedule 12.01.2012
comment
Это вызывает исключение. Выражение типа «System.Int32» не может использоваться для параметра типа «System.Object» метода «Boolean Equals (System.Object)». it[\"Age\"].Equals(object(30)) работает. - person svick; 12.01.2012
comment
Спасибо, это решение также работает хорошо. Любые соображения производительности между этим решением и теми, которые дает @afrischke - person Darsegura; 12.01.2012