Can I reuse code for selecting a custom DTO object for a child property with EF Core?
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 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();
NeinLinq - almost the same as in LINQKit
[InjectLambda]
public static 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();
DelegateDecompiler - less verbose than others
[Compute]
public static 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
.