我可以重用代码来使用 EF Core 为子属性选择自定义 DTO 对象吗?

mic*_*ael 4 c# entity-framework expression-trees entity-framework-core

使用 Entity Framework Core 进行查询时,我使用表达式转换为 DTO 对象,这对于对象和任何子集合都很有效。

一个简化的例子:

模型:

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()
        };
}
Run Code Online (Sandbox Code Playgroud)

询问:

dbContext.Models.Where(m => SomeCriteria).Select(Model.AsDto).ToList();
Run Code Online (Sandbox Code Playgroud)

我的问题是试图找到一种方法为非集合的孩子做类似的事情。如果我已添加到我的模型中:

public AnotherChildModel AnotherChildModel { get; set; }
Run Code Online (Sandbox Code Playgroud)

我可以在表达式中添加转换:

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
        }
    };
Run Code Online (Sandbox Code Playgroud)

但是,我还没有找到一种好方法来避免每次需要将第二个子模型转换为 DTO 对象时重复此代码。这些表达式适用于主对象和任何子集合,但不适用于单个实体。有没有办法为单个实体添加 .Select() 的等效项?

Svy*_*liv 8

有几个库可以以直观的方式做到这一点:

LINQ套件

[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
        }
    };
Run Code Online (Sandbox Code Playgroud)
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .AsExpandable()
   .ToList();
Run Code Online (Sandbox Code Playgroud)

更新:对于 EF Core,LINQKit 可以全局配置并且AsExpanding()可以省略。

dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .AsExpandable()
   .ToList();
Run Code Online (Sandbox Code Playgroud)

NeinLinq - 几乎与 LINQKit 中的相同

builder
    .UseSqlServer(connectionString)
    .WithExpressionExpanding(); // enabling LINQKit extension
Run Code Online (Sandbox Code Playgroud)
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .ToInjectable()
   .ToList();
Run Code Online (Sandbox Code Playgroud)

更新:对于 EF Core,NenLinq 可以全局配置并且ToInjectable()可以省略。

[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
        }
    };
Run Code Online (Sandbox Code Playgroud)

DelegateDecompiler - 比其他的更简洁

dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .ToInjectable()
   .ToList();
Run Code Online (Sandbox Code Playgroud)
dbContext.Models
   .Where(m => SomeCriteria).Select(m => Model.AsDto(m))
   .Decompile()
   .ToList();
Run Code Online (Sandbox Code Playgroud)

所有库都执行相同的操作 - 在 EF Core 处理之前纠正表达式树。它们都需要额外的调用来注入它自己的IQueryProvider