Expression class, change boolean to string compare - c#

Our database had IsActive booleans everywhere as a soft delete. We have entity framework blocking all requests with that flag deactivated. We recently changed out database to instead have a statusCode string instead of an IsActive flag.
How would I change the below expression to instead check to see if the StatusCode == "ACTIVE"
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var isActiveProperty = entityType.FindProperty("IsActive");
if (isActiveProperty != null && isActiveProperty.ClrType == typeof(bool))
{
var parameter = Expression.Parameter(entityType.ClrType, "p");
var filter = Expression.Lambda(Expression.Property(parameter, isActiveProperty.PropertyInfo), parameter);
entityType.QueryFilter = filter;
}
}
I tried changing the types to the following, but it is not working. Lambda's written out this way is throwing me off (Lambda/parameter long form). Somewhere I need to compare (isActivyProperty == "ACTIVE")
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var isActiveProperty = entityType.FindProperty("StatusCode");
if (isActiveProperty != null && isActiveProperty.ClrType == typeof(string))
{
var parameter = Expression.Parameter(entityType.ClrType, "p");
var filter = Expression.Lambda(Expression.Property(parameter, isActiveProperty.PropertyInfo), parameter);
entityType.QueryFilter = filter;//entityType.SetQueryFilter(filter);//Update for ef 3.0
}
}

You were generating code like the following for each type;
.QueryFilter = (p) => p.IsActive;
Now you want
.QueryFilter = (p) => p.StatusCode == "ACTIVE";
I find it useful to write the expression I want in C#, then use the debugger to see how the C# compiler turned that into an expression graph. Or look through the static methods on the Expression class and guess what I might need.
In your case you need your lambda body to include the extra equality test and constant value;
Expression.Equal(Expression.Property(...), Expression.Constant("ACTIVE"))
But there are other ways to use the C# compiler to create the Expression you want. For example;
public interface HasStatus {
StatusCode { get; set; }
}
public static void SetQueryFilter<T>(ModelBuilder builder) where T:HasStatus =>
builder.Entity<T>().HasQueryFilter(p => p.StatusCode == "ACTIVE");
typeof(...).GetMethod("SetQueryFilter")
.MakeGenericMethod(entityType.ClrType)
.Invoke(null, new object[]{ builder });

I think you're after:
Expression.Lambda(Expression.Equal(Expression.Property(parameter, "StatusCode"),
Expression.Constant("ACTIVE", typeof(string))), parameter);
Note: if FindProperty returns the member, you may also be able to use it in Expression.Property to avoid a second reflection lookup:
Expression.Lambda(Expression.Equal(Expression.Property(parameter, isActiveProperty),
Expression.Constant("ACTIVE", typeof(string))), parameter);

Related

Expression trees in middle of regular LINQ method chaining; dynamic and nullable types?

I'm trying to write a dynamic query but having trouble when dealing with dynamic types in the OrderBy section of my query. I've seen some solutions using method call expressions, which may work for me, but I don't know how to fit that into my existing code where I am using method chaining on either side. It's possible I can just use dynamic as my type but I don't think that will work with Nullable types.
public async Task<IEnumerable<MyEntityModel>> GetEntities(QueryEntityResource request)
{
IQueryable<MyEntityModel> queryableData = ...;
Expression whereLambdaExp = ...;
queryableData = queryableData.Where(whereLambdaExp);
ParameterExpression param = Expression.Parameter(typeof(MyEntityModel));
PropertyInfo property = typeof(MyEntityModel).GetProperty(request.sortModel.ColId);
Expression propExpr = Expression.Property(param, property);
var lambdaExpression = Expression.Lambda<Func<MyEntityModel, dynamic>>(propExpr , new ParameterExpression[] { param }); // can't use dynamic
// Errors here; can't use dynamic type in lambda expression -- Need to specify types
if (request.sortModel.Sort == "asc")
{
queryableData = queryableData.OrderBy(lambdaExpression);
}
if (request.sortModel.Sort == "desc")
{
queryableData = queryableData.OrderByDescending(lambdaExpression);
}
queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);
return queryableData.ToListAsync();
}
If I use dynamic it won't work with my nullable types:
System.ArgumentException: Expression of type 'System.Nullable`1[System.Int32]' cannot be used for return type 'System.Object'
This is untested; but I think it is close to what you need (I feel like I've written this code before... also, see this answer):
You can build calls to OrderBy instantiated to the types you need and then build a new IQueryable. My method removes the async as I couldn't build a quick test for that in LINQPad.
public IEnumerable<MyEntityModel> GetEntities(QueryEntityResource request) {
IQueryable<MyEntityModel> queryableData = default;
Expression<Func<MyEntityModel, bool>> whereLambdaExp = default;
queryableData = queryableData.Where(whereLambdaExp);
var param = Expression.Parameter(typeof(MyEntityModel));
var propExpr = Expression.Property(param, request.sortModel.ColId);
var lambdaExpression = Expression.Lambda(propExpr, param);
if (request.sortModel.Sort == "asc" || request.sortModel.Sort == "desc")
queryableData = queryableData.Provider.CreateQuery<MyEntityModel>(Expression.Call(typeof(Queryable), request.sortModel.Sort == "asc" ? "OrderBy" : "OrderByDescending",
new[] { typeof(MyEntityModel), propExpr.Type }, queryableData.Expression, Expression.Quote(lambdaExpression)));
queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);
return queryableData.ToList();
}

How to make LINQ-to-Objects handle projections?

I have implemented a basic (naive?) LINQ provider that works ok for my purposes, but there's a number of quirks I'd like to address, but I'm not sure how. For example:
// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
My IQueryProvider implementation had a CreateQuery<TResult> implementation looking like this:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return (IQueryable<TResult>)Activator
.CreateInstance(typeof(ViewSet<>)
.MakeGenericType(elementType), _view, this, expression, _context);
}
Obviously this chokes when the Expression is a MethodCallExpression and TResult is a string, so I figured I'd execute the darn thing:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof(TResult));
return (IQueryable<TResult>)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable<TResult>)Execute(methodCallExpression);
}
throw new NotSupportedException(string.Format("Expression '{0}' is not supported by this provider.", expression));
}
So when I run var vendorCodes = context.Vendors.Select(e => e.Key); I end up in my private static object Execute<T>(Expression,ViewSet<T>) overload, which switches on the innermost filter expression's method name and makes the actual calls in the underlying API.
Now, in this case I'm passing the Select method call expression, so the filter expression is null and my switch block gets skipped - which is fine - where I'm stuck at is here:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var returnType = method.Type.GenericTypeArguments[0];
var expType = typeof (Func<,>).MakeGenericType(typeof (T), returnType);
var body = method.Arguments[1] as Expression<Func<T,object>>;
if (body != null)
{
// body is null here because it should be as Expression<Func<T,expType>>
var compiled = body.Compile();
return viewSet.Select(string.Empty).AsEnumerable().Select(compiled);
}
}
What do I need to do to my MethodCallExpression in order to be able to pass it to LINQ-to-Objects' Select method? Am I even approaching this correctly?
(credits to Sergey Litvinov)
Here's the code that worked:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var lambda = ((UnaryExpression)method.Arguments[1]).Operand as LambdaExpression;
if (lambda != null)
{
var returnType = lambda.ReturnType;
var selectMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Select");
var typedGeneric = selectMethod.MakeGenericMethod(typeof(T), returnType);
var result = typedGeneric.Invoke(null, new object[] { viewSet.ToList().AsQueryable(), lambda }) as IEnumerable;
return result;
}
}
Now this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
Can look like this:
var vendorCodes = context.Vendors.Select(e => e.Key);
And you could even do this:
var vendors = context.Vendors.Select(e => new { e.Key, e.Name });
The key was to fetch the Select method straight from the Queryable type, make it a generic method using the lambda's returnType, and then invoke it off viewSet.ToList().AsQueryable().

Modifying Linq to remove repetitiveness

private readonly IRepository<Order> _orderRepo; // initialized in the constructor
IRepository<Order> GetOrder(string orderstate)
{
if(orderstate == null)
{
return null;
}
IQueryable<Order> query = null;
if(orderstate == "OrderStateChanged")
{
query = (from c in _orderRepo.Table
where c.OrderStateChanged != 0
select c
);
}
else if (orderstate == "PaymentStateChanged")
{
query = (from c in _orderRepo.Table
where c.PaymentStateChanged != 0
select c
);
}
/*More else if statement*/
}
I used LINQ to extract data from the repo and I have more else if statement. I want to remove the repetitive behavior of my code.
I have a clue "Expression Tree" but I can't understand How can I use that in my code Or suggest me any other way to remove
repetitiveness.
If you really want to prevent manually mapping orderState to members (using if/else, switch, IDictionary, ...), your only option are indeed expression trees:
var orderType = typeof(Order);
var param = Expression.Parameter(orderType);
var member = orderType.GetMember(orderState).FirstOrDefault();
if (member == null)
{
/* requested member of "Order" does not exist */
}
var filter = Expression.Lambda<Func<Order, bool>>( // "param => param.member != 0"
Expression.NotEqual( // "param.member != 0"
Expression.MakeMemberAccess(param, member), // "param.member"
Expression.Constant(0)), // "0"
param); // "param =>"
query = _orderRepo.Table.Where(filter);
Alternative (simpler, but throws ArgumentException if the member doesn't exist)
var orderType = typeof(Order);
var param = Expression.Parameter(orderType);
var member = Expression.PropertyOrField(param, orderState); // may throw ArgumentException!
var filter = Expression.Lambda<Func<Order, bool>>(
Expression.NotEqual(member, Expression.Constant(0)),
param);
query = _orderRepo.Table.Where(filter);
This way you are really generic, even if the object layout of Order changes. One disadvantage is of course the risk of invalid orderState values leading to non-existing members of Order but I'm sure you had some kind of mechanism already, anyway.
Further reading
MSDN - Expression Trees
MSDN - How to: Use Expression Trees to Build Dynamic Queries
CodeProject - Expression Tree Basics
You won't get it much better, but the extension method syntax is (IMHO) a bit more concise for this sort of code
IEnumerable<Order> GetOrder(string orderstate)
{
if(orderstate == null)
{
return null;
}
IQueryable<Order> query = _orderRep.Table;
if(orderstate == "OrderStateChanged")
{
query = query.Where(c => c.OrderStateChanged != 0);
}
else if (orderstate == "PaymentStateChanged")
{
query = query.Where(c => c.PaymentStateChanged != 0);
}
/*More else if statement*/
}
If you need this filtering in multiple places, you could maintain some kind of dictionary with the expression predicates:
static IDictionary<string, Expression<Func<Order,bool>> Predicates = new Dictionary<string, Expression<Func<Order,bool>>
{
{"OrderStateChanged", o => o.OrderStateChanged != 0},
{"OrderPaymentChanged", o => o.PaymentStateChanged != 0},
};
Then your method could become something like:
IEnumerable<Order> GetOrder(string orderstate)
{
if (orderstate == null || !Predicates.ContainsKey(orderstate))
return null; // or throw exception
var predicate = Predicates[orderstate];
return _orderRep.Table.Where(predicate);
}
LINQ is composable by nature. This means that you can do this:
var query = _orderRepoTable.AsQueryable();
switch (orderState)
{
case "OrderStateChanged": query = query.Where(c => c.OrderStateChanged != 0); break;
...
}
// Now query has the filters you want.
I'd still stick with the explicit filters, rather than using expression trees to build them based on orderState directly - that's just asking for trouble. Using an enum for orderState might also be a good idea. Don't worry, this isn't really code repetition - it's just a definition of a language, basically.

How to build a collection filter via expression trees in c#

I'm building a filtering system for UserProfiles based on known properties but unknown (until runtime) combination of filtering conditions.
In my previous question How do I create a generic Expression that has an expression as a parameter, I've figured out a way to have a FilterDefinition for any value property reachable from User entity via navigation properties (i.e. (User)u=> u.NavigationProperty.AnotherNavigationProperty.SomeValue)
and I have a method that can return a predicate as Expression<Func<User,bool>> for a given property, operation ( > < == etc ) and a value.
Now the time has come to filter them on collection properties as well.
Say for example User has CheckedOutBooks collection (which is a total fiction, but will do)
And I need to create a filter definition for Name property of CheckedOutBooks collection on User object.
What I have:
A collection of Users
User class has a collection of Books
now I would like to create a method
Expression<Func<User,bool>> GetPredicate(Expression<User,TProperty>, Operations operation, TProperty value)
That I can call like GetPredicate(u=>u.Books.Select(b=>b.Name), Operations.Contains, "C# in a nutshell")
and get an expression back similar to
u=>u.Books.Any(b=>b.Name == "C# in a nutshell")
I'm thinking maybe it will be easier to split first parameter in two to achieve this.
Maybe u=>u.Books and b=>b.Name will do better?
EDIT:
what I got so far:
class FilterDefinitionForCollectionPropertyValues<T>:FilterDefinition, IUserFilter
{
public Expression<Func<UserProfile, IEnumerable<T>>> CollectionSelector { get; set; }
public Expression<Func<T, string>> CollectionPropertySelector { get; set; }
public Expression<Func<Profile.UserProfile, bool>> GetFilterPredicateFor(FilterOperations operation, string value)
{
var propertyParameter = CollectionPropertySelector.Parameters[0];
var collectionParameter = CollectionSelector.Parameters[0];
// building predicate to supply to Enumerable.Any() method
var left = CollectionPropertySelector.Body;
var right = Expression.Constant(value);
var innerLambda = Expression.Equal(left, right);
Expression<Func<T, bool>> innerFunction = Expression.Lambda<Func<T, bool>>(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(T));
var outerLambda = Expression.Call(method, Expression.Property(collectionParameter, typeof(UserProfile).GetProperty("StaticSegments")), innerFunction);
throw new NotImplementedException();
}
}
Now this one works awesomely and does exactly what's needed, now the only thing I need to figure out is how to replace typeof(UserProfile).GetProperty("StaticSegments")) somehow to use CollectionPropertySelector that is in current example would be (UserProfile)u=>u.StaticSegments
You're almost done. Now you just need to do a little trick - wrap your CollectionPropertySelector lambda expression in the CollectionSelector lambda expression.
Expression<Func<TParent,bool>> Wrap<TParent,TElement>(Expression<Func<TParent, IEnumerable<TElement>>> collection, Expression<Func<TElement, bool>> isOne, Expression<Func<IEnumerable<TElement>, Func<TElement, bool>, bool>> isAny)
{
var parent = Expression.Parameter(typeof(TParent), "parent");
return
(Expression<Func<TParent, bool>>)Expression.Lambda
(
Expression.Invoke
(
isAny,
Expression.Invoke
(
collection,
parent
),
isOne
),
parent
);
}
You may have to change this a bit to be used for your particular scenario, but the idea should be clear. My test looked basically like this:
var user = new User { Books = new List<string> { "Book 1", "Book 2" }};
var query = Wrap<User, string>(u => u.Books, b => b.Contains("Bookx"), (collection, condition) => collection.Any(condition));
So you specify the collection selector, predicate and predicate operator, and you're done.
I've written it as a generic method for clarity, but it's dynamic, not strongly typed in essence, so it should be pretty easy to change it to non-generic, if you need that.
Ok I've got it solved for myself.
And I published it to gitHub:
https://github.com/Alexander-Taran/Lambda-Magic-Filters
Given the filter definition class (not reafactored to support properties other than string so far, but will do later):
class FilterDefinitionForCollectionPropertyValues<T>:FilterDefinition, IUserFilter
{
//This guy just points to a collection property
public Expression<Func<UserProfile, IEnumerable<T>>> CollectionSelector { get; set; }
// This one points to a property of a member of that collection.
public Expression<Func<T, string>> CollectionPropertySelector { get; set; }
//This one does the heavy work of building a predicate based on a collection,
//it's member property, operation type and a valaue
public System.Linq.Expressions.Expression<Func<Profile.UserProfile, bool>> GetFilterPredicateFor(FilterOperations operation, string value)
{
var getExpressionBody = CollectionPropertySelector.Body as MemberExpression;
if (getExpressionBody == null)
{
throw new Exception("getExpressionBody is not MemberExpression: " + CollectionPropertySelector.Body);
}
var propertyParameter = CollectionPropertySelector.Parameters[0];
var collectionParameter = CollectionSelector.Parameters[0];
var left = CollectionPropertySelector.Body;
var right = Expression.Constant(value);
// this is so far hardcoded, but might be changed later based on operation type
// as well as a "method" below
var innerLambda = Expression.Equal(left, right);
Expression<Func<T, bool>> innerFunction = Expression.Lambda<Func<T, bool>>(innerLambda, propertyParameter);
// this is hadrcoded again, but maybe changed later when other type of operation will be needed
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(T));
var outerLambda = Expression.Call(method, Expression.Property(collectionParameter, (CollectionSelector.Body as MemberExpression).Member as System.Reflection.PropertyInfo), innerFunction);
var result = Expression.Lambda<Func<UserProfile, bool>>(outerLambda, collectionParameter);
return result;
}
}

Querying Entity with LINQ using Dyanmic Field Name

I have created a dynamic search screen in ASP.NET MVC. I retrieved the field names from the entity through reflection so that I could allow the user to choose which fields they wanted to search on instead of displaying all fields in the view.
When the search result is Posted back to the controller, I receive a FormCollection containing the FieldName and the Value. I don't know how many fields are being searched on, and the FormCollection only contains fields that were chosen by the user.
I want to be able to now take that field name and apply that to my LINQ statement when I query the database for example:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
List<People> searchResults = new List<People>();
foreach (string key in fieldValueDictionary.Keys)
{
searchResults.Add(entities.People.Where(p => p.<use the key string as the fieldName> == fieldValueDictionary[key]));
}
return searchResults;
}
Where I have "use the key string as the fieldName" it would be like p => p.FirstName == fieldValueDictionary[key] where key = "FirstName". I've tried and failed to use Lambda Expression Trees, and have had a little success with Dynamic LINQ. The only other alternative is to do something like:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
IQueryable<People> results = entities.People;
foreach (string key in fieldValueDictionary.Keys)
{
switch (k)
{
case "FirstName": results = results.Where(entities.People.Where(p => p.FirstName == k);
case "LastName": results = results.Where(entities.People.Where(p => p.LastName == k);
// Repeat for all 26 fields in table
}
}
return results.ToList<People>();
}
UPDATE: I've done research into Lambda Expression Trees through the following posts:
dynamically create lambdas expressions + linq + OrderByDescending
Parameter problem with Expression.Lambda()
LINQ: Passing lambda expression as parameter to be executed and returned by method
I've gotten as far as getting a lambda to output the following: "p => p.FirstName", but I can't get this to work in a where. Any Suggestions? My code is below:
MemberInfo member = typeof(People).GetProperty("FirstName");
ParameterExpression cParam = Expression.Parameter(typeof(People), "p");
Expression body = Expression.MakeMemberAccess(cParam, member);
var lambda = Expression.Lambda(body, cParam);
After a lot more trial and error and searching I accidentally found another SO post that covers the same issue:
InvalidOperationException: No method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied arguments
Here is my modified code that works:
IQueryable query = entities.People;
Type[] exprArgTypes = { query.ElementType };
string propToWhere = "FirstName";
ParameterExpression p = Expression.Parameter(typeof(People), "p");
MemberExpression member = Expression.PropertyOrField(p, propToWhere);
LambdaExpression lambda = Expression.Lambda<Func<People, bool>>(Expression.Equal(member, Expression.Constant("Scott")), p);
MethodCallExpression methodCall = Expression.Call(typeof(Queryable), "Where", exprArgTypes, query.Expression, lambda);
IQueryable q = query.Provider.CreateQuery(methodCall);
With some hopefully pretty easy modifications, I should be able to get this to work with any type.
Thanks again for your answers Ani & John Bowen
Have you tried getting the value from PropertyInfo?
entities.People.Where(p => (p.GetType().GetProperty(key).GetValue(p, null) as string) == fieldValueDictionary[key])
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
return !fieldValueDictionary.Any()
? entities.People
: entities.People.Where(p => fieldValueDictionary.All(kvp => PropertyStringEquals(p, kvp.Key, kvp.Value)))
.ToList();
}
private bool PropertyStringEquals(object obj, string propertyName, string comparison)
{
var val = obj.GetType().GetProperty(propertyName).GetValue(obj, null);
return val == null ? comparison == null : val.ToString() == comparison; ;
}

Categories

Resources