2016-08-30 55 views
1

我想在多列上实现一个过滤器,但我不想为每个列都写一个新的查询 。所以,我实现了一个GetDistinctProperty功能,看起来像这样:具有多个选择值的动态Linq查询

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

它的工作原理。

任何任何想法?

+0

这不是一个有效的语法:'{X =>转换(x.SiteIdentifier)}'除非你有一个名为'Convert'函数接受单个参数。 – user3185569

+0

不,我知道,这是CreateExpression (propertyName)的结果;我希望它像这样x => x。SiteIdentifier – Walter

+0

如何让'propertyName'传递给'GetDistinctProperty'方法? –

回答

0

在静态类型语言中使用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); 
+0

这不是我想要的,现在我必须为每个属性调用该函数。我只是想创建一个动态x => x.Propertyname – Walter

+0

@Walter你是什么意思*我必须为每个属性调用函数*? – user3185569

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方法上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); 
}