Order by using Iqueryable for datatable - c#

I have following function that prepare data for jquery datatable grid.
Now I am facing following error for Datatype other than string
Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
Code:
public GeneralResponse<IEnumerable<Holiday>> GetHolidays(string filter,
int initialPage,
int pageSize,
out int totalRecords,
out int recordFilterd,
int sortColumn,
string sortDirection)
{
var response = new GeneralResponse<IEnumerable<Holiday>>();
totalRecords = 0;
recordFilterd = 0;
filter = filter.Trim().ToLower();
try
{
Expression<Func<Holiday, dynamic>> expr;
switch (sortColumn)
{
case 0:
expr = p => p.HolidayDate;
break;
case 1:
expr = p => p.Name;
break;
case 2:
expr = p => p.ExchangeMarket.Name;
break;
default:
expr = p => p.CreatedOn;
break;
}
var data = holidayRepository.Query(true);
//var data = holidayRepository.GetAll(true).AsQueryable(); previous working one
totalRecords = data.Count();
//filter
if (!string.IsNullOrWhiteSpace(filter))
{
data = data.Where(x => x.Name.ToLower().Contains(filter));
//todo : Add date search as well
}
recordFilterd = data.Count();
//sort
data = sortDirection == "asc" ? data.OrderBy(expr) : data.OrderByDescending(expr);
data = data
.Skip(initialPage * pageSize)
.Take(pageSize);
var result = data.ToList();
response.Data = result;
}
catch (Exception e)
{
response.Error = true;
response.Exception = e;
}
return response;
}
// This method is under generic repository
public IQueryable<T> Query()
{
return Query(false);
}
earlier I was using IEnumerable which was first loading all data in list & then performing filter which was working fine (but not correct or best practice)
Now I am stuck with the filter part. How can I fix this error to have order by all type of property column?
I did lots of research but could not found the any solution.

dynamic or object cannot be used as TKey generic argument of EF Queryable.OrderBy (actually in any Queryable method expression) - it has to be the actual type of the key. Which in turn means you cannot use a common Expression<Func<...>> variable to hold the keySelector expression.
The solution is to use conditional .OrderBy[Descending] inside your switch / case block.
To make handling the ascending/descending option easier (and avoid expression duplication), start by creating a simple custom extension method like this:
namespace System.Linq
{
public static class QueryableExtensions
{
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, bool ascending)
{
return ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
}
}
}
Then move the switch / case block after the
recordFilterd = data.Count();
line and use the above helper method inside:
bool ascending = sortDirection == "asc";
switch (sortColumn)
{
case 0:
data = data.OrderBy(p => p.HolidayDate, ascending);
break;
case 1:
data = data.OrderBy(p => p.Name, ascending);
break;
case 2:
data = data.OrderBy(p => p.ExchangeMarket.Name, ascending);
break;
default:
data = data.OrderBy(p => p.CreatedOn, ascending);
break;
}

Related

Trying to create a morethan, equal or greaterthan dynamic filter for dates in linq

I have been trying to create a expression tree filter for Linq that take 2 dates and a string of possible values { "lessthan", "equals", "morethan" }. I wish to format the call to be something like Query.Where(CompareDates(x => x.left, right, "less than"));
I have the code:
public static IQueryable<TSource> CompareDates<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, DateTime?>> left,
DateTime? right,
string equality)
{
if (right == null || string.IsNullOrWhiteSpace(equality))
return source;
var p = left.Parameters.Single();
Expression member = p;
Expression leftExpression = Expression.Property(member, "left");
Expression rightParameter = Expression.Constant(right, typeof(DateTime));
BinaryExpression BExpression = null;
switch (equality)
{
case "lessthan":
BExpression = Expression.LessThan(leftExpression, rightParameter);
break;
case "equal":
BExpression = Expression.Equal(leftExpression, rightParameter);
break;
case "morethan":
BExpression = Expression.GreaterThan(leftExpression, rightParameter);
break;
default:
throw new Exception(String.Format("Equality {0} not recognised.", equality));
}
return source.Where(Expression.Lambda<Func<TSource, bool>>(BExpression, p));
}
Unfortunately it is producing an error of "System.ArgumentException: Instance property 'left' is not defined for type 'Model' at System.Linq.Expressions.Expression.Property(Expression expression, String propertyName) at SARRestAPI.Extensions.Expressions.CompareDates[TSource](IQueryable1 source, Expression1 src, DateTime supplied, String equality)"
Anyone have an ideas why this is happening?
Here we go; what you want to do is use the .Body of the incoming selector, not look for .left. Meaning, given an input selector of x => x.Foo.Bar.Blap, a constant, and a comparison, you want to construct something like x => x.Foo.Bar.Blap < someValue, by reusing both the x (the parameter, which you're already doing) and the body (x.Foo.Bar.Blap).
In code (note this works for any TValue, not just DateTime):
public enum Comparison
{
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual
}
public static IQueryable<TSource> Compare<TSource, TValue>(
this IQueryable<TSource> source,
Expression<Func<TSource, TValue>> selector,
TValue value,
Comparison comparison)
{
Expression left = selector.Body;
Expression right = Expression.Constant(value, typeof(TValue));
BinaryExpression body;
switch (comparison)
{
case Comparison.LessThan:
body = Expression.LessThan(left, right);
break;
case Comparison.LessThanOrEqual:
body = Expression.LessThanOrEqual(left, right);
break;
case Comparison.Equal:
body = Expression.Equal(left, right);
break;
case Comparison.NotEqual:
body = Expression.NotEqual(left, right);
break;
case Comparison.GreaterThan:
body = Expression.GreaterThan(left, right);
break;
case Comparison.GreaterThanOrEqual:
body = Expression.GreaterThanOrEqual(left, right);
break;
default:
throw new ArgumentOutOfRangeException(nameof(comparison));
}
return source.Where(Expression.Lambda<Func<TSource, bool>>(body, selector.Parameters));
}
Example usage (here using LINQ-to-Objects, but it should work for other LINQ backends, too):
var arr = new[] { new { X = 11 }, new { X = 12 }, new { X = 13 }, new { X = 14 } };
var source = arr.AsQueryable();
var filtered = source.Compare(x => x.X, 12, Comparison.GreaterThan);
foreach (var item in filtered)
{
Console.WriteLine(item.X); // 13 and 14
}
Note that with C# vCurrent you can do:
var body = comparison switch
{
Comparison.LessThan => Expression.LessThan(left, right),
Comparison.LessThanOrEqual => Expression.LessThanOrEqual(left, right),
Comparison.Equal => Expression.Equal(left, right),
Comparison.NotEqual => Expression.NotEqual(left, right),
Comparison.GreaterThan => Expression.GreaterThan(left, right),
Comparison.GreaterThanOrEqual => Expression.GreaterThanOrEqual(left, right),
_ => throw new ArgumentOutOfRangeException(nameof(comparison)),
};
return source.Where(Expression.Lambda<Func<TSource, bool>>(body, selector.Parameters));
which you might fine preferable.

Dynamic lambda expression doesn't work in LINQ to EF Where function

I'm trying to accomplish a page in asp.net mvc to filter a sql server view.
My page looks like this:
Each "filter" is a SearchViewModel which has a class definition like this:
public class SearchViewModel
{
//Property has a property called "SqlColumnName"
public Property Property { get; set; }
public Enums.SearchOperator Operator { get; set; }
public string Value { get; set; }
}
When I submit the form a IList<SearchViewModel> will be passed to the controller. Until here everything works fine.
In the controller class I'm passing the IList<SearchViewModel> to a helper method:
public static Func<ViewEvents, bool> GetLamdaExpression(IEnumerable<SearchViewModel> lstSearchViewModels)
{
Expression dynamiclambda = null;
Expression call;
//creating the first part of the lambda expression (select.Where(ve => ve.Name == "exp"))
// this function returns "(ve =>" as typeof ViewEvents
var param = Expression.Parameter(typeof(ViewEvents), "viewEvents");
//storing functions for use with the combiner
var containsMethod = typeof(string).GetMethod("Contains");
var equalsMethod = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
foreach (var lstSearchViewModel in lstSearchViewModels.Where(svm => svm.Value != null))
{
//get the property info of the property from the SearchViewModel
var property = typeof(ViewEvents).GetProperty(lstSearchViewModel.Property.SqlColumnName);
if (property == null)
throw new ObjectNotFoundException($"The object {typeof(ViewEvents).ToString()} does not have a property called {lstSearchViewModel.Property.SqlColumnName}");
//create the second part of the lambda expression
//this function returns "ve.Property"
var propertyAccess = Expression.MakeMemberAccess(param, property);
//add the "toLower" function to the property
//this function returns "(ve.Property.ToLower()"
var toLower = Expression.Call(propertyAccess, toLowerMethod);
//adds the operator to the lambda expression
//functions return p.ex.: ve.Property.ToLower().Contains("value")
// or ve.Property.ToLower().Equals("value") != true (NotEquals)
switch (lstSearchViewModel.Operator)
{
case Enums.SearchOperator.Contains:
call = Expression.Call(toLower, containsMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
break;
case Enums.SearchOperator.ContainsNot:
call = Expression.Call(toLower, containsMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
//adding ..Contains("value") != true ; used like .ContainsNot("value")
call = Expression.NotEqual(call, Expression.Constant(true));
break;
case Enums.SearchOperator.StartsWith:
call = Expression.Call(toLower, startsWithMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
break;
case Enums.SearchOperator.EndsWith:
call = Expression.Call(toLower, endsWithMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
break;
case Enums.SearchOperator.Equals:
call = Expression.Call(toLower, equalsMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
break;
case Enums.SearchOperator.EqualsNot:
call = Expression.Call(toLower, equalsMethod,
Expression.Constant(lstSearchViewModel.Value.ToLower()));
//adding ..Equals("value") != true ; used like .NotEquals("value")
call = Expression.NotEqual(call, Expression.Constant(true));
break;
default:
throw new ArgumentOutOfRangeException();
}
//Combind the filters with an and combiner
dynamiclambda = dynamiclambda == null ? call : Expression.And(dynamiclambda, call);
}
if (dynamiclambda == null)
{
throw new InvalidOperationException("No dynamiclambda was created");
}
//gets the actual lambda expression like: (ve => ve.Property.ToLower().Contains("value") AND ve.Property.ToLower().Equals("value") ...
var predicate = Expression.Lambda<Func<ViewEvents, bool>>(dynamiclambda, param);
var compiled = predicate.Compile();
return compiled;
}
Now when I enter a value of "1" in the input field next to "Typ", the variable predicate of the function contains the following value:
{viewEvents => viewEvents.Level.ToLower().Contains("1")}
Later on in the controller the compiled predicate will be used to query the view of the database with entity framework 6 like this:
var lambda = Helper.GetLamdaExpression(lstSearchViewModels);
var eventEntries = DataBase.ViewEvents.Where(lambda);
and here comes the "error". No rows were returned from the database. But when I change the code to
var lambda = Helper.GetLamdaExpression(lstSearchViewModels);
var eventEntries = DataBase.ViewEvents.Where(viewEvents => viewEvents.Level.ToLower().Contains("1"));
The expected number of rows are returned.
Anyone an idea where the bug is?
The problem is that you must use an Expression<Func<>> and not a Func<>. The first contains an expression tree that can be parsed to convert the Where clause into SQL. This can't be done with the latter.
Besides you're overcomplicating: you can return a lambda directly, without doing so much work. This is a simplified example:
public Expression<Func<ViewEvents, bool>> GetEqualIdLambda(int id)
{
return (ViewEvents ve) => ve.Id == id;
}

Telerik MVC Extensions Grid - How to have the grid filter apply to initial LINQ query or get passed down to db?

Currently in my MVC grids I am using normal Server Bindings, and the filters then are appended to the URL as query strings. The problem with this method is that if I am querying a grid that by default has thousands of records, but I am only displaying the first 30 records on the first page (paging filter) of my grid. Same thing would apply to a string filter for last name. I filter my 2000 records for last name smith, get 100 records, only 30 displayed on first page. I will then actually query a person object, have the full 2k objects returned, filter it to 100, and display 30. This is horribly inefficient.
How does one pass the filter parameters into a LINQ query for example so the initial query only returns results shown on that page? Also is there some automated way to do this generically for any grid? Or would you have to write this logic for every single grid you have?
I know if ToGridModel, which I use when exporting a grid to excel:
public ActionResult Export(int page, string orderBy, string filter, string groupBy)
{
//Get the data representing the current grid state - page, sort and filter
List<Building> buildings = _buildingService.GetList();
List<BuildingModel> buildingModels = new List<BuildingModel>();
buildings.ForEach(x => buildingModels.Add(_buildingService.ConvertToModel(x)));
GridModel model = buildingModels.AsQueryable().ToGridModel(1, buildingModels.Count, orderBy, groupBy, filter);
MemoryStream fileOutput = ExcelUtil.ExportGridModelToExcel<BuildingModel>(model);
return File(fileOutput.ToArray(), //The binary data of the XLS file
"application/vnd.ms-excel", //MIME type of Excel files
"BuildingsReport.xls"); //Suggested file name in the "Save as" dialog which will be displayed to the end user
}
but I guess then another problem is that the grid itself is made up of ViewModels, not the POCO object. So even then, when I export to excel. I have to requery the whole result set, and then filter it down.
Surely there is a better way to do this?
You can use Custom Binding to do this.
simple example of it you can read here: Telerik Documentation
For more generic approach you can use method CreateFilterExpression of class FilterDescriptor
Update
Generic example:
[GridAction(EnableCustomBinding = true)]
public ViewResult GetRecords(GridCommand command)
{
using (var context = _contextFactory())
{
var records = context.Set<Records>();
if (command.FilterDescriptors.Any()) //RequestNumber
{
var filter = command.FilterDescriptors.GetFilter<ChangeRecord>();
records = records.Where(filter);
}
return View(new GridModel(records.ToArray()));
}
}
public static class GridCommandExtensions
{
public static Expression<Func<TGridModel, bool>> GetFilter<TGridModel>(this IList<IFilterDescriptor> filterDescriptors)
{
var filters = filterDescriptors.SelectMany(GetAllFilterDescriptors).ToArray();
var parameter = Expression.Parameter(typeof(TGridModel), "c");
if (filters.Length == 1)
return Expression.Lambda<Func<TGridModel, bool>>(GetExpression(parameter, filters[0]), parameter);
Expression exp = null;
for (int index = 0; index < filters.Length; index += 2) // условие И
{
var filter1 = filters[index];
if (index == filters.Length - 1)
{
exp = Expression.AndAlso(exp, GetExpression(parameter, filter1));
break;
}
var filter2 = filters[index + 1];
var left = GetExpression(parameter, filter1);
var right = GetExpression(parameter, filter2);
exp = exp == null
? Expression.AndAlso(left, right)
: Expression.AndAlso(exp, Expression.AndAlso(left, right));
}
return Expression.Lambda<Func<TGridModel, bool>>(exp, parameter);
}
private static Expression GetExpression(ParameterExpression parameter, FilterDescriptor filter)
{
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var property = filter.Member.Contains(".") ?
filter.Member.Split('.').Aggregate((Expression)parameter, Expression.Property) // (x => x.Property.FieldName)
: Expression.Property(parameter, filter.Member); // (x => x.FieldName)
var constant = Expression.Constant(filter.Value); // значение для выражения
switch (filter.Operator)
{
case FilterOperator.IsEqualTo:
return Expression.Equal(property, constant);
case FilterOperator.IsNotEqualTo:
return Expression.NotEqual(property, constant);
case FilterOperator.Contains:
return Expression.Call(property, containsMethod, constant);
case FilterOperator.StartsWith:
return Expression.Call(property, startsWithMethod, constant);
case FilterOperator.EndsWith:
return Expression.Call(property, endsWithMethod, constant);
case FilterOperator.IsGreaterThan:
return Expression.GreaterThan(property, constant);
case FilterOperator.IsGreaterThanOrEqualTo:
return Expression.GreaterThanOrEqual(property, constant);
case FilterOperator.IsLessThan:
return Expression.LessThan(property, constant);
case FilterOperator.IsLessThanOrEqualTo:
return Expression.LessThanOrEqual(property, constant);
default:
throw new InvalidOperationException(string.Format("Неподдерживаемая операция {0} для колонки {1}", filter.Operator, filter.Member));
}
}
public static IEnumerable<FilterDescriptor> GetAllFilterDescriptors(this IFilterDescriptor descriptor)
{
var filterDescriptor = descriptor as FilterDescriptor;
if (filterDescriptor != null)
{
yield return filterDescriptor;
yield break;
}
var compositeFilterDescriptor = descriptor as CompositeFilterDescriptor;
if (compositeFilterDescriptor != null)
{
if (compositeFilterDescriptor.LogicalOperator == FilterCompositionLogicalOperator.Or)
throw new ArgumentOutOfRangeException("descriptor", "В фильтрах не поддерживается OR");
foreach (var childDescriptor in compositeFilterDescriptor.FilterDescriptors.SelectMany(GetAllFilterDescriptors))
yield return childDescriptor;
}
}
}
if(request.Filters.Count > 0)
{
foreach (Kendo.Mvc.FilterDescriptor f in request.Filters)
{
f.Value = f.Value.ToString().Trim();
}
}
I prefer to use
IEnumerable<Building> buildings = _buildingService.GetIEnumerable().AsQueryable().ToGridModel(page, pageSize, orderBy, string.Empty, filter).Data.Cast<Building>();

DataTables multisort columns .net mvc hint

i'm using Datatables Jquery Extension and i've made multicolum sorting reading those posts:
http://www.codeproject.com/Articles/155422/jQuery-DataTables-and-ASP-NET-MVC-Integration-Part#Sorting
http://farm-fresh-code.blogspot.it/2012/02/mvc-jquery-ui-and-datatable-pluginajax.html
My question is, those two methods are dependent to The model that i'm using in my Controller, in this case
If i need to use similar code in other controllers i need to replicate that and changing model fields and the Type with others, for example .
This seems to me not very DRY.
How to Proceed? Is controller the good position for those two methods?
private IOrderedQueryable<User> CreateSortedQuery(DataTableParameterModel parameterModel, IQueryable<User> baseQuery)
{
var orderedQuery = (IOrderedQueryable<User>)baseQuery;
for (int i = 0; i < parameterModel.iSortingCols; ++i)
{
var ascending = string.Equals("asc", parameterModel.sSortDir[i], StringComparison.OrdinalIgnoreCase);
int sortCol = parameterModel.iSortCol[i];
Expression<Func<User, string>> orderByExpression = GetOrderingFunc(sortCol);
if (orderByExpression != null)
{
if (ascending)
{
orderedQuery = (i == 0)
? orderedQuery.OrderBy(orderByExpression)
: orderedQuery.ThenBy(orderByExpression);
}
else
{
orderedQuery = (i == 0)
? orderedQuery.OrderByDescending(orderByExpression)
: orderedQuery.ThenByDescending(orderByExpression);
}
}
else
{
if (ascending)
{
orderedQuery = (i == 0)
? orderedQuery.OrderBy(c => c.Id)
: orderedQuery.ThenBy(c => c.Id);
}
else
{
orderedQuery = (i == 0)
? orderedQuery.OrderByDescending(c => c.Id)
: orderedQuery.ThenByDescending(orderByExpression);
}
}
}
return orderedQuery;
}
private Expression<Func<User, string>> GetOrderingFunc(int ColumnIndex)
{
Expression<Func<User, string>> InitialorderingFunction;
switch (ColumnIndex)
{
case 1:
InitialorderingFunction = c => c.FirstName;
break;
case 2:
InitialorderingFunction = c => c.LastName;
break;
case 3:
InitialorderingFunction = c => c.UserName;
break;
case 4:
InitialorderingFunction = c => c.Email;
break;
case 5:
InitialorderingFunction = c => c.BusinessName;
break;
default:
InitialorderingFunction = null;
break;
}
return InitialorderingFunction;
}
I guess, your question is pretty close to these two answers:
Property name evaluating from expression:
public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
var expression = (MemberExpression)action.Body;
string fieldName = expression.Member.Name;
and
Applying linq sorting passing string values with LINQ Dynamic Query Library:
var result = data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy(fieldName + " asc");

Dynamically Modifying a LINQ Query Based on a Related Table

I am dynamically creating a LINQ query based on various search criteria.
As an example, let's say I am searching a Automobiles table, and I have an option to filter by ratings. I have two controls:
Compare type: [At least], [At most], [Less than], [Greater than], and [Equal].
Value: The value to compare the rating against.
So the user could, for example, select the compare type [At least] and the value 3, and my code needs to create a query that limits results to automobile ratings greater than or equal to 3.
I found a great solution given by VinayC in the question How to implement search functionality in C#/ASP.NET MVC. His DynamicWhere() method dynamically creates part of the expression that would produce the correct filter.
My problem is that my primary query type is Automobile but my ratings are in a separate table (Automobile.Ratings). How could I implement this same technique and filter on a type other than my primary query type?
Thanks for any tips.
Since the number of operations that you have is small, finite, and known at comiple time, you can simply handle it with a switch:
IQueryable<Something> query = GetQuery();
int ratingToComareWith = 1;
string operation = "Equal";
switch (operation)
{
case ("Equal"):
query = query.Where(item => item == ratingToComareWith);
break;
case ("Less Than"):
query = query.Where(item => item < ratingToComareWith);
break;
}
Here's an Entity Framework friendly alternative to expression building. Of course you will want to validate column and op to prevent SQL injection.
// User provided values
int value = 3;
string column = "Rating";
string op = "<";
// Dynamically built query
db.Database.SqlQuery<Automobile>(#"select distinct automobile.* from automobile
inner join ratings on ....
where [" + column + "] " + op + " #p0", value);
Here is a method to build conditions for nested collections or types for linq-to-entities.
Restructured for your needs:
public static Expression GetCondition(Expression parameter, object value, OperatorComparer operatorComparer, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetCondition(childParameter, test, innerProperties);
if (isCollection)
{
//build methodexpressioncall
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
navigationPropertyPredicate = Expression.Call(anyMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(value,value.GetType());
if(!new List<OperatorComparer> {OperatorComparer.Contains,OperatorComparer.StartsWith}.Contains(operatorComparer))
{
navigationPropertyPredicate = Expression.MakeBinary((ExpressionType)operatorComparer,left, right);
}
else
{
var method = GetMethod(childProperty.PropertyType, operatorComparer); //get property by enum-name from type
navigationPropertyPredicate = Expression.Call(left, method, right);
}
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static MethodInfo GetMethod(Type type,OperatorComparer operatorComparer)
{
var method = type.GetMethod(Enum.GetName(typeof(OperatorComparer),operatorComparer));
return method;
}
public enum OperatorComparer
{
Equals = ExpressionType.Equal,
Contains,
StartsWith,
GreaterThan = ExpressionType.GreaterThan
....
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
}
You could replace the params string[] with params Expression(Func(T,object)), if you want. Would need some more work to do it that way. You would need to definie nested collections with a syntax like
item => item.nestedCollection.Select(nested => nested.Property)
and rewrite the expression with the help of an expressionvisitor.

Categories