Избегание ВНЕШНЕГО ПРИМЕНЕНИЯ в проекциях Entity Framework

#c# #entity-framework #linq #automapper

#c# #entity-framework #linq #automapper

Вопрос:

У меня есть довольно простой объект Entity Framework, который выглядит следующим образом:

 public class Student
{
    public string Given { get; set; }
    public string Surname { get; set; }

    public ICollection<Address> Addresses { get; set; }
}
  

Я хотел бы использовать AutoMapper для сопоставления этого объекта с соответствующей плоской ViewModel, которая выглядит следующим образом:

 public class StudentViewModel
{
    public string Given { get; set; }
    public string Surname { get; set; }

    public string PhysicalAddressStreet { get; set; }
    public string PhysicalAddressCity { get; set; }
    public string PhysicalAddressState { get; set; }

    public string PostalAddressStreet { get; set; }
    public string PostalAddressCity { get; set; }
    public string PostalAddressState { get; set; }
}
  

Для этого я попробовал следующую конфигурацию сопоставления:

 CreateMap<Student, StudentViewModel>()
    .ForMember(dest => dest.Given, opt => opt.MapFrom(src => src.Given))
    .ForMember(dest => dest.Surname, opt => opt.MapFrom(src => src.Surname))
    .ForMember(dest => dest.PhysicalAddressStreet, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Physical).Street))
    .ForMember(dest => dest.PhysicalAddressCity, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Physical).City))
    .ForMember(dest => dest.PhysicalAddressState, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Physical).State))
    .ForMember(dest => dest.PostalAddressStreet, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Postal).Street))
    .ForMember(dest => dest.PostalAddressCity, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Postal).City))
    .ForMember(dest => dest.PostalAddressState, opt => opt.MapFrom(src => src.Addresses.FirstOrDefault(add => add.Type == AddressType.Postal).State));
  

Проблема в том, что когда я запускаю это сопоставление с использованием проекций:

 studentDbSet.Where(st => st.Id == studentId)
            .ProjectTo<TProjection>(_mapper.ConfigurationProvider);
  

Я получаю следующую ошибку:

Динамическая ошибка SQL Код ошибки SQL = -104 неизвестный токен — строка 14, столбец 2 ВНЕШНИЙ

Это ошибка Firebird, кажется, что при компиляции Linq в SQL генерируемый запрос включает OUTER APPLY в себя , что не поддерживается в Firebird.

Есть ли какой-либо способ переработать мою проекцию, чтобы избежать OUTER APPLY ?

Насколько мне известно, OUTER APPLY генерируется из FirstOrDefault() вызова. Есть ли другой способ, которым я могу написать Linq, чтобы избежать его использования?

Редактировать для пояснения: это ситуация, когда я не в состоянии изменить сущность или схему базы данных, поэтому предположим, что они являются неприкосновенными.

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

1. Я полагаю, что вам нужно полностью загрузить свои адреса в коллекцию, используя . Включить () или спроецировать его на набор адресов и только после этого сопоставить с вашими свойствами PhysicalAddress / PostalAddress.

2. @raderick спасибо, это то, чего я надеялся избежать, но, боюсь, вы можете быть правы…

3. Боюсь, вы можете загружать адреса только по отдельности. Разве нет лучшего поставщика запросов Firebird?

4. @GertArnold если есть, я бы хотел узнать об этом. В настоящее время об этом сообщается как об ошибке на веб-сайте отчета об ошибках Firebird, но помечено как «не исправит» … вздох.

Ответ №1:

Я думаю, что здесь в основе лежит проблема моделирования. Если вам нужен физический адрес, просто включите свойство PhysicalAddress в модель и поддерживайте эту связь. У вас все еще может быть коллекция адресов с типом. Похоже, вы выполняете «FirstOrDefault», что означает, что либо у вас может быть только один физический адрес, либо имеет значение только первый. Я предполагаю, что у вас может быть только один.

Так что просто возьмите один. В модели Student (и таблице Student) укажите FK для адресной таблицы «PhysicalAddress». Затем в тех местах кода, где вы сохраняете адреса, соответствующим образом обновите физический адрес. Помогает инкапсуляция дочерней коллекции, чтобы вы не могли выполнять какие-либо операции добавления / удаления.

Как только у вас есть отношение PhysicalAddress к студенту, эта проблема становится тривиальной, это просто обычное отображение.

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

1. Извините, ради этого вопроса предположим, что сущность и схема базы данных не могут быть изменены (они являются частью другого проекта, который я не контролирую). Я отредактирую свой вопрос, чтобы добавить это, поскольку в противном случае ваш ответ отличный, извините, что потратил ваше время впустую.

Ответ №2:

Вот единственный способ написания запроса LINQ, который позволяет избежать OUTER APPLY (не уверен, как это можно сопоставить AutoMapper , оставив эту часть для вас, если вам это действительно нужно):

 var query =
    from student in studentDbSet
    where student.Id == studentId
    from physicalAddress in student.Addresses.Where(a => a.Type == AddressType.Physical)
    from postalAddress in student.Addresses.Where(a => a.Type == AddressType.Postal)
    select new StudentViewModel
    {
        Given = student.Given,
        Surname = student.Surname,
        PhysicalAddressStreet = physicalAddress.Street,
        PhysicalAddressCity = physicalAddress.City,
        PhysicalAddressState = physicalAddress.State,
        PostalAddressStreet = postalAddress.Street,
        PostalAddressCity = postalAddress.City,
        PostalAddressState = postalAddress.State,
    };
  

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

1. Как бы я использовал это для проекции Entity Framework? Скажем, у меня было studentDbSet , я бы обычно проецировал его, используя: studentDbSet.Select([Expression goes here]) .

2. Я не понимаю комментарий. Проекция означает Select метод или select ключевое слово, следовательно, в приведенном выше запросе используется проекция. То, как это соотносится с синтаксисом метода, выходит за рамки вопроса. Исходная проблема вызвана FirstOrDefault вызовами. Я пробовал разные варианты, и, как я упоминал в начале, это единственный способ , который я нашел, который дает желаемый результат.