从linq到实体查询动态构建选择列表

Pau*_*ugt 6 c# linq linq-to-entities entity-framework

我正在寻找一种从iQueryable对象动态创建选择列表的方法.

具体的例子,我想做类似以下的事情:

public void CreateSelectList(IQueryable(of EntityModel.Core.User entities), string[] columns)
{
    foreach(var columnID in columns)
    {
        switch(columnID)
        {
            case "Type":
                SelectList.add(e => e.UserType);
                break;
            case "Name":
                SelectList.add(e => e.Name);
                break;
            etc....
        }
    }
    var selectResult = (from u in entities select objSelectList);
}
Run Code Online (Sandbox Code Playgroud)

因此,所有属性都是已知的,但我事先并不知道要选择哪些属性.这将通过columns参数传递.

我知道我将遇到selectResult类型的问题,因为当选择列表是动态的时,编译器不知道匿名类型的属性需要是什么.

如果上述情况不可能:我需要它的场景如下:
我正在尝试创建一个可以实现的类来显示分页/过滤的数据列表.这些数据可以是任何东西(取决于实现).使用的linq是实体的linq.所以它们直接链接到sql数据.现在我只想选择我实际在列表中显示的实体的列.因此我希望select是动态的.我的实体可能有一百个属性,但如果列表中只显示了3个属性,我不想生成一个选择所有100列数据的查询,然后只使用其中的3个.如果有一种我没有想到的不同方法,我会接受各种想法

编辑:


关于约束的一些澄清:- 查询需要使用linq到实体(请参阅问题主题)
- 实体可能包含100列,因此选择所有列然后只读取我需要的列不是一个选项.
- 最终用户决定要显示哪些列,因此要选择的列在运行时确定
- 我需要创建一个SINGLE选择,有多个select语句意味着对数据库有多个查询,我不想要

Iva*_*oev 17

动态选择表达到编译时已知类型可以容易地生成使用Expression.MemberInit方法与MemberBinding使用所创建的小号Expression.Bind方法.

这是一个自定义扩展方法,它执行以下操作:

public static class QueryableExtensions
{
    public static IQueryable<TResult> Select<TResult>(this IQueryable source, string[] columns)
    {
        var sourceType = source.ElementType;
        var resultType = typeof(TResult);
        var parameter = Expression.Parameter(sourceType, "e");
        var bindings = columns.Select(column => Expression.Bind(
            resultType.GetProperty(column), Expression.PropertyOrField(parameter, column)));
        var body = Expression.MemberInit(Expression.New(resultType), bindings);
        var selector = Expression.Lambda(body, parameter);
        return source.Provider.CreateQuery<TResult>(
            Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
                source.Expression, Expression.Quote(selector)));
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一的问题是什么是TResult类型.在EF Core中,您可以传递实体类型(如EntityModel.Core.User您的示例中所示),它将起作用.在EF 6及更早版本中,您需要一个单独的非实体类型,否则您将获得NotSupportedException- 无法在LINQ to Entities查询中构造实体或复杂类型.

更新:如果你想要删除字符串列,我建议你用以下类替换扩展方法:

public class SelectList<TSource>
{
    private List<MemberInfo> members = new List<MemberInfo>();
    public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
    {
        var member = ((MemberExpression)selector.Body).Member;
        members.Add(member);
        return this;
    }
    public IQueryable<TResult> Select<TResult>(IQueryable<TSource> source)
    {
        var sourceType = typeof(TSource);
        var resultType = typeof(TResult);
        var parameter = Expression.Parameter(sourceType, "e");
        var bindings = members.Select(member => Expression.Bind(
            resultType.GetProperty(member.Name), Expression.MakeMemberAccess(parameter, member)));
        var body = Expression.MemberInit(Expression.New(resultType), bindings);
        var selector = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
        return source.Select(selector);
    }
}
Run Code Online (Sandbox Code Playgroud)

样本用法:

var selectList = new SelectList<EntityModel.Core.User>();
selectList.Add(e => e.UserType);
selectList.Add(e => e.Name);

var selectResult = selectList.Select<UserDto>(entities);
Run Code Online (Sandbox Code Playgroud)