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

时间:2016-07-21 13:39:58

标签: c# entity-framework linq linq-to-entities

我正在寻找一种从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);
}

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

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

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

编辑:

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

3 个答案:

答案 0 :(得分:16)

动态选择表达式到编译时已知类型可以使用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)));
    }
}

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

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

此代码输出:

  
      
  • 首先
  •   
  • 第二
  •   
  • TypeA
  •   
  • 的TypeB
  •   

更新(根据评论)

      $random_bit_stream = array_map(function() {
            return mt_rand(0,1);
        }, range(1,6));

or

 $random_bit_stream = array_map(function() {
            return rand(0,1);
        }, range(1,6));

        print_r($random_bit_stream);

此代码输出:

  
      
  • 首先
  •   
  • TypeA
  •   
  • 第二
  •   
  • 的TypeB
  •