You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# .NET Core中如何使用LINQ按展示模型列名对实体模型排序

解决方案

ProjectTo 不是唯一的解决方案,你可以根据自己的项目架构选择以下两种主流实现方式:


方案1:新增Dto字段到实体路径的映射规则(无需调整现有仓储返回逻辑)

适合已经写死仓储层返回List<TEntity>、不想调整原有架构的场景。核心思路是给Dto的字段配置和实体导航路径的映射关系,改造现有排序扩展方法,自动识别映射关系生成正确的排序表达式。

实现步骤:

  1. 定义映射标记Attribute
[AttributeUsage(AttributeTargets.Property)]
public class SortMapAttribute : Attribute
{
    /// <summary>
    /// 对应实体类的导航属性路径
    /// </summary>
    public string EntityPropertyPath { get; }
    public SortMapAttribute(string entityPropertyPath)
    {
        EntityPropertyPath = entityPropertyPath;
    }
}
  1. 给Dto字段加映射标记
public class UserDetailsViewDto
{
    public long Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
    public bool EmailConfirmed { get; set; }
    // 配置映射到实体的导航路径
    [SortMap("UserRoles.FirstOrDefault().Role.Name")]
    public string Role { get; set; }
}
  1. 改造排序扩展方法,支持嵌套路径解析和映射识别
    首先新增路径解析逻辑,把类似A.FirstOrDefault().B.C的字符串转成表达式:
private static Expression GetPropertyPathExpression(ParameterExpression parameter, string propertyPath)
{
    var properties = propertyPath.Split('.');
    Expression propertyAccess = parameter;
    Type currentType = parameter.Type;

    foreach (var propName in properties)
    {
        // 处理FirstOrDefault()这类方法调用
        if (propName.EndsWith("()"))
        {
            var methodName = propName.TrimEnd('(', ')');
            var method = currentType.GetMethods()
                .First(m => m.Name == methodName && m.GetParameters().Length == 0);
            propertyAccess = Expression.Call(propertyAccess, method);
            currentType = method.ReturnType;
            continue;
        }
        // 普通属性访问
        var property = currentType.GetProperty(propName, 
            BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
        currentType = property.PropertyType;
    }
    return propertyAccess;
}

然后改造原有的OrderBy方法,优先读取Dto的映射配置:

// 新增重载,传入Dto类型用于读取映射配置
public static IQueryable<TEntity> OrderBy<TEntity, TDto>(this IQueryable<TEntity> source, 
    string orderByProperty, bool desc)
{
    string command = desc ? "OrderByDescending" : "OrderBy";
    var entityType = typeof(TEntity);
    var dtoType = typeof(TDto);

    // 先查Dto对应字段有没有配置映射路径
    var dtoProperty = dtoType.GetProperty(orderByProperty, 
        BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
    var sortMapAttr = dtoProperty?.GetCustomAttribute<SortMapAttribute>();
    string finalPath = sortMapAttr?.EntityPropertyPath ?? orderByProperty;

    var parameter = Expression.Parameter(entityType, "p");
    var propertyAccess = GetPropertyPathExpression(parameter, finalPath);
    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
    
    var resultExpression = Expression.Call(typeof(Queryable), command, 
        new Type[] { entityType, propertyAccess.Type }, 
        source.Expression, Expression.Quote(orderByExpression));

    return source.Provider.CreateQuery<TEntity>(resultExpression);
}

使用的时候只需要在调用排序时指定Dto类型即可:

var users = Context.Users
                   .Include(user => user.UserRoles)
                       .ThenInclude(ur => ur.Role)
                   .OrderBy<User, UserDetailsViewDto>(query.SortColumn, query.SortDirection == "desc")
                   .ToList();
// 后续再做Dto映射即可

方案2:使用ProjectTo提前完成投影(推荐,多页面场景维护成本更低)

如果可以调整仓储层返回IQueryable<TEntity>,或者直接在业务层操作DbContext,这个方案代码更简洁,不需要维护额外的映射Attribute,直接复用AutoMapper的字段映射配置即可。

实现步骤:

  1. 配置AutoMapper映射规则,和你现有的Dto映射规则完全一致
CreateMap<User, UserDetailsViewDto>()
    .ForMember(d => d.Role, opt => opt.MapFrom(s => s.UserRoles.FirstOrDefault().Role.Name));
  1. 直接在查询阶段完成投影,排序直接作用于Dto字段
var users = Context.Users
                   .ProjectTo<UserDetailsViewDto>(_mapper.ConfigurationProvider)
                   .ApplySort(query)
                   .ToList();

这个方案不需要手动写Include,AutoMapper会自动根据映射关系生成关联查询,排序时直接传前端传的Role字段即可,EF会自动生成对应导航路径的排序SQL。


方案对比

方案架构改造成本维护成本性能
映射Attribute方案低,无需修改现有仓储返回逻辑中,每个需要特殊映射的字段都要加Attribute略低,需要手动写Include加载关联数据
ProjectTo方案中,需要调整查询逻辑提前投影低,直接复用现有Dto映射配置高,自动生成最优SQL,只查需要的字段

你可以根据自己项目的实际架构选择合适的方案,不需要强制使用ProjectTo。


内容的提问来源于stack exchange,提问作者Stefan Petrović

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 22:36:03