Dynamically build select list from linq to entities query

前端 未结 3 1692
挽巷
挽巷 2020-12-13 22:21

I\'m looking for a way to dynamically create a select list from a iQueryable object.

Concrete example, i want to do something like the following:

pub         


        
相关标签:
3条回答
  • 2020-12-13 23:05

    Dynamic select expression to a compile time known type can easily be build using Expression.MemberInit method with MemberBindings created using the Expression.Bind method.

    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. In EF Core you can pass the entity type (like EntityModel.Core.User in your example) and it will work. 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.

    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);
    
    0 讨论(0)
  • 2020-12-13 23:16

    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.

    See this question for a good example of how you can dynamically build your Select expression.

    0 讨论(0)
  • 2020-12-13 23:25

    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
    • 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
    • Second
    • TypeB
    0 讨论(0)
提交回复
热议问题