9

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:

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.

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.

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. So they are directly linked to sql data. 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. 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. 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)
- an entity might contain 100 columns, so selecting ALL columns and then only reading the ones i need is not an option.
- 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

PaulVrugt
  • 1,682
  • 2
  • 17
  • 40
  • I've been scouring the internet and found some people with similar problems. However, the suggested solution uses strings a propertyinfo objects to determine the members to create the select expression. My goal is to be able to have a type safe solution without string property lookups. Like i said in my original post, all properties are known. Ideally I would like to use lambda's to point the expression to my properties to select. – PaulVrugt Jul 21 '16 at 14:33
  • If you don't want to use strings, you can use PropertyInfo instead, which is type safe. Also, even if you do use a string, Expression.Property makes it "safe" by checking whether the property really exists on the declared type and throwing an exception if your property name is invalid. – Tim Copenhaver Jul 21 '16 at 14:51
  • First, it will be good if you provide a signature of the method you are looking for. Second, unfortunately for you LINQ to Entities does not allow projection to entity types, so how are you planning to handle that? – Ivan Stoev Jul 21 '16 at 17:41
  • Ivan, I'm not sure what you mean. I have provided a code example of what i'm trying to do. It would be sufficient to just return a list of User (see example) POCO's with only the properties filled that i selected. This i think should take care of the property issues. I'm simply trying to dynamically determine the columns i'm selecting from the database – PaulVrugt Jul 22 '16 at 11:06
  • Have you done performance testing to demonstrate that querying extra fields causes unacceptable performance? In almost all applications, this is not a noticeable problem. Querying extra tables can slow things down, but bringing back more rows from a single table has a minimal performance impact unless they're large out-of-row columns, but those don't get selected by default anyway. – Tim Copenhaver Jul 22 '16 at 13:15
  • 1
    @TimCopenhaver, your assumption that all data is coming from the same table is wrong. Most of the time the data comes from a view, that queries LOTS of tables, and is not meant to be selected with all columns. But yes, we did tests and selecting ALL columns makes our sql server very hot – PaulVrugt Jul 22 '16 at 13:19
  • @PaulVrugt Let me ask you differently. Can you do manually what are you asking for? e.g. create a static query that returns `User` objects with only 1-2 properties selected? I doubt. If you can do that, then we can talk about dynamic stuff. – Ivan Stoev Jul 22 '16 at 16:45
  • @IvanStoev, why not? how about: var result = (from u in entities select u.UserType, u.Name).toArray(); This will create an underlying query that will select only the usertype and name properties of a user. – PaulVrugt Jul 25 '16 at 06:35
  • @PaulVrugt This is not a valid LINQ query at all. Anyway, what I'm talking about is that if you try using `select new User { UserType = u.UserType, Name = u.Name }`, you'll get (at runtime) a `NotSupportedException` stating [the entity or complex type 'xxx' cannot be constructed in a LINQ to Entities query](http://stackoverflow.com/search?q=the+entity+or+complex+type+cannot+be+constructed+in+a+LINQ+to+Entities+query) – Ivan Stoev Jul 25 '16 at 06:47
  • Btw, this is EF6 and below limitation, which seems to have been fixed in EF Core. Are you targeting EF Core? – Ivan Stoev Jul 25 '16 at 07:28
  • Hi all! I just made a test on a project of mine with EntityFramework 6.0 and it work perfectly fine! I don't know about any specific limitations of "LinqToEntity", but as I quickly seen, it seems to be a subset of EF, so @PaulVrugt, why dont you go for the EF and it is fully supported in the solution I proposed to you below! – cnom Jul 25 '16 at 07:36
  • @IvanStoev, I think we're having some miscommunication. The query i typed is a valid linq query? Why would it not be? This query executes fine and our project has loads of these kind of queries. – PaulVrugt Jul 25 '16 at 07:47
  • @cnom, I think test collection you are using is not an actual linq-to-entities collection. If you run your example code on a list, it runs perfectly indeed. However, if you replace this list with an actual iQueryable object (in my instance the Users collection in a dbcontext), it will be unable to convert a statement lambda to an expression tree – PaulVrugt Jul 25 '16 at 07:52
  • 1
    @PaulVrugt `select new User { UserType = u.UserType, Name = u.Name }` is valid LINQ, `select u.UserType, u.Name` is not. Anyway, all I'm trying to figure out is if it's possible to help you. I have no problem creating a dynamic select (which you can see if you look at my answers in the expressions area), the problem is the select target object **type** and EF limitations. If you can create a class having all these properties, but not recognized by EF as an entity (a.k.a. DTO object), then it's doable. Another option is DynamicLINQ which creates dynamic classes at runtime for projections. – Ivan Stoev Jul 25 '16 at 08:26
  • @IvanStoev, right, i think i know where the miscommunication is. I'm writing code in vb.net because my employer requires me to. In vb.net the names of the select are implied. The result of the example i sent you is an anonymous type with the properties UserType and Name. However, I'm satisfied when the result of the query i'm looking for simply returns a User object with only the properties filled that i need. I have no requirement for having an exactly matching class. – PaulVrugt Jul 25 '16 at 08:37

3 Answers3

23

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);
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • At first glance this seems about what I am looking for. Once I have time I try to integrate this in my test project to see if it is what I am looking for. Thank you for the example – PaulVrugt Jul 25 '16 at 10:58
  • Right, i'm indeed running into the NotSupportedException you are referring to. However, the query indeed does what I want, except for the fact the i still need to use a string to specify the column now. However, I think i can work with this to get the result i'm looking for. Thank you! – PaulVrugt Jul 25 '16 at 15:13
  • This is a clean solution for sure, but isn't it the same problem you mentioned for my answer below? You have to pass the columns as a list of strings, which you were specifically trying to avoid. – Tim Copenhaver Jul 25 '16 at 15:25
  • I'm glad we are on the same page now. Updated the answer with alternative solution. Hope that helps (or at least giving you a starting point :) – Ivan Stoev Jul 25 '16 at 16:11
  • @TimCopenhaver, indeed this solution still had the same problem with the string column names, but this one works with linq to entities. – PaulVrugt Jul 28 '16 at 14:38
  • @IvanStoev, Thank you for the update. Avoiding the string names makes the solution even better. The issue with the entity type (my poco) is a bit of a hickup, but creating a new class is a concession i am willing to do. We can now create views with the correct columns, create entities for these views, and use these entities to dynamically display data exactly the way i was planning to implement. This is the solution for sure. Might you be interested in the limitations this still has: following navigation properties (User.Function.Name for instance) doesn't work, but is not required for me – PaulVrugt Jul 28 '16 at 14:40
  • @PaulVrugt I'm aware of that limitation. But as usual for a SO post, I'm including only the minimum required :) – Ivan Stoev Jul 28 '16 at 15:30
  • @IvanStoev, In my scenario, the properties to include, are not part of `sourceType` but only in some of the subclasses of it. Can we make this function work by binding the property names by its runtime-type instead of the `IQueryable.ElementType` type? – Shimmy Weitzhandler May 28 '17 at 01:59
  • @Shimmy Not in general (except eventually L2O). The problem is that when we build `Select` expression for `IQueryable`, it's not really executed like `IEnumerable.Select` `Func<...>` which is just code, but instead it's used to build SQL query for instance, so there is no way to get the "runtime" type. Here is the point - if you can create *working* L2E query statically (with compile time expression) , then it will be possible to create it dynamically. If you have a concrete example, you could post a question and I'll be glad to take look at it. – Ivan Stoev May 28 '17 at 16:33
  • @IvanStoev Can you add as sample call of the extension method (your first answer)? I actually need exactly this functionality, where I have a string array of columns and want to make a fluent API call of Select on IQueryable using EF Core and am having trouble with the syntax. – Alex Feb 05 '18 at 00:56
  • @Alex It would be like `source.Select(columns)` where `source` is your `IQueryable`. In this case the generic type argument cannot be inferred and must be specified during the call. – Ivan Stoev Feb 05 '18 at 08:35
  • Hi Ivan. How can I work with an ignore case column names array? – alhpe Nov 16 '18 at 11:37
  • added BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance to ignore case – alhpe Nov 16 '18 at 12:21
  • @alhpe I guess you mean here `resultType.GetProperty()`. Sounds good. – Ivan Stoev Nov 16 '18 at 12:41
  • Hi Ivan. How can I emulate this. var queryable = a dbset as queryable; var player = await queryable .Include(d => d.CreditCard) .Include(d => d.Photo) .Include(d => d.EmailAddresses) .Select(e => new { Id = e.Id, FirstName = e.FirstName, CardNumber = e.CreditCard.CardNumber, PhotoFileName = e.Photo.PhotoFileName, MailAddress = e.EmailAddresses.FirstOrDefault().MailAddress }).ToListAsync(); The extension method only works with root properties of the graph – alhpe Nov 16 '18 at 18:44
  • I woud do like to do a projection also on related entities for the root graph as follows – alhpe Nov 16 '18 at 18:45
  • Select(e => new { Id = e.Id, FirstName = e.FirstName, CardNumber = e.CreditCard.CardNumber, PhotoFileName = e.Photo.PhotoFileName, MailAddress = e.EmailAddresses.FirstOrDefault().MailAddress }).ToListAsync(); – alhpe Nov 16 '18 at 18:45
  • that is generating the following t-sql SELECT [d].[Id], [d].[FirstName], [d.CreditCard].[CardNumber], [d.Photo].[PhotoFileName], ( SELECT TOP(1) [e].[MailAddress] FROM [EmailAddress] AS [e] WHERE [d].[Id] = [e].[PlayerId] ) AS [MailAddress] FROM [Player] AS [d] LEFT JOIN [Photo] AS [d.Photo] ON [d].[Id] = [d.Photo].[PlayerId] LEFT JOIN [CreditCard] AS [d.CreditCard] ON [d].[Id] = [d.CreditCard].[PlayerId] – alhpe Nov 16 '18 at 18:46
  • @alhpe This method is just for simple (not even nested) property projection. For your scenario you need something more flexible, like AutoMapper library. Reinventing (reimplementing) al that these guys do doesn't make sense to me. – Ivan Stoev Nov 16 '18 at 18:49
  • I am looking at your reply here https://stackoverflow.com/questions/51753165/c-sharp-dynamically-generate-linq-select-with-nested-properties/51764873#51764873 and guessing what is doing.. ;-) – alhpe Nov 16 '18 at 20:00
  • @alhpe Yeah, it's better, but still not enough for what you need. It doesn't handle the so called "flattening" like `CardNumber = e.CreditCard.CardNumber` in your example. And for sure doesn't handle `MailAddress = e.EmailAddresses.FirstOrDefault().MailAddress`. As I said in the previous comment, AutoMapper provides such flexibility of defining the source to destination advanced mappings while handling simple mappings automatically. – Ivan Stoev Nov 16 '18 at 21:01
  • Is there a way to get this to work on EF6 when the query needs to be run on an entity type? – Garrett Banuk Feb 27 '20 at 17:04
  • @GarrettBanuk No, because EF6 does not allow doing `new EntityType { … }` – Ivan Stoev Feb 27 '20 at 18:32
1

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.

Community
  • 1
  • 1
Tim Copenhaver
  • 3,282
  • 13
  • 18
  • I found exactly this post. However, what i would like to do is use the code in the answer for that question, but then instead of using propertyinfo's and strings to determine the property, i would like to somehow use lambda expressions to point out what properties i am using. Is this possible? I haven't been able to find any examples on this – PaulVrugt Jul 21 '16 at 14:55
  • I'm sure it's possible, but it will be complicated to keep your ParameterExpressions lined up and probably won't save you much. It sounds like you're shooting for some extreme premature optimizations - selecting extra fields typically doesn't cause a huge database slowdown. Even if it does, it's simpler to create a view and new entity type. Also, using property expressions is not particularly safer than using strings unless you are renaming your properties often. – Tim Copenhaver Jul 21 '16 at 20:54
  • Selecting extra data is not an option. In my case the extra data being selected will cause very heavy database queries, which i am trying to avoid. Creating new views is not an option either. For instance: the list i'm creating might have 100 (possible) columns, and the end user will be able to determine what columns to shown. I cannot create new entity types for each combination of columns – PaulVrugt Jul 22 '16 at 11:02
1

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
cnom
  • 3,071
  • 4
  • 30
  • 60
  • I don't think this does what i want. This performs multiple selects, which causes multiple queries to be executed against the database. I'm looking for a way to build a select list and then perform a single select – PaulVrugt Jul 22 '16 at 11:01
  • @PaulVrugt, I guess now I understand what you need. I updated my answer (with a second set of code) that has a single select and "collects" all the wanted properties of the object in each itteration. – cnom Jul 22 '16 at 12:53
  • Thank you for the update! We are going into the right direction. But i don't think this is going to work with linq to entities is it? – PaulVrugt Jul 22 '16 at 13:10
  • Why not? You mean on collections of entity framework? I cant see the reason why it would not work.. – cnom Jul 22 '16 at 19:54
  • Trying to do with entity framework collection causes the following syntax error: "A lambda expression with a statement body cannot be converted to an expression tree". That's why I don't believe this approach works with entity framework collections – PaulVrugt Jul 25 '16 at 07:15
  • check this out: http://stackoverflow.com/questions/5179341/a-lambda-expression-with-a-statement-body-cannot-be-converted-to-an-expression – cnom Jul 25 '16 at 07:52
  • this is exactly the problem i have with the solution you propose. However, there is no solution in this topic, other than selecting the entity (user in my case) with ALL properties and then reading out only the properties I need, which is exactly what i am trying to avoid – PaulVrugt Jul 25 '16 at 07:55
  • I see, mate! Ok, if you decide there is no solution, at least give an upvote for the effort :-) – cnom Jul 25 '16 at 08:00
  • Haha, I was hoping that we would be able to tweak your example into code that would execute properly using entity framework. However if you don't know of way on how to do this we've hit a dead end. I do appreciate the effort though :) – PaulVrugt Jul 25 '16 at 08:40
  • Oh, of course! As I see it, all you have to do is use System.Data.Entity.DbContext to query your db. Reference EF6 and you have to make the necessary configuration for your current DB. Then you just instantiat that dbContext object and use it to query your DB i.e. var result = myDbContext.Users().Select(...).Where(...) – cnom Jul 25 '16 at 09:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/118204/discussion-between-paulvrugt-and-cnom). – PaulVrugt Jul 25 '16 at 09:50