Страницы

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

среда, 12 декабря 2018 г.

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

Пытаюсь сделать 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 и т.д.


Ответ

Проблема в том, что когда вы пишите вот так:
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);

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

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