简体   繁体   中英

Avoiding OUTER APPLY in Entity Framework projections

I have a pretty basic Entity Framework entity that looks like this:

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

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

I'd like to use AutoMapper to map this entity to a corresponding flattened ViewModel that looks like this:

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

For this I've tried the following mapping configuration:

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));

The problem is, when I run this mapping using projections:

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

I get the following error:

Dynamic SQL Error SQL error code = -104 Token unknown - line 14, column 2 OUTER

This is a Firebird error, it seems that when compiling the Linq to SQL the query that is being generated includes OUTER APPLY , which is not supported in Firebird.

Is there any way to rework my projection to avoid the OUTER APPLY ?

To the best of my knowledge, the OUTER APPLY is generated from the FirstOrDefault() call. Is there another way I can write the Linq to avoid using that?

Edit for clarification: This is a situation where I am not in a position to be able to modify the Entity or the database schema, so assume that those are untouchable.

I think you have a modeling problem at the core here. If you need the physical address, just include a PhysicalAddress property on the model, and maintain that relationship. You can still have the collection of addresses with the type. It looks like you're doing "FirstOrDefault", meaning either you can only have one physical address or only the first matters. I'm guessing it's that you can only have one.

So just have one. On the Student model (and Student table), have a FK to the Address table, "PhysicalAddress". Then in the places in the code you maintain addresses, update the PhysicalAddress appropriately. Encapsulating the child collection so that you can't do just any add/remove operation helps.

Once you have a PhysicalAddress relationship on the Student, this problem becomes trivial, it's just a normal mapping.

Here is the only way of writing the LINQ query that avoids OUTER APPLY (not sure how that can be mapped with AutoMapper , leaving that part for you if you really need it):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM