Выражение.Свойство (параметр, поле) — это «троллинг» меня [System.ArgumentException] = {«Свойство экземпляра ‘B.Name ‘ не определено для типа A»}

#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» — этот конкретный синтаксис действительно троллит мой разум. «Обычные» запросы с лямбдами всегда казались мне более дружелюбными.