Generic method for querying Entity Framework - c#

I would like to have generic method for filtering Entity Framework IQueryable<TItem> by IEnumerable<TEnum>. Its signature should probably look like this:
public static IQueryable<TItem> ApplyFilter<TItem, TEnum>(IQueryable<TItem> items, IEnumerable<TEnum> enumValues, Expression<Func<TItem, IEnumerable<TEnum>, bool>> predicate)
{
return items.Where(??????);
}
and I would want to be able to call it for example like this:
IQueryable<Request> requests = service.GetAllRequests();
IEnumerable<RequestState> states = new RequestState[] {RequestState.Active, RequestState.Closed};
Expression<Func<Request, IEnumerable<RequestState>, bool>> predicate = (r, s) => s.Contains(r.State);
requests = ApplyFilter(requests, states, predicate);
But what should be inside method's body? How can I convert Expression<Func<TItem, IEnumerable<TEnum>, bool>> to Expression<Func<TItem, bool>> for use as parameter to "Where" method? Will it work with Entity Framework?

IMO, the predicate should be inside your ApplyFilter method (respect of concerns).
One possible way to code this would be :
public static IQueryable<TItem> ApplyFilter<TItem, TEnum>(IQueryable<TItem> items,
IEnumerable<TEnum> enumValues, Expression<Func<TItem,TEnum>> enumField)
{
return items.Where(i => enumValues.Contains(enumField(i)));
}
with a call like this :
requests = ApplyFilter(requests, states, r => r.State);

When I was implementing Sharped's answer I found that it is actually possible to do exactly what I want. The point is to use custom class that inherits from ExpressionVisitor. This class will replace every occurrence of IEnumerable<TEnum> parameter within expression tree with its actual value. My final code:
public static IQueryable<TFilteredItem> ApplyFilter<TFilteredItem, TEnum>(IQueryable<TFilteredItem> items, IEnumerable<TEnum> enumValues, Expression<Func<TFilteredItem, IEnumerable<TEnum>, bool>> predicate)
{
ParameterExpression itemParam = predicate.Parameters[0];
ParameterExpression enumsParam = predicate.Parameters[1];
var em = new ExpressionModifier<IEnumerable<TEnum>>(enumsParam.Name, enumValues);
Expression predicateBody = em.Modify(predicate.Body);
return items.Where(Expression.Lambda<Func<TFilteredItem, bool>>(predicateBody, new[] { itemParam }));
}
public class ExpressionModifier<T> : ExpressionVisitor
{
private readonly string parameterName;
private readonly T newValue;
public Expression Modify(Expression expression)
{
return Visit(expression);
}
public ExpressionModifier(string parameterName, T newValue)
{
this.parameterName = parameterName;
this.newValue = newValue;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Name == this.parameterName ? Expression.Constant(this.newValue, node.Type) : base.VisitParameter(node);
}
}
And I am using it like:
var requests = this.ServiceManager.Messaging.GetRequests();
var states = new[] {RequestState.Active};
requests = ApplyFilter(requests, states, (i, e) => e.Contains(i.State));

Related

FilterByItems Method not working correctly

Im building a filter and im using the FilterByItems method to compare two Arrays(one from my frontend and one from my database). It all goes through a Overlay Panel in my Frontend, where the user can select different persontypes like member, worker and much more. So my problem is now that i got multiple persontype hovers which dont want do work together. If i just have one it works fine, if i add a second one it only works if both have the same values in the frontend list.
The right case would be:
Frontend Panel one got the Member in the Array, Frontend Panel two got the Friend in the Array.
Table shows all persons who are saved with these types in their profile.
The current and wrong case is:
Panel 1 and Panel two are not working with different arrays and only showing informations if both have the same list for example both got member on position [0]
Generelly it seems like it works as a single one as an and query and as the second panel joins in it blocks completely. So here is my code(FilterByItems Method is linked above):
//The Filter
//Login
[HttpPost("filter/")]
public async Task<IActionResult> Filter([FromBody] Filter user)
{
var baseQuery = _context.Personens.AsQueryable();
//Personentyp 1 Dont works in combination with below
if (user.personenTypFilter.Length > 0)
baseQuery = baseQuery.FilterByItems(user.personenTypFilter, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)), true);
////Personentyp 2
//if (user.personenTypFilter2.Length > 0)
// baseQuery = baseQuery.FilterByItems(user.personenTypFilter2, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)), true);
//---------------------------------
var result = await (baseQuery.Select(p => new
{
personId = p.PersonId,
nachname = p.Nachname,
vorname = p.Vorname,
plz = p.Plz,
firmBez = p.Firmenbezeichnung,
ort = p.Ort,
personentyp = p.Personentypzuordnungens.Select(i => new
{
personentypId = i.PersonentypId,
}),
aktuellePosition = p.AktuellePosition,
taetigkeit = p.Tätigkeit,
kernkompetenzen = p.Kernkompetenzen,
datenReviewedZeitpunkt = p.DatenReviewedZeitpunkt,
}).ToListAsync());
return Ok(result);
}
Thats how i declared my variables in my Filter Model:
public string[] personenTypFilter { get; set; }
public string[] personenTypFilter2 { get; set; }
New problem with .CombineAnd(from comments)
baseQuery = baseQuery.Where(
//Characteristics
character1Predicate.CombineOr(character1Predicate2).CombineOr(character1Predicate3)
//Persontypes
.CombineAnd(personType1Predicate.CombineOr(personType2Predicate).CombineOr(personType3Predicate)));
This function is evolution of FilterByItems and adds additional public methods for generating predicate and combining them.
You can use new extensions in your query in the following way:
var baseQuery = _context.Personens.AsQueryable();
var predicate1 = baseQuery.GetItemsPredicate(ser.personenTypFilter, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)));
var predicate2 = baseQuery.GetItemsPredicate(user.personenTypFilter2, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)));
// filter by combined predicates
baseQuery = baseQuery.Where(predicate1.CombineOr(predicate2));
And implementation:
public static class QueryableExtensions
{
public static IQueryable<T> FilterByItems<T, TItem>(this IQueryable<T> query, IEnumerable<TItem> items,
Expression<Func<T, TItem, bool>> filterPattern, bool isOr = true, bool emptyValue = true)
{
var filterLambda = query.GetItemsPredicate(items, filterPattern, isOr, emptyValue);
return query.Where(filterLambda);
}
public static Expression<Func<T, bool>> GetItemsPredicate<T, TItem>(this IEnumerable<T> query, IEnumerable<TItem> items, Expression<Func<T, TItem, bool>> filterPattern, bool isOr = true, bool emptyValue = false)
{
Expression predicate = null;
foreach (var item in items)
{
var itemExpr = Expression.Constant(item);
var itemCondition = ExpressionReplacer.Replace(filterPattern.Body, filterPattern.Parameters[1], itemExpr);
if (predicate == null)
predicate = itemCondition;
else
{
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
itemCondition);
}
}
predicate ??= Expression.Constant(emptyValue);
var filterLambda = Expression.Lambda<Func<T, bool>>(predicate, filterPattern.Parameters[0]);
return filterLambda;
}
public static Expression<Func<T, bool>> CombineOr<T>(this Expression<Func<T, bool>> predicate1,
Expression<Func<T, bool>> predicate2)
{
var parameter = predicate1.Parameters[0];
var body = Expression.OrElse(predicate1.Body, ExpressionReplacer.GetBody(predicate2, parameter));
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
public static Expression<Func<T, bool>> CombineAnd<T>(this Expression<Func<T, bool>> predicate1,
Expression<Func<T, bool>> predicate2)
{
var parameter = predicate1.Parameters[0];
var body = Expression.AndAlso(predicate1.Body, ExpressionReplacer.GetBody(predicate2, parameter));
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression node)
{
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
return replacement;
return base.Visit(node);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}

Flatten Expression Parameter with Value [duplicate]

This question already has an answer here:
Replace parameter in lambda expression
(1 answer)
Closed 4 years ago.
I have this property in a class
public Expression<Func<T, string, bool>> FilterExp { get; set; }
I need to replace the string paramater with a value that is known at runtime and covert in into this:
Expression<Func<T, bool>>
so I can use it with IQueryable.
This is what it looks like when I set the filter:
PagingProvider<Employee> provider = new PagingProvider<Employee>(context.Employees);
provider.FilterExp = (employee, filterText) => employee.FullName.Contains(filterText);
return provider.GetResults();
What would be the best way of changing the expression?
I came up with this and it seems to be working:
protected class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression parameterExpression;
private string newValue;
public ParameterReplacer(ParameterExpression parameterExpression, string newValue)
{
this.parameterExpression = parameterExpression;
this.newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == parameterExpression)
return Expression.Constant(this.newValue);
return base.Visit(node);
}
}
And in my code I use it like this:
var replacer = new ParameterReplacer(FilterExp.Parameters[1], Filter);
var newBody = replacer.Visit(FilterExp.Body);
var exp = Expression.Lambda<Func<T, bool>>(newBody, FilterExp.Parameters[0]);
return SourceData.Where(exp);

Expression parameter is not defined

I am trying to perform a query against a list to give immediate results using an expression that is set elsewhere in the code, while a second thread goes off and uses it to get a full set of results from a database in a Linq query.
I know that the expression itself is OK as when I send it over the wire to the server side and apply it against an IQueryable then it will work. However, when applied on the client side the following error is produced:
System.InvalidOperationException: 'variable 'item' of type 'MissionControlSuite.Shared.Interface.Model.IBox' referenced from scope '', but it is not defined'
This code that performs the filtering:
public abstract class GridWithPaging<T> where T : class
{
List<T> _items
public Expression<Func<T, bool>> Filter { get; set; }
public ObservableCollection<IGridFilter<T>> Filters { get; set; }
// more code skipped for breveity
public void FiltersChanged(object sender, EventArgs e)
{
Expression<Func<T, bool>> combined = item => true;
foreach (var filter in Filters)
combined = Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(filter.Expression.Body, combined.Body),
combined.Parameters.Single());
Filter = combined;
NotifyPropertyChanged("Filter");
}
public void AddFilter(IGridFilter<T> filter)
{
Filters.Add(filter);
NotifyPropertyChanged("Filters");
}
private void DoFiltering(int start)
{
var newView = _items.Skip(start);
if(Filter != null)
newView = newView.AsQueryable().Where(Filter);
//some more code that acts on the filtered list
}
}
The expression is set elsewhere like this:
Expression<Func<IBox, bool>> expression = item => item.BoxId.Contains(v);
var filter = new GridFilter<IBox>()
{
Name = string.Format("{0} contains {1}", Property, Value),
Expression = expression
};
Grid.AddFilter(filter);
This answer will help you: Combining two expressions (Expression<Func<T, bool>>)
To summarize: The problem is that the parameters, although with the same name, are not the same instance of the ParameterExpression - which is included in the comment to that answer.
The solution is to use a Visitor (code copied over from linked post):
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
Also note: the ORM provider (or another IQueryable you use) probably works because it translates that to text directly, whereas executing (Invokeing) this LambdaExpression causes the error as it's more strict, in a way. Some IQueryables can also accept simple string parameters and parse that on their own, which should indicate that they have a wider range of acceptable inputs.

Modifying predicate expression for wrapping class

I have a repository class that allows for queries using a lambda expression (simplified partial code):
public class SomeRepository<T>: IRepository<T>
{
public IList<T> Find(Expression<Func<T, bool>> filter)
{
return SomeQueryProvider.Where(filter).ToList();
}
}
However, for a specific provider I have the need to wrap the original object in another class:
public class Wrapped<T>
{
public T OriginalObject { get; set; }
}
So in this case, I also need to wrap the incoming predicate expression in another expression:
public class AnotherRepository<T>: IRepository<T>
{
public IList<T> Find(Expression<Func<T, bool>> filter)
{
Expression<Func<Wrapped<T>, bool>> wrappedFilter = ...
return AnotherQueryProvider.Where(wrappedFilter).ToList();
}
}
For example x => x.ParentId == 123 should become x => x.OriginalObject.ParentId == 123.
I can't find examples for this scenario, and I'm having difficulty solving this myself. How can I prepend the predicate expression with the OriginalObject property?
Answering the concrete question.
Given expression
Expression<Func<Wrapped<T>, T>> e1 = w => w.OriginalObject;
converting the expression
Expression<Func<T, bool>> e2 = o => o.ParentId == 123;
to
Expression<Func<Wrapped<T>, T>> e3 = w => w.OriginalObject.ParentId == 123;
is a matter of replacing the o parameter occurrences inside the e2 body with w.OriginalObject (the body of the e1). Something like string replace, but for expressions :)
First you need a method that replaces expression parameter with something else. Here is the one that I use:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
Now the method in question could be like this:
partial class ExpressionUtils
{
public static Expression<Func<Wrapped<T>, TResult>> ToWrapped<T, TResult>(this Expression<Func<T, TResult>> source)
{
Expression<Func<Wrapped<T>, T>> unwrap = w => w.OriginalObject;
var parameter = unwrap.Parameters[0];
var body = source.Body.ReplaceParameter(source.Parameters[0], unwrap.Body);
return Expression.Lambda<Func<Wrapped<T>, TResult>>(body, parameter);
}
}
and the usage
var wrappedFilter = filter.ToWrapped();

Combining multiple expressions trees

I'm getting the following error
The parameter 'p' was not bound in the specified LINQ to Entities
query expression.
I understand the problem (same instance of ParameterExpression should be used with all the expressions in the tree) and have attempted to use solutions I've found online but with no luck.
This is my method
private void SeedEntity<TEntity>(DatabaseContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) where TEntity : class
{
Expression<Func<TEntity, bool>> allExpresions = null;
var parameters = identifierExpressions.SelectMany(x => x.Parameters).GroupBy(x => x.Name).Select(p => p.First()).ToList();
foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
{
Func<TEntity, object> vv = identifierExpression.Compile();
object constant = vv(entity);
ConstantExpression constExp = Expression.Constant(constant, typeof(object));
BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, parameters);
if (allExpresions == null)
{
allExpresions = equalExpression2;
}
else
{
BinaryExpression bin = Expression.And(allExpresions.Body, equalExpression2.Body);
allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, parameters);
}
}
TEntity existingEntity = null;
if (allExpresions != null)
{
existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
}
if (existingEntity == null)
{
context.Set<TEntity>().Add(entity);
}
else
{
entity = existingEntity;
}
}
It generates an expression for the lookup of an entity based on a number of properties.
It works fine for a single expression, the error only occurs when passing in multiple.
Called like this:
SeedEntity(context, ref e, p=> p.Name);//Works
SeedEntity(context, ref e, p=> p.Name, p=> p.Age);//Fails
It generates something similar to me performing the following:
context.Set<TEntity>().FirstOrDefault(p=>p.Name == e.Name && p.Age == e.Age);
Replacing e.Name && e.Age with a ConstantExpression
You can see in the method above I grab all of the unique params and store them in parameters at the top, then use the same variable throughout.This is the start, but then I need to replace the instances of the parameter in each of the Expression<Func<TEntity, bool>> passed in as the params array, this is where I'm failing.
I've tried enumerate the expressions and use the .Update() method passing in the params
I also tried a solution using the ExpressionVisitor
public class ExpressionSubstitute : ExpressionVisitor
{
public readonly Expression from, to;
public ExpressionSubstitute(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
if (node == from) return to;
return base.Visit(node);
}
}
public static class ExpressionSubstituteExtentions
{
public static Expression<Func<TEntity, TReturnType>> RewireLambdaExpression<TEntity, TReturnType>(Expression<Func<TEntity, TReturnType>> expression, ParameterExpression newLambdaParameter)
{
var newExp = new ExpressionSubstitute(expression.Parameters.Single(), newLambdaParameter).Visit(expression);
return (Expression<Func<TEntity, TReturnType>>)newExp;
}
}
You're really close. I don't see the point of your parameters variable. Grouping them by name is a mistake. Why not just pass the parameters from the expression? Then visit if necessary. Your visitor code is fine.
private static void SeedEntity<TEntity>(DbContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions)
where TEntity : class
{
Expression<Func<TEntity, bool>> allExpresions = null;
foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
{
Func<TEntity, object> vv = identifierExpression.Compile();
object constant = vv(entity);
ConstantExpression constExp = Expression.Constant(constant, typeof(object));
BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, identifierExpression.Parameters);
if (allExpresions == null)
{
allExpresions = equalExpression2;
}
else
{
var visitor = new ExpressionSubstitute(allExpresions.Parameters[0], identifierExpression.Parameters[0]);
var modifiedAll = (Expression<Func<TEntity,bool>>)visitor.Visit(allExpresions);
BinaryExpression bin = Expression.And(modifiedAll.Body, equalExpression2.Body);
allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, identifierExpression.Parameters);
}
}
TEntity existingEntity = null;
if (allExpresions != null)
{
existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
}
if (existingEntity == null)
{
context.Set<TEntity>().Add(entity);
}
else
{
entity = existingEntity;
}
}

Categories