Dynamic linq: есть ли способ получить доступ к данным объекта по индексу?

Мне нужна фильтрация в памяти с помощью Dynamic Linq. Мои объекты имеют только индексатор:

public  object this[int index] { }

Доступ к моим данным выглядит следующим образом: объект [0], объект [1],...

Итак, мой запрос выглядит так:

// get FilterText from user at runtime
// eg. filterText can be: [0] > 100 and [1] = "wpf"
collection.AsQueryable().where(filterText);

Есть какой-либо способ сделать это?


person Schorsch    schedule 28.01.2012    source источник


Ответы (3)


У меня есть новости для вас.

На самом деле возможно... НО! (Да, есть но).

Я предполагаю, что вы используете динамическую библиотеку Linq. Затем вы должны использовать ключевое слово «it», чтобы иметь возможность указать текущий объект, к которому вы хотите применить операцию индексирования.

Однако... Возвращаемый тип данных вашего индексатора - объект, поэтому вы не сможете написать [0] > 100 и [1] = "wpf" из-за несоответствия типов данных.

Кроме того, даже если вы наследуете DynamicObject и добавляете свойства во время выполнения, эти свойства не будут разрешены во время выполнения динамической библиотекой linq в ее текущем состоянии. Вы просто получите, что поле или свойство не существует в типе xxx.

Для этого есть несколько решений, некоторые из которых вы можете принять как решение.

  • Одно уродливое решение, если у вас есть ограниченное количество типов данных, скажем, n (где n ‹ количество типов в .NET), вы можете использовать n индексаторов с сопоставлением параметров и получить нужный тип данных. Например, если у вас в основном целые числа и некоторые строки:

    it[0] > 100 AND it[1, "dummy"] = "wpf" //The dummy parameter allows you to specify return type.
    
  • Другое уродливое решение, Dynamic Linq, поддерживает использование методов ToString() и Convert, поэтому вы можете, например, написать тот же запрос, что и выше:

    Convert.ToDouble(it[0]) > 100 AND it[1].ToString() = "wpf".
    
  • Третье уродливое решение: вы можете использовать соглашение, в котором вы оборачиваете операторы таким образом, который говорит вам, как преобразовывать данные. Например, вы можете написать:

    it[0] > 100 AND it{1} = "wpf"
    

    И замените "it[" на "Convert.ToDouble(it[" и т.д....

Если я прав, я думаю, что вы также не можете использовать общий индексатор с библиотекой. И Convert.ChangeType в этом случае вам не поможет, так как возвращаемый тип по-прежнему является объектом.

Возможно, я или кто-то другой когда-нибудь перепишу библиотеку для поддержки таких вещей, но у меня нет на это времени в ближайшем будущем (несколько недель).

Что ж, извините, но я должен быть где-то через 15 минут, так что, надеюсь, нам придется принять решение получше позже!

телепортироваться на встречу

ОБНОВЛЕНИЕ:

Кажется, я нашел решение вашей (и моей) проблемы!

В библиотеке DLINQ вы можете добавить элемент в интерфейс(ы) IxxxSignatures для типа, с которым вы хотели бы иметь возможность работать.

Итак, я добавил (например) в IEqualitySignatures:

void F(Object x, Int32 y);

И изменил (в данном случае) метод ParseComparison в блоке else следующим образом.

left = Expression.Convert(left, right.Type);

И, не поверите, это сработало :)

Я не проверял все виды операций, так как я не добавлял сигнатуры для других типов и операций, но это должно быть довольно просто сделать!

ОБНОВЛЕНИЕ

Обновлены некоторые незначительные вещи выше..

Я еще немного поэкспериментирую с этим, и хотя это может быть и не самое красивое решение, вы можете сделать что-то вроде этого (DataObject — это просто DynamicObject с индексатором):

    [TestMethod]
    public void DynamicTest()
    {
        List<DataObject> dataObjects = new List<DataObject>();

        dynamic firstObject = new DataObject();
        dynamic secondObject = new DataObject();

        firstObject.dblProp = 10.0;
        firstObject.intProp = 8;
        firstObject.strProp = "Hello";

        secondObject.dblProp = 8.0;
        secondObject.intProp = 8;
        secondObject.strProp = "World";

        dataObjects.Add(firstObject);
        dataObjects.Add(secondObject);

        /* Notice the different types */
        string newQuery = FormatQuery("dblProp > 9.0 AND intProp = 8 AND strProp = 'Hello'");

        var result = dataObjects.Where(newQuery);

        Assert.AreEqual(result.Count(), 1);
        Assert.AreEqual(result.First(), firstObject);
    }

И какой-то метод форматирования, например (представьте, что я написал полный метод):

    public string FormatQuery(string query)
    {
        query = query.Replace('\'', '\"');

        string[] operators = new string[] { "<", ">", "!=", "<=", ">=", "<>", "=" };

        string[] parts = query.Split();

        for (int i = 0; i < parts.Length; i++)
        {
            if (operators.Contains(parts[i]))
            {
                parts[i - 1] = "it[\"" + parts[i - 1] + "\"]";
            }
        }

        return String.Join(" ", parts);
    }

Вместо этого мы могли бы, конечно, использовать метод-расширение.

Или... Мы могли бы поместить этот метод в библиотеку DLINQ, используя что-то вроде (хотя и не говорю, что это хорошая идея):

if (typeof(T).GetInterface("IDynamicMetaObjectProvider", true) != null)
    whereClause = whereClause.FormatQuery();

И проверьте, конечно, реализует ли тип индексатор строк, что-то вроде (здесь игнорируется атрибут IndexerName):

if (t.GetType().GetProperty("Item") != null)

что позволит «обычным пользователям» писать:

data.Where("dblProp > 9.0 AND intProp = 8 AND strProp = 'Hello'")

Что ж, может быть, это и нехорошо, что все это там есть, но суть вы поняли. Вы могли бы! :)

person Johan    schedule 28.01.2012
comment
В самом деле? Я не думаю, что вам нужно делать такие уродливые вещи, смотрите мой ответ. - person Krizz; 28.01.2012
comment
Да, я знаю. Я просто пытался подчеркнуть, что тип является объектом, и поэтому некоторые операции не применяются без преобразования. Было бы неплохо иметь неявное преобразование. Возможно, DLINQ мог бы посмотреть на значение правой стороны и преобразовать в него левую часть (я знаю, что он уже делает кое-что из этого, но библиотеки всегда могут сделать для вас больше :P). - person Johan; 29.01.2012
comment
Спасибо за этот отличный ответ. Теперь я понимаю предысторию и то, что возможно. Но я не очень доволен :(. Я пытался в этот день - person Schorsch; 30.01.2012
comment
Как вы описали, проблема заключается в преобразовании типов. Я не могу найти способ динамического изменения типа. Ни получить тип с отражением и преобразовать его. Итак, следующим шагом я попытаюсь получить элемент моей коллекции. И чем с отражением, я буду использовать индексатор this[n], чтобы получить все элементы и получить тип этих элементов. В операторе switch я могу добавить методы преобразования. Итак, я получаю кое-что как ваш второй пример. - person Schorsch; 30.01.2012
comment
что произойдет, если left не может быть преобразовано в right.Type? Исключение? - person Krizz; 31.01.2012
comment
Да, вы получите: Eine Ausnahme (first Chance) des Types System.InvalidCastException ist in Anonymous Hosted DynamicMethods Assembly aufgetreten. Проблема возникает, если ваше значение имеет тип float. DLINQ всегда будет преобразовывать число, которое находится в filterString, в двойное число. Это произойдет, когда запрос будет выполнен. Немного плохо увидеть ошибку позже. И нет подробного сообщения об ошибке для пользователя. Нет позиции и нет ожидаемых типов.... Кроме этого, это будет работать :). Спасибо за это большое обновление. Я был на том же пути, но нашел решение. - person Schorsch; 31.01.2012
comment
Извините, я имею в виду ... не нашел решения. - person Schorsch; 31.01.2012
comment
Йохан, я обновил ваш пост относительно ваших идей формата. Завтра буду работать над улучшением типа, если это возможно!? И проведите несколько тестов. Если это возможно и вас это интересует, мы можем обменять наш DLINQ. - person Schorsch; 01.02.2012
comment
Йохан, я вижу, что мое обновление было удалено. Очень жаль времени, которое я трачу на написание и форматирование. Но если вы заинтересованы, я отправлю его на ваш адрес электронной почты. Я думаю, что это было удалено, потому что это был ответ на ваше обновление формата, а не ответ/расширение исходной проблемы!? Возможно, мы должны открыть новую тему для этого. - person Schorsch; 01.02.2012
comment
@ Шорш и Йохан, то, что вы, ребята, обсуждаете здесь, как-то связано с dynamic объектами, как в другом вопросе Йохана? - person Krizz; 04.02.2012
comment
@Krizz нет, не было. Возможно, это похоже. Мы обсудим индексатор и возвращаемый тип object. - person Schorsch; 04.02.2012

Вы либо используете DynamicLinq http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx или вы строите свои деревья выражений, используя отражение. Многие люди предпочитают для этого библиотеку Dynamic Linq; Я никогда не использовал его сам, я использовал подход отражения.

person Icarus    schedule 28.01.2012
comment
Не уверен, кто дал вам -1. Это совершенно правильный ответ. +1 - person EBarr; 28.01.2012
comment
Спасибо, @EBarr. Мне было интересно, почему за меня проголосовали, хотя это правильно. - person Icarus; 29.01.2012
comment
Благодаря вашему комментарию. Я использую библиотеку DLinq. Теперь я расширил его, чтобы перевести [n] в it[n]. Но проблема в преобразовании типов. Смотрите мой ответ на другие утверждения. - person Schorsch; 30.01.2012

Вам просто нужно добавить к индексатору it, что эквивалентно в языке DynamicLinq this в C#.

Итак, вам просто нужно it[1] == "wpf".

Однако есть еще некоторые сложности из-за того, что ваш индексатор возвращает object. Для типов классов (включая string) у вас все в порядке, DLinq будет продвигать все по мере необходимости.

Однако для таких типов значений, как ints, вам придется сделать Int32(it[0]) > 10.

person Krizz    schedule 28.01.2012
comment
Нет, это не полный набор функций, как у меня [1].ToString(). В этом случае я также могу использовать =,›,‹. Окружить или добавить метод не проблема. Проблема состоит в том, чтобы получить правильный тип для добавления этого метода. Смотрите мой комментарий к первому ответу. - person Schorsch; 30.01.2012
comment
@Schorsch извините, я не понимаю, что вы имеете в виду. Не могли бы вы объяснить? Проблема состоит в том, чтобы получить правильный тип для добавления этого метода. - чтобы получить правильный тип чего? каким методом добавить? - person Krizz; 31.01.2012
comment
@Schorsch, я обновил свой ответ. Надеюсь, это полезно для вас! - person Johan; 31.01.2012
comment
@Krizz Спасибо за ваши мысли. Я имел в виду, что проблема состоит в том, чтобы получить реальный тип значения из моей модели. Потому что результатом this[n] или it[n] является объект. Но у Йохана есть отличные решения, чтобы справиться с этим. - person Schorsch; 01.02.2012