1

The question is similar to this one, but answer does not provide 2 critical things to me:

  • I need the code to work over navigational properties
  • I am trying to build extension method

I want to write queries like this:

this.context.User
    .Where(t => t.Id > 10)
    .SelectCustom(t => t.Address.Country.Title)
    .OrderBy(t => t.DisplayName)
    .Skip(10).Take(5);

With answer in provided link I get this far:

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 Expression<Func<TSource, TResult>> ToDynamicColumns()
    {
        return this.members.??????????;
    }
}

public static IQueryable<T> SelectCustom<T>(this IQueryable<T> query, Expression<Func<TSource, TKey>> FirstAdditional = null)
{
    var columns = new SelectList<T>();
    columns.Add(t => t.Id);
    columns.Add(t => t.DisplayName)
    if (FirstAdditional != null)
        columns.Add(FirstAdditional);

    return query.Select(columns.ToDynamicColumns);
}

Can this be done with EF Core 2.0?

Makla
  • 9,899
  • 16
  • 72
  • 142
  • Any reason you can't just select a view model? E.g. `.Select(user => new UserViewModel { Id = user.Id, Name = user.DisplayName })`? Why do you need it to be dynamic? – Charles Nov 28 '17 at 13:59
  • Because user may defined which columns he wish to see. And those columns can get deep (navigational property on navigational property ...) – Makla Nov 28 '17 at 17:30
  • Shouldn't you be cleaning up user input anyways somewhere? You might find System.Linq.Dynamic.Core useful. I've done similar things with it. – Charles Nov 28 '17 at 17:38
  • I will use System.Linq.Dynamic.Core if it comes to that, but I am pretty sure it can be done with Expressions. – Makla Nov 28 '17 at 17:41
  • Think logically. For simple properties like `DisplayName` the function creates something like this `t => new T { DisplayName = t.DisplayName }`. But in case of nested property, what would be the left side of the assignment (there is no such C# construct), i.e. `t => new T { ??? = t.Address.Country.Title }` – Ivan Stoev Nov 30 '17 at 09:18
  • Select expect expression, and I think expression have names. So this could work. I can dynamically build left side of expression (Address_Country_Title for example). – Makla Nov 30 '17 at 15:16
  • I see. So `TResult` parameter of `ToDynamicColumns` will specify different object having target property. I guess it's doable, but starts looking like AutoMapper. – Ivan Stoev Nov 30 '17 at 20:32

2 Answers2

1

EF will look through lambda invoke operations as if the body of that expression was inlined. So I would recommend leaving the source expressions alone and just generate expressions to invoke them.

Also I would keep the result type simple, and just return each row as an array of object. This should result in less overhead than creating lots of dictionaries. If you do need to access fields by name, you should create a single dictionary to maintain the relationship between names and column numbers.

public class SelectList<TSource>
{
    private List<LambdaExpression> members = new List<LambdaExpression>();
    public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
    {
        members.Add(selector);
        return this;
    }

    public Expression<Func<TSource, TResult>> ToDynamicColumns()
    {
        var parameter = Expression.Parameter(typeof(TSource), "e");
        return Expression.Lambda<Func<TSource, object[]>>(
            Expression.NewArrayInit(
                typeof(object),
                members.Select(m =>
                    Expression.Convert(Expression.Invoke(m, parameter), typeof(object))
                )
            ),
            parameter);
    }
}

Though in your case, since you are writing an extension method to only return the same key details and a single additional field, you could probably define a single generic type to hold the results, and avoid any mucking around with Linq expressions at all;

public class UserResult<V>{
    public int Id { get; set; }
    public string DisplayName { get; set; }
    public V Value { get; set; }
}

public static IQueryable<UserResult<V>> SelectCustom<V>(this IQueryable<User> query, Expression<Func<User, V>> ValueGetter)
{
    return query.Select(u => new UserResult<V>{
        Id = u.Id,
        DisplayName = u.DisplayName,
        Value = ValueGetter(u)
    });
}

Well almost, if c# would just allow you to compile a call of one Expression<Delegate> from within another. Instead we can implement an ExpressionVisitor to unwrap any call to Compile;

public class DontCompile : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        // Inline any lambda arguments that are expressions
        if (node.Expression is ConstantExpression lambdaArgs
            && node.Member is FieldInfo field
            && typeof(Expression).IsAssignableFrom(field.FieldType))
            return (Expression)field.GetValue(lambdaArgs.Value);
        return base.VisitMember(node);
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // Don't compile lambda expressions
        if (node.Method.Name == "Compile" 
            && typeof(LambdaExpression).IsAssignableFrom(node.Object.Type))
            return Visit(node.Object);
        return base.VisitMethodCall(node);
    }

    public static Expression<T> Tidy<T>(Expression<T> func) => (Expression<T>)new DontCompile().Visit(func);
}
...
    return query.Select(DontCompile.Tidy<...>(u => new UserResult<V>{
        Id = u.Id,
        DisplayName = u.DisplayName,
        Value = ValueGetter.Compile()(u)
    });
...

But that's starting to look a bit messy.

Jeremy Lakeman
  • 9,515
  • 25
  • 29
0

You could do this with Expression.ListInit, here the TResult must have an Add instance method e.g. Dictionary<string, object>. This could just work but I havent even compiled it. In any way this should give enough hint on how to build this the way you want.

public Expression<Func<TSource, Dictionary<string, object>>> ToDynamicColumns()
{
   var addMethod = typeof(TResult).GetMethod("Add");

   var paramX = Expression.Parameter(typeof(TSource), "e");

   var bindings = 
       this.members.Select (
          member => 
             return Expression.ElementInit(
                addMethod,
                Expression.Constant(mem.Name),
                Expression.Convert(Expression.Property(paramX, member), typeof(object))
          );
       )

   var listInit = Expression.ListInit(
       Expression.New(typeof(TResult)),
       bindings
   );

   return Expression.Lambda<Func<TSource, Dictionary<string, object>>(
       listInit,
       paramX
   );
}
  • I believe that LINQ will look through calls to lambda's. So don't bother unpacking the Expression to find the member, just pass it directly to Expression.Invoke() as the ElementInit() parameter. – Jeremy Lakeman Jul 05 '19 at 06:21
  • @JeremyLakeman except, using Expression allows one to use this in combination with IQueryable as e.g. with EntityFramework's so that it's translated to and executed as SQL. – Ibrahim ben Salah Jul 09 '19 at 09:34
  • I've tested it now, see my answer (currently below). EF will treat an invoke of an Expression as if it was inlined. So there's no need to extract the MemberInfo's in Add, just keep the whole Expression and invoke it later. – Jeremy Lakeman Jul 10 '19 at 01:34