When querying using Entity Framework Core, I am using expressions to convert to DTO objects, which works well for the object, and any child collections.
A simplified example:
Model:
public class Model
{
    public int ModelId { get; set; }
    public string ModelName { get; set; }
    public virtual ICollection<ChildModel> ChildModels { get; set; }
    // Other properties, collections, etc.
    public static Expression<Func<Model, ModelDto>> AsDto =>
        model => new ModelDto
        { 
            ModelId = model.ModelId,
            ModelName = model.ModelName,
            ChildModels = model.ChildModels.AsQueryable().Select(ChildModel.AsDto).ToList()
        };
}
Query:
dbContext.Models.Where(m => SomeCriteria).Select(Model.AsDto).ToList();
My question is about trying to find a way to do something similar for a child that is not a collection. If I have added to my model:
public AnotherChildModel AnotherChildModel { get; set; }
I can add a conversion in the expression:
public static Expression<Func<Model, ModelDto>> AsDto =>
    model => new ModelDto
    { 
        ModelId = model.ModelId,
        ModelName = model.ModelName,
        ChildModels = model.ChildModels.AsQueryable().Select(ChildModel.AsDto).ToList(),
        AnotherChildModel = new AnotherChildModelDto
        {
            AnotherChildModelId = model.AnotherChildModelId
        }
    };
But, I have not found a good way to avoid repeating this code every time that I need to convert the second child model to a DTO object. The expressions work for the main object and any child collections, but not for single entities. Is there a way to add the equivalent of a .Select() for a single entity?
There are several libraries which allows to do that in intuitive way:
LINQKit
[Expandable(nameof(AsDtoImpl))]
public static ModelDto AsDto(Model model)
{
   _asDtoImpl ??= AsDtoImpl() .Compile();
   return _asDtoImpl(model);
}
private static Func<Model, ModelDto> _asDtoImpl;
private static Expression<Func<Model, ModelDto>> AsDtoImpl =>
    model => new ModelDto
    { 
        ModelId = model.ModelId,
        ModelName = model.ModelName,
        ChildModels = model.ChildModels.AsQueryable().Select(ChildModel.AsDto).ToList(),
        AnotherChildModel = new AnotherChildModelDto
        {
            AnotherChildModelId = model.AnotherChildModelId
        }
    };
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .AsExpandable()
   .ToList();
UPDATE: For EF Core, LINQKit can be confugred globally and AsExpanding() can be omitted.
builder
    .UseSqlServer(connectionString)
    .WithExpressionExpanding(); // enabling LINQKit extension
NeinLinq - almost the same as in LINQKit
[InjectLambda]
public static ModelDto AsDto(Model model)
{
   _asDto ??= AsDto() .Compile();
   return _asDto(model);
}
private static Func<Model, ModelDto> _asDto;
private static Expression<Func<Model, ModelDto>> AsDto =>
    model => new ModelDto
    { 
        ModelId = model.ModelId,
        ModelName = model.ModelName,
        ChildModels = model.ChildModels.AsQueryable().Select(ChildModel.AsDto).ToList(),
        AnotherChildModel = new AnotherChildModelDto
        {
            AnotherChildModelId = model.AnotherChildModelId
        }
    };
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .ToInjectable()
   .ToList();
UPDATE: For EF Core, NenLinq can be confugred globally and ToInjectable() can be omitted.
builder
    .UseSqlServer(connectionString)
    .WithLambdaInjection(); // enabling NeinLinq extension
DelegateDecompiler - less verbose than others
[Computed]
public static ModelDto AsDto(Model model)
  => new ModelDto
    { 
        ModelId = model.ModelId,
        ModelName = model.ModelName,
        ChildModels = model.ChildModels.AsQueryable().Select(ChildModel.AsDto).ToList(),
        AnotherChildModel = new AnotherChildModelDto
        {
            AnotherChildModelId = model.AnotherChildModelId
        }
    }
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .Decompile()
   .ToList();
All libraries do the same thing - correct expression tree before EF Core processing. All of them need additional call to inject it's own IQueryProvider.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With