具有多个选择值的动态Linq查询

时间:2016-08-30 09:23:57

标签: c# asp.net-mvc linq select

我想在多个列上实现过滤器,但我不想写  为每个列一个新的查询。所以我实现了一个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();

它有效。

任何想法?

2 个答案:

答案 0 :(得分:0)

在静态类型语言中使用Linq时,没有理由将属性名称作为字符串传递。泛型和代表(Func)被介绍使这种逻辑过时了。

您只需传递表达式而不是传递属性名称:

"rooms": {
          "$roomId": {
            ".write": "auth != null && auth.uid.contains($roomId)"
          }

用法:

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

答案 1 :(得分:0)

问题在于虽然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的{​​{1}}方法:

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>扩展方法,您将从EF获得相同的不支持的异常。因此,您需要明确调用IQueryable.Cast

IEnumerable.Cast