Dynamic LINQ Expression for sorting navigation property - linq

MVC3, Entity Framework 4.1 Code first.
Working with 2 tables
Model:
public class UniversityMaster
{
[Key]
public string UniversityId { get; set; }
public string UniversityName { get; set; }
}
public class ProgramMaster
{
[Key]
public string ProgramId { get; set; }
public string ProgramName { get; set; }
public string UniversityId { get; set; }
public virtual UniversityMaster University { get; set; } // navigation property
}
Dynamic expression for sorting (just to avoid a switch case statement):
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField="", string sortDirection="")
{
IQueryable<ProgramMaster> query = _dbSet;
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
MemberExpression property = Expression.PropertyOrField(pe, sortField);
//get a exception here if the sort field is of navigation property (University.UniversityName)
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(typeof(Queryable),
orderbydir, new Type[] { typeof(TEntity), property.Type }, query.Expression, Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery< ProgramMaster >(call);
return returnquery;
}
The page is displaying a grid with two columns Program Name and University Name using webgrid. The sorting work fine for Program Name column, however fails if sorted by University Name as this property is in UniversityMaster and the Expression.PropertyOrField searches this property in ProgramMaster. Here is the exception:
University.UniversityName' is not a member of type 'App.Core.Model.ProgramMaster
My question is how I make this work for navigation properties of my model class.
Hope I was able explain the scenario. Any help is appreciated.

Well that's because the MemberExpression is trying to call a member named Univerty.UniversityName on the parameter. What you want to do is call a member named Univerity on the parameter, then call UniversityName on that. Effectively, you need to iteratively resolve the property names.
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField = "", string sortDirection = "")
{
IQueryable<ProgramMaster> query = _dbSet;
var propertyNames = sortField.Split(".");
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
Expression property = pe;
foreach(var prop in propertyName)
{
property = Expression.PropertyOrField(property, prop);
}
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(
typeof(Queryable),
orderbydir,
new Type[] { typeof(TEntity), property.Type },
query.Expression,
Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery<ProgramMaster>(call);
return returnquery;
}

Microsoft has a DynamicQueryable class which can be used to dynamically construct certain portions of a LINQ query using strings. With this you can say myQuery.OrderBy("University.UniversityName") and it will handle building the expression. The same library also supports dynamic construction of SELECT and WHERE clauses.
You can find a copy of the source as part of the excellent EntityFramework.Extended package by Loresoft. Microsoft's file is at https://github.com/loresoft/EntityFramework.Extended/blob/master/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs

Related

RavenDB, Linq: Build an expression from a Property inside a nested hierarchy

I stored the objects of the following classes in a ravendb database:
public class Continent
{
public string Id { get; set; }
public string Name { get; set; }
public List<Country> Countries { get; set; }
}
public class Country
{
public string Name { get; set; }
public List<Province> Provinces { get; set; }
}
public class Province
{
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public string Address { get; set; }
}
I want to write a method that searches Continents having a specific city. The latter is a parameter of my method. In this method, I want to execute a dynamic query like the following: query= session.Query().Where(ConditionOnTheSearchedCity). But I can not formulate the Expression "ConditionOnTheSearchedCity" because I can not iterate till the Name of each City. In fact, to create an expression based on the Name of the City, I've tried the following which is not working:
ParameterExpression parameterExpression = Expression.Parameter(typeof(Continent), "p");
Expression NameExpression = Expression.PropertyOrField(
Expression.PropertyOrField(
Expression.PropertyOrField(
Expression.PropertyOrField(parameterExpression, "Countries"), "Provinces"),
"Cities"),
"Name");
Can you please help me? Thanks in advance
Why are you going to all the trouble of dynamically building Linq?
Why not just use the RavenDB's DocumentQuery API which allows you to easily build queries dynamically?
As I mentioned in the comments, the question is too broad, so I'll give you just a starting point by providing an example. For more information, take a look at How to: Use Expression Trees to Build Dynamic Queries.
Let have a parameter string cityName and want a filter like this
Expression<Func<Continent, bool>> filter = continent =>
continent.Countries.Any(country =>
country.Provinces.Any(province =>
province.Cities.Any(city => city.Name == cityName)));
It can be build dynamically like this
var city = Expression.Parameter(typeof(City), "city");
var cityCondition = Expression.Equal(Expression.PropertyOrField(city, "Name"), Expression.Constant(cityName));
var province = Expression.Parameter(typeof(Province), "province");
var provinceCondition = Expression.Call(
typeof(Enumerable), "Any", new[] { city.Type },
Expression.PropertyOrField(province, "Cities"),
Expression.Lambda(cityCondition, city));
var country = Expression.Parameter(typeof(Country), "country");
var countryCondition = Expression.Call(
typeof(Enumerable), "Any", new[] { province.Type },
Expression.PropertyOrField(country, "Provinces"),
Expression.Lambda(provinceCondition, province));
var continent = Expression.Parameter(typeof(Continent), "continent");
var continentCondition = Expression.Call(
typeof(Enumerable), "Any", new[] { country.Type },
Expression.PropertyOrField(continent, "Countries"),
Expression.Lambda(countryCondition, country));
var filter = Expression.Lambda<Func<Continent, bool>>(continentCondition, continent);

Trying to filter on a Nullable type using Expression Trees

I have pasted my entire test app below. It's fairly compact so I am hoping that it's not a problem. You should be able to simply cut and paste it into a console app and run it.
I need to be able to filter on any one or more of the Person objects' properties, and I don't know which one(s) until runtime. I know that this has beed discussed all over the place and I have looked into and am also using tools such as the PredicateBuilder & Dynamic Linq Library but the discussion aroung them tends to focus more on Sorting and ordering, and each have been struggling with their own issues when confronted with Nullable types. So I thought that I would attempt to build at least a supplemental filter that could address these particular scenarios.
In the example below I am trying to filter out the family members who were born after a certain date. The kick is that the DateOfBirth field on the objects being filterd is a DateTime property.
The latest error I am getting is
No coercion operator is defined between types 'System.String' and 'System.Nullable`1[System.DateTime]'.
Which is the problem. I have attempted several different means of casting and converting but to varying degrees of failure. Ultimately this will be applied against an EF database whcih has also balked at conversion methods such as DateTime.Parse(--).
Any assistence would be greatly appreciated!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Person> people = new List<Person>();
people.Add(new Person { FirstName = "Bob", LastName = "Smith", DateOfBirth = DateTime.Parse("1969/01/21"), Weight=207 });
people.Add(new Person { FirstName = "Lisa", LastName = "Smith", DateOfBirth = DateTime.Parse("1974/05/09") });
people.Add(new Person { FirstName = "Jane", LastName = "Smith", DateOfBirth = DateTime.Parse("1999/05/09") });
people.Add(new Person { FirstName = "Lori", LastName = "Jones", DateOfBirth = DateTime.Parse("2002/10/21") });
people.Add(new Person { FirstName = "Patty", LastName = "Smith", DateOfBirth = DateTime.Parse("2012/03/11") });
people.Add(new Person { FirstName = "George", LastName = "Smith", DateOfBirth = DateTime.Parse("2013/06/18"), Weight=6 });
String filterField = "DateOfBirth";
String filterOper = "<=";
String filterValue = "2000/01/01";
var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue);
var query = from p in people.AsQueryable().Where(oldFamily)
select p;
Console.ReadLine();
}
public static Expression<Func<T, bool>> ApplyFilter<T>(String filterField, String filterOper, String filterValue)
{
//
// Get the property that we are attempting to filter on. If it does not exist then throw an exception
System.Reflection.PropertyInfo prop = typeof(T).GetProperty(filterField);
if (prop == null)
throw new MissingMemberException(String.Format("{0} is not a member of {1}", filterField, typeof(T).ToString()));
Expression convertExpression = Expression.Convert(Expression.Constant(filterValue), prop.PropertyType);
ParameterExpression parameter = Expression.Parameter(prop.PropertyType, filterField);
ParameterExpression[] parameters = new ParameterExpression[] { parameter };
BinaryExpression body = Expression.LessThanOrEqual(parameter, convertExpression);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(body, parameters);
return predicate;
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
string Nickname { get; set; }
public int? Weight { get; set; }
public Person() { }
public Person(string fName, string lName)
{
FirstName = fName;
LastName = lName;
}
}
}
Update: 2013/02/01
My thought was then to convert the Nullabe type to it's Non-Nullable type version. So in this case we want to convert the <Nullable>DateTime to a simple DateTime type. I added the following code block before the call Expression.Convert call to determine and capture the type of the Nullable value.
//
//
Type propType = prop.PropertyType;
//
// If the property is nullable we need to create the expression using a NON-Nullable version of the type.
// We will get this by parsing the type from the FullName of the type
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
String typeName = prop.PropertyType.FullName;
Int32 startIdx = typeName.IndexOf("[[") + 2;
Int32 endIdx = typeName.IndexOf(",", startIdx);
String type = typeName.Substring(startIdx, (endIdx-startIdx));
propType = Type.GetType(type);
}
Expression convertExpression = Expression.Convert(Expression.Constant(filterValue), propType);
This actually worked in removing the Nullable-ness from the DateTime but resulted in the following Coercion error. I remain confused by this as I thought that the purpose of the "Expression.Convert" method was to do just this.
No coercion operator is defined between types 'System.String' and 'System.DateTime'.
Pushing on I explicitly parsed the value to a DateTime and plugged that into the mix ...
DateTime dt = DateTime.Parse(filterValue);
Expression convertExpression = Expression.Convert(Expression.Constant(dt), propType);
... which resulted in an exception that outpaces any knowledge I have of Expressions, Lambdas and their related ilk ...
ParameterExpression of type 'System.DateTime' cannot be used for delegate parameter of type 'ConsoleApplication1.Person'
I am not sure what's left to try.
The problem is that when generating binary expressions, the operands have to be of compatible types. If not, you need to perform a conversion on one (or both) until they are compatible.
Technically, you cannot compare a DateTime with a DateTime?, the compiler implicitly promotes one to the other which allows us to do our comparisons. Since the compiler is not the one generating the expression, we need to perform the conversion ourself.
I've tweaked your example to be more general (and working :D).
public static Expression<Func<TObject, bool>> ApplyFilter<TObject, TValue>(String filterField, FilterOperation filterOper, TValue filterValue)
{
var type = typeof(TObject);
ExpressionType operation;
if (type.GetProperty(filterField) == null && type.GetField(filterField) == null)
throw new MissingMemberException(type.Name, filterField);
if (!operationMap.TryGetValue(filterOper, out operation))
throw new ArgumentOutOfRangeException("filterOper", filterOper, "Invalid filter operation");
var parameter = Expression.Parameter(type);
var fieldAccess = Expression.PropertyOrField(parameter, filterField);
var value = Expression.Constant(filterValue, filterValue.GetType());
// let's perform the conversion only if we really need it
var converted = value.Type != fieldAccess.Type
? (Expression)Expression.Convert(value, fieldAccess.Type)
: (Expression)value;
var body = Expression.MakeBinary(operation, fieldAccess, converted);
var expr = Expression.Lambda<Func<TObject, bool>>(body, parameter);
return expr;
}
// to restrict the allowable range of operations
public enum FilterOperation
{
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
}
// we could have used reflection here instead since they have the same names
static Dictionary<FilterOperation, ExpressionType> operationMap = new Dictionary<FilterOperation, ExpressionType>
{
{ FilterOperation.Equal, ExpressionType.Equal },
{ FilterOperation.NotEqual, ExpressionType.NotEqual },
{ FilterOperation.LessThan, ExpressionType.LessThan },
{ FilterOperation.LessThanOrEqual, ExpressionType.LessThanOrEqual },
{ FilterOperation.GreaterThan, ExpressionType.GreaterThan },
{ FilterOperation.GreaterThanOrEqual, ExpressionType.GreaterThanOrEqual },
};
Then to use it:
var filterField = "DateOfBirth";
var filterOper = FilterOperation.LessThanOrEqual;
var filterValue = DateTime.Parse("2000/01/01"); // note this is an actual DateTime object
var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue);
var query = from p in people.AsQueryable().Where(oldFamily)
select p;
I don't know if this will work as-is for all cases but it certainly works for this particular case.
If you interrogate your body variable, you can see that the body of the expression you're creating is essentially DateOfBirth <= '2000/01/01'.
While on the face this may seem correct, you are trying to assign that body to a function that takes a Person (that's what T would be in your example) and returns a bool. You need to alter your logic such that the body reflects the input as a Person object, accesses the DateOfBirth property on that instance of the Person, and then performs the comparison.
In other words, the body of your expression has to take a T, find the right property on it, and then compare.

Comparing against child property in generic/dynamic linq predicate with reflection

I am having to generically build a comparative predicate for an Entity Framework Linq query. I'm using reflection and am able to build a single level Lambda expression without any trouble. However where I am starting to get stuck is I have an Entity that has a relationship
public class Parent {
public virtual Child child { get; set; }
.... Other Stuff...
}
public class Child {
public int property { get; set; }
public virtual Parent parent { get; set; }
.... Other Stuff.....
}
How can I can I pass in "Child.property" into Reflection to be able to create a lambda expression comparing and come up with a lambda expression similar to item => item.Child.property == value?
I think you are looking for this:
ParameterExpression parameter = Expression.Parameter(typeof(Parent), "item");
Expression child = Expression.PropertyOrField(parameter, "child");
Expression childProperty = Expression.PropertyOrField(child, "property");
int value = 1;
Expression comparison = Expression.Equal(childProperty, Expression.Constant(value));
Expression<Func<Parent, bool>> lambda = Expression.Lambda<Func<Parent, bool>>(comparison, parameter);
var sample = new[] { new Parent() { child = new Child() { property = 1 } } };
var result = sample.Where(lambda.Compile());
I assume you are wanting a generic solution supporting nested properties:
public Expression buildLambda(Type startingType, string propertyPath, object value) {
var parameter=Expression.Parameter(startingType,"item");
var valueExpression = Expression.Constant(value);
var propertyExpression=propertyPath.Split('.').Aggregate(parameter,(Expression parent,string path)=>Expression.Property(parent,path));
return Expression.Lambda(Expression.Equal(propertyExpression,valueExpression),parameter);
}

No generic method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments

I want to retrieve a specific record using IQueryable. But i get error 'No generic method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.'. I got the selected row id, but I cannot display it out. Here is my code.
internal static IQueryable GetRecordsFromPrimaryKeys(this IQueryable datasource, List<FilterDescriptor> primaryKeys)
{
IQueryable data = datasource;
ParameterExpression paramExp = null;
bool firstLoop = false;
System.Linq.Expressions.Expression predicate = null;
var RecordType = datasource.GetObjectType();
paramExp = RecordType.Parameter();
foreach (FilterDescriptor primaryKey in primaryKeys)
{
if (!(firstLoop))
{
predicate = data.Predicate(paramExp, primaryKey.ColumnName, primaryKey.Value, FilterType.Equals, false, RecordType);
firstLoop = true;
}
else
{
predicate = predicate.AndPredicate(data.Predicate(paramExp, primaryKey.ColumnName, primaryKey.Value, FilterType.Equals, false, RecordType));
}
}
if (paramExp != null && predicate != null)
{
var lambda = Expression.Lambda(predicate, paramExp);
data = data.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
"Where",
new Type[] { data.ElementType },
data.Expression,
lambda
)
);
}
return data;
}
My Code works well for IEnumerable/IQueryable/ICollection . But it throws the exception when i specify the class with the keyword virtual and type as ICollection. My code is
public class RoomType
{
public int ID { get; set; }
[MaxLength(10, ErrorMessage = "Room code cannot be longer than 10 characters.")]
public string Code { get; set; }
[MaxLength(50, ErrorMessage = "Room name cannot be longer than 50 characters.")]
public string Name { get; set; }
public virtual ICollection<RoomCategory> RoomCategories { get; set; }
}
Some random values gets appended to 'RecordType' while using the keyword 'virtual'. I think this leads to the exception. Still searching for the solution.
I don't know what is going wrong . Any suggestions welcome.
Thanks.
I just ran into a similar situation. The problem stems from the fact that in some cases you're dealing with the "proxy" not the actual entity. So, you want to make sure that RecordType matches data.ElementType.
try:
var recordType = datasource.GetObjectType();
// make sure we have the correct type (not the proxy)
if (recordType.BaseType.Name != "Object")
recordType = recordType.BaseType;
Or better yet, try:
var recordType = data.ElementType
Try to use typeof(Enumerable) instead of typeof(Queryable)

Why predicate isn't filtering when building it via reflection

I'm building a rather large filter based on an SearchObject that has 50+ fields that can be searched.
Rather than building my where clause for each one of these individually I thought I'd use some slight of hand and try building custom attribute suppling the necessary information and then using reflection to build out each of my predicate statements (Using LinqKit btw). Trouble is, that the code finds the appropriate values in the reflection code and successfully builds a predicate for the property, but the "where" doesn't seem to actually generate and my query always returns 0 records.
The attribute is simple:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public class FilterAttribute: Attribute
{
public FilterType FilterType { get; set; } //enum{ Object, Database}
public string FilterPath { get; set; }
//var predicate = PredicateBuilder.False<Metadata>();
}
And this is my method that builds out the query:
public List<ETracker.Objects.Item> Search(Search SearchObject, int Page, int PageSize)
{
var predicate = PredicateBuilder.False<ETracker.Objects.Item>();
Type t = typeof(Search);
IEnumerable<PropertyInfo> pi = t.GetProperties();
string title = string.Empty;
foreach (var property in pi)
{
if (Attribute.IsDefined(property, typeof(FilterAttribute)))
{
var attrs = property.GetCustomAttributes(typeof(FilterAttribute),true);
var value = property.GetValue(SearchObject, null);
if (property.Name == "Title")
title = (string)value;
predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);
}
}
var res = dataContext.GetAllItems().Take(1000)
.Where(a => SearchObject.Subcategories.Select(b => b.ID).ToArray().Contains(a.SubCategory.ID))
.Where(predicate);
return res.ToList();
}
The SearchObject is quite simple:
public class Search
{
public List<Item> Items { get; set; }
[Filter(FilterType = FilterType.Object, FilterPath = "Title")]
public string Title { get; set; }
...
}
Any suggestions will be greatly appreciated. I may well be going way the wrong direction and will take no offense if someone has a better alternative (or at least one that works)
You're not assigning your predicate anywhere. Change the line to this:
predicate = predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);

Resources