由于在 EF 中,不管是什么 LINQ 表达式,都是只能写在外面,如果写在 LINQ 的 Lambda 表达式里面,编译器会直接编译成结果,导致 EF 无法拼出我们想要的 SQL。
比如我想达到这样的效果,按照 CREATED_DATE 进行升序排列:
var filteredCases = _dataModel.TEST_RESULT
.Where(res => res.IS_ACTIVE && res.CUSTOMER_GUID == customerGuid);
filteredCases.OrderBy(res => res.CREATED_DATE);
return filteredCases.ToList();
动态配置要排序的列
如果现在要动态配置,让这个列名从外部传入。
直观的想法就是改成这样:
public List<TEST_RESULT> GetResults(string orderByPropName)
{
var filteredCases = _dataModel.TEST_RESULT
.Where(res => res.IS_ACTIVE && res.CUSTOMER_GUID == customerGuid);
filteredCases.OrderBy(res => res.GetType().GetProperty(orderByPropName));
return filteredCases.ToList();
}
显然,这样是错的!
再加上 GetValue() 依然是错的。
我们就需要冷静下来思考一下,既然他需要一个完整的 Lambda 表达式来进行 SQL 的生成,那么我们需要传入的就是一个完整的 Lambda 表达式本身,而不是上面这种一半一半的四不像。
参考了 《C# 5 In a Nutshell》 之后,有了答案:
要组成我们自己的 Lambda 表达式(注意这边不是 Where 后面的 Predicate,那个可以用 LinqKit 包来快速生成,但这个不行!)
直接上完整的代码:
public List<TEST_RESULT> GetResults(string orderByPropName)
{
var filteredCases = _dataModel.TEST_RESULT
.Where(res => res.IS_ACTIVE && res.CUSTOMER_GUID == customerGuid);
// 第一步,定义一个 Parameter,就是 Lambda 表达式,箭头符号前面的东西
ParameterExpression orderByParam = Expression.Parameter(typeof(TEST_RESULT));
// 第二部,定义一个 Member,也就是 Lambda 表达式箭头符号后面的那个变量
MemberExpression orderByMember = Expression.PropertyOrField(orderByParam, orderByPropName);
// 第三步,通过这两个组成一个 Lambda 表达式对象
LambdaExpression orderByLambda = Expression.Lambda(orderByMember, orderByParam);
// 其实 Lambda 表达式对象是不能直接使用的,需要下面第四步:
// 第四步,通过 Lambda 表达式对象,生成一个 MethodCall,这样才能用。
Type[] methodCallArgTypes = { sourceQuery.ElementType, orderByLambda.Body.Type };
MethodCallExpression methodCall = Expression.Call(typeof(Queryable), "OrderBy", methodCallArgTypes, sourceQuery.Expression, orderByLambda);
// 第五步,把 Order By 的 SQL 拼接上去
filteredCases = filteredCases.Provider.CreateQuery(methodCall) as IQueryable<TEST_RESULT>;
// 这边 ToList 跑完之后,在 SQL Server Profiler 里面就可以看到带 Order By 的 SQL 啦。
return filteredCases.ToList();
}
当然也可以把他封装成一个方法:
protected MethodCallExpression GetOrderByMethodCallExpression(IQueryable sourceQuery, Type sourceType, string orderByPropName, EnumOrderByType orderByType)
{
if (orderByPropName != null && sourceType.GetProperty(orderByPropName) != null)
{
string orderByMethodName = orderByType == EnumOrderByType.ASC ? "OrderBy" : "OrderByDescending";
ParameterExpression orderByParam = Expression.Parameter(sourceType);
MemberExpression orderByMember = Expression.PropertyOrField(orderByParam, orderByPropName);
LambdaExpression orderByLambda = Expression.Lambda(orderByMember, orderByParam);
Type[] methodCallArgTypes = { sourceQuery.ElementType, orderByLambda.Body.Type };
MethodCallExpression methodCall = Expression.Call(typeof(Queryable), orderByMethodName, methodCallArgTypes, sourceQuery.Expression, orderByLambda);
return methodCall;
}
return null;
}
这样就完美了!