#linq #exception #lambda #expression
#linq #исключение #лямбда #выражение
Вопрос:
И снова я сталкиваюсь с проблемой, на этот раз с LINQ Expression builder, и на этот раз я даже пытаюсь найти причину, по которой она не работает. У меня есть проект EF для базы данных с довольно большим количеством таблиц. Для этого конкретного случая я должен использовать 2 из них — DocHead и Contrag . MyService.metadata.cs выглядит так:
[MetadataTypeAttribute(typeof(DocHead.DocHeadMetadata))]
public partial class DocHead
{
// This class allows you to attach custom attributes to properties
// of the DocHead class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class DocHeadMetadata
{
// Metadata classes are not meant to be instantiated.
private DocHeadMetadata()
{
}
public string doc_Code { get; set; }
public string doc_Name { get; set; }
public string doc_ContrCode { get; set; }
//...
[Include]
public Contragent Contragent { get; set; }
}
}
[MetadataTypeAttribute(typeof(Contragent.ContragentMetadata))]
public partial class Contragent
{
// This class allows you to attach custom attributes to properties
// of the Contragent class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class ContragentMetadata
{
// Metadata classes are not meant to be instantiated.
private ContragentMetadata()
{
}
public string Code { get; set; }
public string Name { get; set; }
//...
Я беру некоторые документы, подобные этому:
IQueryable<DocHead> docHeads = new MyEntities().DocHead;
Затем я пытаюсь отсортировать их следующим образом:
docHeads = docHeads.OrderByDescending(x => x.Contragent.Name);
Все работает так, как я хочу. Я сортирую эти заголовки документов по имени объединенного контраргентного элемента. Моя проблема в том, что мне придется сортировать их по полю, заданному в качестве строкового параметра. Мне нужно иметь возможность написать что-то вроде этого:
string field = "Contragent.Name";
string linq = "docHeads = docHeads.OrderByDescending(x => x." field ")";
IQueryable<DocHead> result = TheBestLinqLibraryInTheWorld.PrepareLinqQueryable(linq);
К сожалению, TheBestLinqLibraryInTheWorld не существует (на данный момент). Итак, я настроил метод в качестве обходного пути.
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, SortField); // normally returns x.sortField
var exp = Expression.Lambda(prop, param); // normally returns x => x.sortField
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp); // normally returns sth similar to q.OrderBy(x => x.sortField)
return q.Provider.CreateQuery<T>(mce);
}
Обычно … да, когда дело доходит до собственных свойств класса DocHead — тех, которые имеют префикс doc_ . Катастрофа возникает, когда я вызываю этот метод следующим образом:
docHeads = docHeads.OrderByField<DocHead>("Contragent.Name", true); // true - let it be Ascending order
Чтобы быть более конкретным, исключение в заголовке выдается в строке 2 метода OrderByField():
var prop = Expression.Property(param, SortField);
В моем .edmx (модели) для таблиц DocHead и Contragent уже настроено отношение, которое выглядит следующим образом: от 0 ..1 до * .
Еще раз, у меня вообще нет проблем с написанием «статических» запросов. У меня нет проблем с созданием «динамических» с помощью метода OrderByField(), но только когда дело доходит до свойств класса DocHead . Когда я пытаюсь сделать заказ с помощью реквизита объединенного противоположного класса — происходит катастрофа. Любая помощь будет очень признательна, спасибо!
Комментарии:
1. Я никогда не думал, что LINQ меня троллит, но я часто испытываю это чувство с помощью XAML.
Ответ №1:
Проблема в том, что Expression.Property
метод не поддерживает вложенные свойства. Он делает именно то, что говорит — создает выражение, представляющее свойство, обозначаемое propertyName
параметром объекта, обозначаемого expression
параметром.
К счастью, его можно легко расширить. Вы можете использовать следующий простой Split
/ Aggregate
трюк в любое время, когда вам нужно создать выражение доступа к вложенному свойству:
var prop = SortField.Split('.').Aggregate((Expression)param, Expression.Property);
Комментарии:
1. Спасибо за ваше 100% рабочее решение! Ура! @Ed Plunket — Иногда, просто для развлечения, я заменяю слово «бросает» фразой «троллит меня», потому что «бросать» и «троллить» звучат очень похоже, когда произносятся. Честно говоря, я делаю все возможное, чтобы избежать написания запросов linq для данных типа «from t in tt select» — этот конкретный синтаксис действительно троллит мой разум. «Обычные» запросы с лямбдами всегда казались мне более дружелюбными.