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

Dynamically build select list from linq to entities query

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

具体的例子,我想做如下的事情:

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);
}

所以所有属性都是已知的,但是我事先不知道要 select编辑哪些属性。这将通过列参数传递。

我知道我要 运行 解决 select 结果类型的问题,因为当 select 列表是动态的时,编译器不知道匿名类型的属性需要是什么。

如果以上不可能:我需要它的场景如下:
我正在尝试创建一个 class 可以用来显示 paged/filtered 数据列表。这个数据可以是任何东西(取决于实现)。使用的 linq 是 linq to entities。所以它们直接链接到 sql 数据。现在我只想 select 我实际显示在列表中的实体的列。因此我希望 select 是动态的。我的实体可能有一百个属性,但如果列表中只显示其中的 3 个,我不想生成一个 selects 所有 100 列的数据然后只使用其中 3 个的查询。如果有我没有想到的不同方法,我愿意接受想法

编辑:

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

你想要的是可能的,但这并不简单。您可以使用 System.Linq.Expressions 命名空间中的方法和 类 动态构建 EF 查询。

有关如何动态构建 Select 表达式的良好示例,请参阅 this question

我相信这就是您所需要的:

var entities = new List<User>();

entities.Add(new User { Name = "First", Type = "TypeA" });
entities.Add(new User { Name = "Second", Type = "TypeB" });

string[] columns = { "Name", "Type" };

var selectResult = new List<string>();

foreach (var columnID in columns)
{
    selectResult.AddRange(entities.Select(e => e.GetType().GetProperty(columnID).GetValue(e, null).ToString()));
}

foreach (var result in selectResult)
{
    Console.WriteLine(result);
}

此代码输出:

  • First
  • Second
  • TypeA
  • TypeB

更新(根据评论)

// initialize alist of entities (User)
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA", SomeOtherField="abc" });
entities.Add(new User { Name = "Second", Type = "TypeB", SomeOtherField = "xyz" });

// set the wanted fields
string[] columns = { "Name", "Type" };

// create a set of properties of the User class by the set of wanted fields
var properties = typeof(User).GetProperties()
                        .Where(p => columns.Contains(p.Name))
                        .ToList();

// Get it with a single select (by use of the Dynamic object)
var selectResult = entities.Select(e =>
{
    dynamic x = new ExpandoObject();
    var temp = x as IDictionary<string, Object>;
    foreach (var property in properties)
        temp.Add(property.Name, property.GetValue(e));
    return x;
});

// itterate the results
foreach (var result in selectResult)
{
    Console.WriteLine(result.Name);
    Console.WriteLine(result.Type);
}

此代码输出:

  • First
  • TypeA
  • Second
  • TypeB

编译时已知类型的动态select表达式可以使用Expression.MemberInit method with MemberBindings created using the 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)));
    }
}

唯一的问题是TResult类型是什么。在 EF Core 中,您可以传递实体类型(如示例中的 EntityModel.Core.User),它会起作用。在 EF 6 及更早版本中,您需要一个单独的非实体类型,否则您将得到 NotSupportedException - The entity or complex type cannot be constructed in a LINQ to Entities 查询.

更新: 如果你想摆脱字符串列,我建议你用以下 class:[=20= 替换扩展方法]

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);
    }
}

使用示例:

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

var selectResult = selectList.Select<UserDto>(entities);