I want to implement a filter on multiple columns, but I don't want to write for every column a new query. So I implemented a GetDistinctProperty function which looks like this :
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);
}
Let's take as example that I have as propertyName SiteIdentifier.
The selector gives me as value
{x => Convert(x.SiteIdentifier)}
and when I want to see the results it gives me the following error :
Unable to cast the type 'System.String' to type 'System.Object'.
LINQ to Entities only supports casting EDM primitive or enumeration types.
When I try the select as follow :
var results = query.Select(x=>x.SiteIdentifier).Distinct().ToList();
it works.
Anyone any Idea?
The problem is that although
IQueryable<T>
interface is covariant, covariance is not supported for value types, soIQueryable<int>
cannot be treated asIQueryable<object>
. From the other side, EF does not like casting value type toobject
.So in order to make it work, you need to resort to non generic
IQueryable
interface. Unfortunately almost allQueryable
extension methods are build aroundIQueryable<T>
, so you have to manually compose a corresponding calls.For instance, in order to select property by name (path), you'll need something like this:
But then you'll need a
Distinct
method that works onIQueryable
:Now you have all the necessary pieces to implement the method in question. But there is another important detail though. In order to be able to create
List<object>
you need to callCast<object>
. But if you useIQueryable.Cast
extension method you'll get the same not supported exception from EF. So you need to call explicitly theIEnumerable.Cast
instead:There is no reason to pass the property name as string when using Linq in a statically typed language. Generics and delegates (Func) got introduced to make this kind of logic obsolete.
You can simply pass the expression instead of passing the property name:
Usage: