实体框架核心:使用导航属性动态构建选择列表

时间:2017-11-27 14:20:34

标签: entity-framework entity-framework-core

问题类似于this one,但答案并没有为我提供两个关键的事情:

  • 我需要代码来处理导航属性
  • 我正在尝试构建扩展方法

我想写这样的查询:

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

在提供链接的答案中,我得到了这个:

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

可以使用EF Core 2.0完成吗?

2 个答案:

答案 0 :(得分:0)

您可以使用Expression.ListInit执行此操作,此处TResult必须具有Add实例方法,例如Dictionary<string, object>。这可能只是工作,但我甚至没有编译它。无论如何,这应该给出足够的提示,告诉你如何以你想要的方式构建它。

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

答案 1 :(得分:0)

EF将遍历lambda调用操作,就好像该表达式的主体是内联的一样。因此,我建议不使用源表达式,而只是生成表达式来调用它们。

我还将保持结果类型简单,只将每一行作为对象数组返回。与创建大量词典相比,这应导致较少的开销。如果确实需要按名称访问字段,则应创建一个字典来维护名称和列号之间的关系。

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

尽管在您的情况下,由于编写的扩展方法仅返回相同的键详细信息和单个附加字段,因此您可以定义单个通用类型来保存结果,并避免在以下位置混入Linq表达式全部;

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

好吧,如果c#仅允许您从另一个内部编译一个Expression<Delegate>的调用。相反,我们可以实现ExpressionVisitor来取消对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)
    });
...

但这看起来有点混乱。