具有多个选择值的动态Linq查询
本文关键字:动态 Linq 查询 选择 | 更新日期: 2023-09-27 18:04:53
我想在多个列上实现一个过滤器,但是我不想写对于每个列一个新的查询。所以我实现了一个GetDistinctProperty函数,看起来像这样:
public ActionResult GetDistinctProperty(string propertyName)
{
var selector = CreateExpression<TDomain>(propertyName);
var query = this.InventoryService.GetAll(Deal);
var results = query.Select(selector).Distinct().ToList();
return Json(results, JsonRequestBehavior.AllowGet);
}
private static Expression<Func<T, object>> CreateExpression<T>(string propertyName)
{
// Specify the type we are accessing the member from
var param = Expression.Parameter(typeof(T), "x");
Expression body = param;
// Loop through members in specified property name
foreach (var member in propertyName.Split('.'))
{
// Access each individual property
body = Expression.Property(body, member);
}
var conversion = Expression.Convert(body, typeof(object));
// Create a lambda of this MemberExpression
return Expression.Lambda<Func<T, object>>(conversion, param);
}
让我们以我的propertyName SiteIdentifier为例。
选择器给我的值
{x => Convert(x.SiteIdentifier)}
,当我想看到结果时,它给了我以下错误:
Unable to cast the type 'System.String' to type 'System.Object'.
LINQ to Entities only supports casting EDM primitive or enumeration types.
当我尝试如下选择时:
var results = query.Select(x=>x.SiteIdentifier).Distinct().ToList();
它的工作原理。
有人知道吗?
在静态类型语言中使用Linq时,没有理由将属性名作为字符串传递。泛型和委托(Func)的引入就是为了淘汰这种逻辑。
你可以简单地传递表达式,而不是传递属性名:
public ActionResult GetDistinctProperty(Expression<Func<TDomain, TProp> selector)
{
var query = this.InventoryService.GetAll(Deal);
var results = query.Select(selector).Distinct().ToList();
return Json(results, JsonRequestBehavior.AllowGet);
}
用法:
GetDistinctProperty(x=> x.SiteIdentifier);
问题是,虽然IQueryable<T>
接口是协变的,但值类型不支持协变,因此不能将IQueryable<int>
视为IQueryable<object>
。另一方面,EF不喜欢将值类型强制转换为object
。
所以为了使它工作,你需要求助于非通用的IQueryable
接口。不幸的是,几乎所有Queryable
扩展方法都是围绕IQueryable<T>
构建的,因此您必须手动编写相应的调用。
public static partial class QueryableExtensions()
{
public static IQueryable SelectProperty(this IQueryable source, string path)
{
var parameter = Expression.Parameter(source.ElementType, "x");
var property = path.Split(',')
.Aggregate((Expression)parameter, Expression.PropertyOrField);
var selector = Expression.Lambda(property, parameter);
var selectCall = Expression.Call(
typeof(Queryable), "Select", new[] { parameter.Type, property.Type },
source.Expression, Expression.Quote(selector));
return source.Provider.CreateQuery(selectCall);
}
}
但是你需要一个Distinct
方法来处理IQueryable
:
public static partial class QueryableExtensions()
{
public static IQueryable Distinct(this IQueryable source)
{
var distinctCall = Expression.Call(
typeof(Queryable), "Distinct", new[] { source.ElementType },
source.Expression);
return source.Provider.CreateQuery(distinctCall);
}
}
现在您已经拥有了实现所讨论的方法所需的所有部分。但是还有一个重要的细节。为了能够创建List<object>
,您需要调用Cast<object>
。但是如果您使用IQueryable.Cast
扩展方法,您将从EF中获得相同的不支持异常。所以你需要显式地调用IEnumerable.Cast
:
public ActionResult GetDistinctProperty(string propertyName)
{
var query = this.InventoryService.GetAll(Deal);
var results = Enumerable.Cast<object>(
query.SelectProperty(propertyName).Distinct()).ToList();
return Json(results, JsonRequestBehavior.AllowGet);
}