简体   繁体   English

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

[英]Dynamically build select list from linq to entities query

I'm looking for a way to dynamically create a select list from a iQueryable object. 我正在寻找一种从iQueryable对象动态创建选择列表的方法。

Concrete example, i want to do something like the following: 具体的例子,我想做类似以下的事情:

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

So all properties are known, i however don't know beforehand what properties are to be selected. 因此,所有属性都是已知的,但我事先并不知道要选择哪些属性。 That will be passed via the columns parameter. 这将通过columns参数传递。

I know i'm going to run into issues with the type of the selectResult type, because when the select list is dynamic, the compiler doesn't know what the properties of the anonymous type needs to be. 我知道我将遇到selectResult类型的问题,因为当选择列表是动态的时,编译器不知道匿名类型的属性需要是什么。

If the above is not possible: The scenario I need it for is the following: 如果上述情况不可能:我需要的方案如下:
I'm trying to create a class that can be implemented to display a paged/filtered list of data. 我正在尝试创建一个可以实现的类来显示分页/过滤的数据列表。 This data can be anything (depends on the implementations).The linq used is linq to entities. 这些数据可以是任何东西(取决于实现)。使用的linq是实体的linq。 So they are directly linked to sql data. 所以它们直接链接到sql数据。 Now i want to only select the columns of the entities that i am actually showing in the list. 现在我只想选择我实际在列表中显示的实体的列。 Therefore i want the select to be dynamic. 因此我希望select是动态的。 My entity might have a hundred properties, but if only 3 of them are shown in the list, i don't want to generate a query that selects the data of all 100 columns and then only uses 3 of them. 我的实体可能有一百个属性,但如果列表中只显示了3个属性,我不想生成一个选择所有100列数据的查询,然后只使用其中的3个。 If there is a different approach that I haven't thought of, I'm open to ideas 如果有一种我没有想到的不同方法,我会接受各种想法

Edit: 编辑:

Some clarifications on the contraints: 关于约束的一些澄清:
- The query needs to work with linq to entities (see question subject) - 查询需要与linq一起使用实体(参见问题主题)
- an entity might contain 100 columns, so selecting ALL columns and then only reading the ones i need is not an option. - 一个实体可能包含100列,因此选择所有列然后只读取我需要的列不是一个选项。
- The end user decides what columns to show, so the columns to select are determined at run time - 最终用户决定显示哪些列,因此要在运行时确定要选择的列
- i need to create a SINGLE select, having multiple select statements means having multiple queries on the database, which i don't want - 我需要创建一个SINGLE选择,有多个select语句意味着在数据库上有多个查询,这是我不想要的

Dynamic select expression to a compile time known type can easily be build using Expression.MemberInit method with MemberBinding s created using the Expression.Bind method. 动态选择表达式到编译时已知类型可以使用Expression.MemberInit方法轻松构建,其中MemberBinding是使用Expression.Bind方法创建的。

Here is a custom extension method that does that: 这是一个自定义扩展方法,它执行以下操作:

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

The only problem is what is the TResult type. 唯一的问题是什么是TResult类型。 In EF Core you can pass the entity type (like EntityModel.Core.User in your example) and it will work. EF Core中,您可以传递实体类型(如EntityModel.Core.User中的EntityModel.Core.User ),它将起作用。 In EF 6 and earlier, you need a separate non entity type because otherwise you'll get NotSupportedException - The entity or complex type cannot be constructed in a LINQ to Entities query . EF 6及更早版本中,您需要一个单独的非实体类型,否则您将获得NotSupportedException - 无法在LINQ to Entities查询中构造实体或复杂类型

UPDATE: If you want a to get rid of the string columns, I can suggest you replacing the extension method with the following class: 更新:如果你想要删除字符串列,我建议你用以下类替换扩展方法:

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

with sample usage: 样本用法:

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

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

What you are going for is possible, but it's not simple. 你想要的是可能的,但这并不简单。 You can dynamically build EF queries using the methods and classes in the System.Linq.Expressions namespace. 您可以使用System.Linq.Expressions命名空间中的方法和类动态构建EF查询。

See this question for a good example of how you can dynamically build your Select expression. 有关如何动态构建Select表达式的一个很好的示例,请参阅此问题

I believe this is what you need: 我相信这就是你所需要的:

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

This code outputs: 此代码输出:

  • First 第一
  • Second 第二
  • TypeA 类型A
  • TypeB 的TypeB

UPDATE (according to comments) 更新(根据评论)

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

This code outputs: 此代码输出:

  • First 第一
  • TypeA 类型A
  • Second 第二
  • TypeB 的TypeB

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM