Dynamic Linq - выполнить запрос к объекту с членами типа «dynamic». - PullRequest
7 голосов
/ 12 января 2012

Я пытаюсь использовать динамический запрос linq для получения IEnumerable из коллекции объектов (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"]);
    }

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

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

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

Ответы [ 3 ]

2 голосов
/ 12 января 2012

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

Оба могут работать вместе. Просто сделайте преобразование в 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);

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

Edit 2: В вашем случае (и в сочетании с Dynamic Linq) есть несколько способов взломать себя вокруг проблем, упомянутых в Edit 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...)

или

// 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");
1 голос
/ 12 января 2012

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

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

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

1 голос
/ 12 января 2012

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

такой, что:

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

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

...