Динамичен linq: Има ли начин за достъп до обектни данни по индекс?

Имам нужда от филтриране в паметта с Dynamic Linq. Моите обекти имат само индексатор:

public  object this[int index] { }

Достъпът до моите данни е като: object[0], object[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 индексатори със съвпадение на параметри и да получите типа данни, който искате. Например, ако имате предимно int и някои низове:

    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 lib, като използваме нещо като (все пак не казвам, че е добра идея):

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 може да погледне стойността на дясната страна и да преобразува лявата страна в нея (знам, че вече прави част от това, но libs винаги може да направи повече за вас :P). - person Johan; 29.01.2012
comment
Благодаря за този отличен отговор. Сега разбирам предисторията и какво е възможно. Но не съм много доволен :(. Опитах се този ден - person Schorsch; 30.01.2012
comment
Както описахте, проблемът е преобразуването на типа. Не мога да намеря начин за динамична промяна на типа. Нито да получите типа с отражение и да го конвертирате. Така че следващата стъпка, която ще опитам, е да взема елемент от моята колекция. И след това с отражение ще използвам индексатора this[n], за да получа всички елементи и да получа типа на тези елементи. В команда за превключване мога да добавя методи за преобразуване. Така че получавам нещо като вашия втори пример. - person Schorsch; 30.01.2012
comment
какво се случва, ако left не може да се преобразува в right.Type? Изключение? - person Krizz; 31.01.2012
comment
Да, там ще получите: Eine Ausnahme (първият шанс) des Typs System.InvalidCastException ist in Anonymously 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
@Schorsch и Йохан, това, което обсъждате тук, свързано ли е по някакъв начин с dynamic обекти, както в другия въпрос на Йохан? - person Krizz; 04.02.2012
comment
@Krizz не, не е така. Може би е подобно. Обсъждаме индексатора и returntype 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] на него [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