Избежание внешнего применения в проекциях Entity Framework

У меня есть довольно базовая сущность 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, чтобы избежать его использования?

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

2 ответа

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

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

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

  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,
        };