Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Падает производительность при использования предиката для гибкой сортировки в LINQ запросе

#c_sharp #entity_framework #linq #expressions


Пытаюсь сделать LINQ запрос в котором можно менять поле (одно) по которому нужно
производить сортировку. Использование переменной предиката позволяет это

public void GetMyData(string sortFieldName)
{
    Func orderPredicate = null;
    switch (sortFieldName)
    {    
         case "FIELD_STRING":
             orderPredicate = x => x.FIELD_STRING;
         case "FIELD_INT":
             orderPredicate = x => x.FIELD_INT;
         case "FIELD_DATE":
             orderPredicate = x => x.FIELD_DATE;
         default: 
             orderPredicate = x => x.FIELD_DATE;
    }

    var queryResult = db.MyEntity.OrderBy(orderPredicate);

    //...some logic

}


Но резко (в десятки раз) падает производительность по сравнению с прямым заданием
условия сортировки.
Насколько понимаю, должно помочь Expression. Меняю тип переменной

Expression> orderPredicate = null;


Но в рантайме выдаёт исключение


  Не удалось привести тип "System.DateTime" к типу "System.Object". LINQ to Entities
поддерживает только приведение типов-примитивов моделей EDM или типов перечисления.


Как же быть? Ведь, если я буду использовать прямое задание условия

public void GetMyData(string sortFieldName)
{
    var query = db.MyEntity;
    var orderedQuery = query.OrderBy(x => x.FIELD_DATE);
    switch (sortFieldName)
    {    
         case "FIELD_STRING":
             orderedQuery = query.OrderBy(x => x.FIELD_STRING); break;
         case "FIELD_INT":
             orderedQuery = query.OrderBy(x => x.FIELD_INT); break;
         case "FIELD_DATE":
             orderedQuery = query.OrderBy(x => x.FIELD_DATE); break;
    }

    //...some logic

}


, мне будет трудно усложнить запрос дополнительными инструкциями Where, Include и т.д.
    


Ответы

Ответ 1



Проблема в том, что когда вы пишите вот так: Expression> orderPredicate; orderPredicate = x => x.FIELD_DATE; Компилятор делает вот так: var _param = Expression.Parameter(typeof(MyEntity), "x"); orderPredicate = Expression.Lambda>( Expression.Convert( Expression.Property(_param, "FIELD_DATE"), typeof(object) ), _param ); Вот на этот самый Expression.Convert EF и ругается. Для того чтобы не было преобразования - возвращаемый тип делегата обязан совпадать с типом свойства, что в свою очередь означает что общую переменную orderPredicate вы использовать не можете. Теперь как строить такие запросы. А строятся они очень просто - через накопление IQueryable: IQueryable q = db.MyEntity; switch (sortFieldName) { case "FIELD_STRING": q = q.OrderBy(x => x.FIELD_STRING); case "FIELD_INT": q = q.OrderBy(x => x.FIELD_INT); case "FIELD_DATE": q = q.OrderBy(x => x.FIELD_DATE); default: q = q.OrderBy(x => x.FIELD_DATE); } Никакого усложнения тут нет, полученный запрос можно точно также дополнять другими условиями: q = q.Where(x => x.Foo > 42); PS поскольку у вас в параметре sortFieldName передается имя свойства, построение выражения можно еще немного упростить. Да, класс Queryable не дает нам вызвать OrderBy с неизвестным заранее именем свойства - но никто не мешает "раскрыть" этот метод и работать непосредственно с IQueryable: IQueryable q = db.MyEntity; var entityParam = Expression.Parameter(typeof(MyEntity)); var propExpr = Expression.Property(entityParam, sortFieldName) q = q.Provider.CreateQuery( Expression.Call(typeof(Queryable), "OrderBy", new [] { entityParam.Type, propExpr.Type }, // Типы-параметры для метода Queryable.OrderBy<,> q.Expression, Expression.Quote(Expression.Lambda(propExpr, entityParam)) ) ); Также вместо того чтобы делать это вручную - можно использовать библиотеку System.Linq.Dynamic, скачав ее из nuget: q = q.OrderBy(sortFieldName);

Комментариев нет:

Отправить комментарий