问题类似于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完成吗?
答案 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)
});
...
但这看起来有点混乱。