Using .net-6 and EF Core 6, I want to define reusable projections using expressions so that I can centralize DTO mappings in one place.
Given an entity with relationships:
class Property {
public int Id {get; set;}
public List<Amenity> Amenities {get; set;}
public Address Address {get; set;}
}
class Amenity {
public int Id {get; set;}
public string Name {get; set;}
public string Value {get; set;}
}
class Address {
public int Id {get; set;}
public string Country {get; set;}
public string City {get; set;}
public string Street {get; set;}
}
And their DTOs:
class PropertyDto {
public int Id {get; set;}
public List<AmenityDto> Amenities {get; set;}
public AddressDto Address {get; set;}
}
class AmenityDto{
public int Id {get; set;}
public string Name {get; set;}
public string Value {get; set;}
}
class AddressDto{
public int Id {get; set;}
public string Country {get; set;}
public string City {get; set;}
public string Street {get; set;}
}
I can create a reusable projection expression:
public class PropertyDto {
...
public static Expression<Func<Property, PropertyDto>> Projection =
property => new PropertyDto{
Id = property.Id,
};
...
}
That I can use in any query's Select()
call as the projection expression, which EF will "visit" and translate into SQL to fetch only those columns I need:
DbContext.Set<Property>()
.Select(Property.Projection)
.ToListAsync();
If I want to reuse projections for Amenities I can create a Projection expression for AmenityDto
and do the following:
public static Expression<Func<Property, PropertyDto>> Projection =
property => new PropertyDto{
Id = property.Id,
Amenities = property.Amenities.AsQueryable().Select(Amenity.Dto).ToList(),
};
But if I want to do the same for Address
I can't use .Select()
to project it because it's not a collection.
public static Expression<Func<Property, PropertyDto>> Projection =
property => new PropertyDto{
Id = property.Id,
Amenities = property.Amenities.AsQueryable().Select(Amenity.Dto).ToList(),
Address = // how can I use AddressDto.Projection here?
};
The Address field expects an AddressDto. If I use a callback e.g. AddressDto.Projection(address)
EF will load the whole entity because it can't translate the method to SQL. After a lot of google I have only come across some articles discussing the use of .AsExpandable()
or [ReplaceWithExpression]
attribute to instruct EF to replace a method with an expression. As far as I can tell, none of these longer work in EF Core 6.0
Is there any way I can reuse projection expressions when projecting a single entity?