为RavenDb构建通用搜索:创建Expression<Func<T,object>>失败
背景
RavenDB的标准Search扩展方法定义如下:
public static IRavenQueryable<T> Search<T>(this IQueryable<T> self, Expression<Func<T, object>> fieldSelector, string searchTerms, decimal boost = 1m, SearchOptions options = SearchOptions.Guess, SearchOperator @operator = SearchOperator.Or) { MethodInfo method = (MethodInfo)MethodBase.GetCurrentMethod(); return (IRavenQueryable<T>)self.Provider.CreateQuery(Expression.Call(null, method, self.Expression, fieldSelector, Expression.Constant(searchTerms), Expression.Constant(boost), Expression.Constant(options), Expression.Constant(@operator))); }
当尝试实现一个支持通过属性名指定搜索字段的扩展方法时,调用时抛出ArgumentException,错误信息如下:
System.ArgumentException: 'Expression of type
'System.Func2[NoSqlModels.PhoneBookCategory,System.String]' cannot be used for parameter of type 'System.String' of method 'Raven.Client.Documents.Linq.IRavenQueryable1[NoSqlModels.PhoneBookCategory]
Search[PhoneBookCategory](System.Linq.IQueryable`1[NoSqlModels.PhoneBookCategory],
System.String, System.String, System.Decimal,
Raven.Client.Documents.SearchOptions,
Raven.Client.Documents.Queries.SearchOperator)' (Parameter
'arguments[1]')'
问题核心:通过属性名生成的表达式是Expression<Func<T, TProperty>>(TProperty为目标属性的实际类型),但标准Search方法要求的参数类型是Expression<Func<T, object>>,类型不匹配导致报错。
解决方法
需要将生成的属性表达式转换为返回类型为object的表达式树,关键是对属性表达式的返回值添加类型转换(处理值类型装箱和引用类型兼容转换)。
实现代码
public static IRavenQueryable<T> SearchByPropertyName<T>(this IQueryable<T> self, string propertyName, string searchTerms, decimal boost = 1m, SearchOptions options = SearchOptions.Guess, SearchOperator @operator = SearchOperator.Or) { // 创建输入参数表达式 ParameterExpression param = Expression.Parameter(typeof(T), "x"); // 获取指定属性的表达式 MemberExpression propertyExpr = Expression.PropertyOrField(param, propertyName); // 将属性返回值转换为object类型(值类型自动装箱,引用类型安全转换) Expression convertedExpr = Expression.Convert(propertyExpr, typeof(object)); // 构建符合要求的Expression<Func<T, object>> Expression<Func<T, object>> fieldSelector = Expression.Lambda<Func<T, object>>(convertedExpr, param); // 调用标准Search方法 return self.Search(fieldSelector, searchTerms, boost, options, @operator); }
原理说明
- 对于值类型属性(如int、DateTime):
Expression.Convert会生成装箱操作,将值类型实例转为object; - 对于引用类型属性(如string):
Expression.Convert会生成兼容的类型转换(string继承自object),不会改变原有表达式的语义; - 最终生成的表达式完全符合标准Search方法的参数类型要求,解决了类型不匹配的问题。
内容的提问来源于stack exchange,提问作者Stephan Steiner

