#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
вызовами. Я пробовал разные варианты, и, как я упоминал в начале, это единственный способ , который я нашел, который дает желаемый результат.