Entity Framework Core enables querying using expressions to convert to DTO objects. This functionality works well for the object and any child collections, as exemplified by the provided 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() }; }
And the Query:
dbContext.Models.Where(m => SomeCriteria).Select(Model.AsDto).ToList();
However, the question arises: how can similar behavior be achieved for a child entity that is not a collection? For instance, if the Model class includes the property:
public AnotherChildModel AnotherChildModel { get; set; }
A conversion can be added to 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 repeating this code every time the second child model needs to be converted to a DTO object is undesirable. Is there an alternative to using .Select() for a single entity?
Several libraries offer an intuitive solution to this problem:
1. LINQKit:
With LINQKit, the .Select() operation can be applied to individual entities using the ExpandableAttribute:
[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 } };
The query can then be written as:
dbContext.Models .Where(m => SomeCriteria).Select(m => Model.AsDto(m)) .AsExpandable() .ToList();
2. NeinLinq:
Similar to LINQKit, NeinLinq utilizes the InjectLambda attribute:
[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 } };
The query can be modified as follows:
dbContext.Models .Where(m => SomeCriteria).Select(m => Model.AsDto(m)) .ToInjectable() .ToList();
3. DelegateDecompiler:
DelegateDecompiler offers a concise approach using the Computed attribute:
[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 } }
The query can be simplified to:
dbContext.Models .Where(m => SomeCriteria).Select(m => Model.AsDto(m)) .Decompile() .ToList();
Each of these libraries accomplishes the goal by modifying the expression tree before EF Core processing, thereby avoiding the need for repeated code. Additionally, all three require a call to inject their respective IQueryProvider.
The above is the detailed content of Can EF Core Code for Selecting Custom DTOs from Child Properties Be Reused Efficiently?. For more information, please follow other related articles on the PHP Chinese website!