Can I create a generic GroupBy() in C# LINQ? - c#

I am trying to modify the a generic IQueryable extension method to perform a GroupBy operation. The method I have performs a generic Where on a dynamically defined column:
public static IQueryable<TEntity> WhereById<TEntity, TKey>(
this IQueryable<TEntity> query, TKey value, string colName)
where TEntity : class
{
var param = Expression.Parameter(typeof(TEntity), "e");
var propAccess = Expression.PropertyOrField(param, colName);
var valExpr = Expression.Constant(value);
BinaryExpression predicate;
predicate = Expression.Equal(propAccess, valExpr);
var predicateLambda = Expression.Lambda<Func<TEntity, bool>>(predicate, param);
return query.Where(predicateLambda);
}
This works perfectly as in:
IQueryable<TEntity> entities = _crudApiDbContext.Set<TEntity>()
.WhereById<TEntity, int>(id, selectField);
Now I need a generic GroupBy(). I am trying the following:
public static IQueryable<TEntity> GroupBy<TEntity>(this IQueryable<TEntity> query, string colName)
where TEntity : class
{
var param = Expression.Parameter(typeof(TEntity), "e");
var propAccess = Expression.PropertyOrField(param, colName);
BinaryExpression predicate;
predicate = Expression.XXX(propAccess); <=what should XXX be?
var predicateLambda = Expression.Lambda<Func<TEntity, int>>(propAccess);
return (IQueryable<TEntity>)query.GroupBy(predicateLambda);
}
and I guess my question is what should XXX be? Or maybe because GroupBy() is an extension method the approach needs to be different?
Update
There is a lot of interest as to why I need this - I am building a generic Blazor form for faceted browsing. So I have a generic method for filtered and sorted search, but I also need to know for certain columns with options, how many options remain after applying the search conditions. To that end I will perform a GroupBy on each of those columns with the IQueryable which has the search conditions applied to it. Example of faceted search:

This is solution how to do grouping by dynamic field. Since we don't know type of grouping key, I've decided to make it as object.
public static class QueryableExtensions
{
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
public static IQueryable<IGrouping<object, TEntity>> GroupBy<TEntity>(this IQueryable<TEntity> query, string colName)
where TEntity : class
{
var param = Expression.Parameter(typeof(TEntity), "e");
var propAccess = MakePropPath(param, colName);
var keyLambda = Expression.Lambda(Expression.Convert(propAccess, typeof(object)), param);
var groupCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy),
new[] { typeof(TEntity), typeof(object) }, query.Expression,
keyLambda);
return query.Provider.CreateQuery<IGrouping<object, TEntity>>(groupCall);
}
}
Usage is simple in your case:
var result = query.GroupBy("Genre")
.Select(g => new
{
g.Key,
Count = g.Count()
})
.ToList();

Related

Generate sorted list using expression tree

I have a collection which is of type IQueryable, I need to sort this based on some dynamic sort fields. Sort fields are inside a list.
I write the following method to do this.
public List<T> Order<T>(IQueryable<T> source, List<string> propertyNames)
{
if(propertyNames != null && propertyNames.Count > 0)
{
var param = Expression.Parameter(typeof(T), string.Empty);
var property = Expression.PropertyOrField(param, propertyNames[0]);
var sort = Expression.Lambda(property, param);
MethodCallExpression orderByCall = Expression.Call(typeof(Queryable),"OrderBy",new[] { property.Type },Expression.Quote(sort));
if(propertyNames.Count > 1)
{
foreach(var item in propertyNames)
{
param = Expression.Parameter(typeof(T), string.Empty);
property = Expression.PropertyOrField(param, item);
sort = Expression.Lambda(property, param);
orderByCall = Expression.Call(
typeof(Queryable),
"ThenBy", new[] { typeof(T), property.Type },
orderByCall,
Expression.Quote(sort));
}
}
var results = (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(orderByCall);
if(results != null)
return results.ToList();
}
return null;
}
when I executed MethodCallExpression orderByCall = Expression.Call(typeof(Queryable),"OrderBy",new[] { property.Type },Expression.Quote(sort)); I got some exception
No generic method 'OrderBy' 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.
Sorry, I don't have a direct solution for your error.
Here is an alternative ("kept it simple") approach to dynamically order your data.
1) Add these extension methods somewhere in your project
public static IOrderedQueryable<TSource> OrderBy<TSource, TProperty>(this IQueryable<TSource> source
, Expression<Func<TSource, TProperty>> expression, bool descending)
{
return !descending ? source.OrderBy(expression) : source.OrderByDescending(expression);
}
public static IOrderedQueryable<TSource> ThenBy<TSource, TProperty>(this IOrderedQueryable<TSource> source
, Expression<Func<TSource, TProperty>> expression, bool descending)
{
return !descending ? source.ThenBy(expression) : source.ThenByDescending(expression);
}
2) Now you can just loop your list of property names and apply the OrderBy / ThenBy on your IQueryable.
Other idea: you could adapt your method so it accepts expressions instead of property name strings.

Store Static Filter By Key Expression

I've got an function which generates an expression to filter a table by it's primary key, when passed in an Object[], this is very similar to Find function except that it doesn't materialize so you can pass an IQueryable around afterwards
public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.{propertyName} == new {id = id[i]}.id
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(
Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
p.ClrType)))
.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
This works by first getting the primary keys for a table, it creates binary expression foreach property, the Id is wrapped in an anonymous type to leverage the query cache. This is working fine. However, I'd like to take this a step further.
I'd like to preserve the Expression so I don't have to generate it each time I pass on a new set of ids, How can I store this Expression while still leveraging the Query Cache?
Edit TL;DR
So I'm attempt to cache it using array access in a static class as suggest, however I'm encountering an error:
public class PrimaryKeyFilterContainer<T>
{
const string ANON_ID_PROP = "id";
static Expression<Func<T, bool>> _filter;
Type ANON_TYPE = new { id = (object)0 }.GetType();
public object[] id { get; set; }
public PrimaryKeyFilterContainer()
{
}
public Expression<Func<T, bool>> GetFilter(DbContext dbContext, object[] id)
{
this.id = id;
if(null == _filter)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.PK[i] == id[i]
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(BuildNewExpression(i),
p.ClrType)))
.Aggregate(Expression.AndAlso);
_filter = Expression.Lambda<Func<T, bool>>(body, parameter);
}
return _filter;
}
NewExpression BuildNewExpression(int index)
{
var currentObject = Expression.Constant(this);
var fieldAccess = Expression.PropertyOrField(currentObject, nameof(id));
var arrayAccess = Expression.ArrayAccess(fieldAccess, Expression.Constant(index));
return Expression.New(ANON_TYPE.GetConstructor(new[] { typeof(object) }), arrayAccess);
}
}
No coercion operator is defined between types '<>f__AnonymousType0`1[System.Object]' and 'System.Int32'
I'm getting closer but I'm not sure if it's going to work still.
As I mentioned in the comments, the main problem is that we cannot use array index access inside the expression tree - EF6 throws not supported exception and EF Core turns it into client evaluation.
So we need to store the keys in a class with dynamic count of properties and property types. Fortunately the System.Tuple generic classes provide such functionality, and can be used in both EF6 and EF Core.
Following is a class that implements the above idea:
public class PrimaryKeyFilter<TEntity>
where TEntity : class
{
object valueBuffer;
Func<object[], object> valueArrayConverter;
public PrimaryKeyFilter(DbContext dbContext)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<TEntity>();
// Create value buffer type (Tuple) from key properties
var valueBufferType = TupleTypes[keyProperties.Count - 1]
.MakeGenericType(keyProperties.Select(p => p.ClrType).ToArray());
// Build the delegate for converting value array to value buffer
{
// object[] values => new Tuple(values[0], values[1], ...)
var parameter = Expression.Parameter(typeof(object[]), "values");
var body = Expression.New(
valueBufferType.GetConstructors().Single(),
keyProperties.Select((p, i) => Expression.Convert(
Expression.ArrayIndex(parameter, Expression.Constant(i)),
p.ClrType)));
valueArrayConverter = Expression.Lambda<Func<object[], object>>(body, parameter).Compile();
}
// Build the predicate expression
{
var parameter = Expression.Parameter(typeof(TEntity), "e");
var valueBuffer = Expression.Convert(
Expression.Field(Expression.Constant(this), nameof(this.valueBuffer)),
valueBufferType);
var body = keyProperties
// e => e.{propertyName} == valueBuffer.Item{i + 1}
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Property(valueBuffer, $"Item{i + 1}")))
.Aggregate(Expression.AndAlso);
Predicate = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}
}
public Expression<Func<TEntity, bool>> Predicate { get; }
public void SetValues(params object[] values) =>
valueBuffer = valueArrayConverter(values);
static readonly Type[] TupleTypes =
{
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>),
};
}
You can create and store an instance of the class. Then use the expression returned by the Predicate property inside the query. And SetValues method to set the parameters.
The drawback is that the value storage is bound to the class instance, hence it cannot be used concurrently. The original approach works well in all scenarios, and the performance impact IMO should be negligible, so you might consider staying on it.

Generic Linq to Entities filter method that accepts filter criteria and properties to be filtered

I've looked into many generic linq filtering questions and their answers here in SO but none of them satisfy my needs so I thought I should create a question.
I've created many of what I call "filter provider" classes, one for each entity class in my model, to provide a simplistic search for my application. I didn't want to go into more advanced solutions like Lucene.Net because a basic filtering with matching score would suffice.
Inside each one of these provider classes there are multiple methods that will receive the filtering terms and query specific properties, returning a score for each match based on the relevance of the property. Most methods will filter multiple properties at once, but not all.
Here are two of these methods:
private IQueryable<Retailer> MatchHighRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.CompanyName != null && r.CompanyName.ToUpper().Contains(searchTerm))
|| (r.TradingName != null && r.TradingName.ToUpper().Contains(searchTerm))
);
return results;
}
private IQueryable<Retailer> MatchMediumRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.Address.Street != null && r.Address.Street.ToUpper().Contains(searchTerm))
|| (r.Address.Complement != null && r.Address.Complement.ToUpper().Contains(searchTerm))
);
return results;
}
These methods are replicated ad nauseum throughout each provider class and I hope I could replace them for a single method that would receive the properties to be included in the query.
Something like:
public static IQueryable<T> Match<T>(string searchTerm, IQueryable<T> data, Expression<Func<T, string>> filterProperties)
{
var results = **build the query for each property in filterProperties**
return results;
}
But I really can't figure it out. I tried using reflection but it only worked with Linq to Objects and I need a solution for Linq to Entities.
So to solve this problem we need a few puzzle pieces first. The first puzzle piece is a method that can take an expression that computes a value, and then another expression that computes a new value taking the same type the first returns, and creates a new expression that represents the result of passing the result of the first function as the parameter to the second. This allows us to Compose expressions:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
This relies on the following tool to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
We'll also need a tool to help us OR two predicate expressions together:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
Now that we have this we can use Compose on each property selector to map it from the property results to whether or not that property value is non-null and contains the search term. We can then OR all of those predicates together to get a filter for your query:
public static IQueryable<T> Match<T>(
IQueryable<T> data,
string searchTerm,
IEnumerable<Expression<Func<T, string>>> filterProperties)
{
var predicates = filterProperties.Select(selector =>
selector.Compose(value =>
value != null && value.Contains(searchTerm)));
var filter = predicates.Aggregate(
PredicateBuilder.False<T>(),
(aggregate, next) => aggregate.Or(next));
return data.Where(filter);
}
You can do it with expression trees but it's not as simple as you might think.
public static IQueryable<T> Match<T>(this IQueryable<T> data, string searchTerm,
params Expression<Func<T, string>>[] filterProperties)
{
var parameter = Expression.Parameter(typeof (T), "source");
Expression body = null;
foreach (var prop in filterProperties)
{
// need to replace all the expressions with the one parameter (gist taken from Colin Meek blog see link on top of class)
//prop.body should be the member expression
var propValue =
prop.Body.ReplaceParameters(new Dictionary<ParameterExpression, ParameterExpression>()
{
{prop.Parameters[0], parameter}
});
// is null check
var isNull = Expression.NotEqual(propValue, Expression.Constant(null, typeof(string)));
// create a tuple so EF will parameterize the sql call
var searchTuple = Tuple.Create(searchTerm);
var matchTerm = Expression.Property(Expression.Constant(searchTuple), "Item1");
// call ToUpper
var toUpper = Expression.Call(propValue, "ToUpper", null);
// Call contains on the ToUpper
var contains = Expression.Call(toUpper, "Contains", null, matchTerm);
// And not null and contains
var and = Expression.AndAlso(isNull, contains);
// or in any additional properties
body = body == null ? and : Expression.OrElse(body, and);
}
if (body != null)
{
var where = Expression.Call(typeof (Queryable), "Where", new[] {typeof (T)}, data.Expression,
Expression.Lambda<Func<T, bool>>(body, parameter));
return data.Provider.CreateQuery<T>(where);
}
return data;
}
public static Expression ReplaceParameters(this Expression exp, IDictionary<ParameterExpression, ParameterExpression> map)
{
return new ParameterRebinder(map).Visit(exp);
}
Now you need to have a expressionvisitor to make all the expressions use one parameter
//http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx
public class ParameterRebinder : ExpressionVisitor
{
private readonly IDictionary<ParameterExpression, ParameterExpression> _map;
public ParameterRebinder(IDictionary<ParameterExpression, ParameterExpression> map)
{
_map = map;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_map.ContainsKey(node))
{
return _map[node];
}
return base.VisitParameter(node);
}
}
Would use it like
var matches = retailers.Match("7", r => r.Address.Street, x => x.Address.Complement).ToList();
Warning - I checked this with linq to objects using the AsQueryable but didn't run it against EF.
You can use Linq.Dynamic to build the query.
public static IQueryable<T> Match<T>(
string searchTerm,
IQueryable<T> data,
params Expression<Func<T, string>>[] filterProperties) where T : class
{
var predicates = new List<string>();
foreach (var prop in filterProperties)
{
var lambda = prop.ToString();
var columnName = lambda.Substring(lambda.IndexOf('.') + 1);
var predicate = string.Format(
"({0} != null && {0}.ToUpper().Contains(#0))", columnName);
predicates.Add(predicate);
}
var filter = string.Join("||", predicates);
var results = data.Where(filter, searchTerm);
return results;
}
Usage.
var retailers = Match(
"asd", db.Retailers, r => r.CompanyName, r => r.TradingName);
var retailers = Match(
"asd", db.Retailers, r => r.Address.Street, r => r.Address.Complement);
Limitation.
The filter can only accept basic expression.
r => r.Name
r => r.PropA.Name
r => r.PropA.PropB.Name
Try to use Expressions like those all
http://www.codeproject.com/Articles/493917/Dynamic-Querying-with-LINQ-to-Entities-and-Express

Create linq query to search for contact the same way smartphone will do so

Let's say that in my database I have the table
**Table Contact**
Id, FirstName, LastName, Phone, Email, DateCreated
1 Tom Williams 3052548623 tom#gmail.com 2013-12-21 14:51:08
etc...
I would like to enable users to search for a contact entering a string. Let's say the user enters:
tom -> TRUE
tom wil -> TRUE
wil tom -> TRUE
tom XX -> FALSE
t w 3 # -> TRUE
wil 305 -> TRUE
(True means search found customer Tom, False means it did not find it)
I will be performing this type of search among different tables in my database. It will be nice if I dont have to build the query for a specific table.
The approach I am thinking on taking is to split the search string every time I find one or more spaces. Then I will be creating n number of searches and then performing an intersect?
You could do something like that, assuming you want only search in the string properties (so your samples will work if we considere Phone as a string property).
It's of course doable with numeric properties (but gets more complicated).
A method like that in an helper static class
public static Expression<Func<T, bool>> BuildPredicateForFilter<T>(string filterString)
{
//first, split search by space, removing white spaces, and putting this to upper case
var filters = filterString.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Select(m => m.ToUpper());
var parameter = Expression.Parameter(typeof (T), "m");
//get string.Contains() method
var containsMethod = typeof (string).GetMethod("Contains");
//get string.ToUpper() method
var toUpperMethod = typeof (string).GetMethod("ToUpper", new Type[]{});
//find all the string properties of your class
var properties = typeof(T).GetProperties().Where(m => m.PropertyType == typeof(string));
//for all the string properties, build a "m.<PropertyName>.ToUpper() expression
var members = properties.Select(p => Expression.Call(Expression.Property(parameter, p), toUpperMethod));
Expression orExpression = null;
//build the expression
foreach (var filter in filters)
{
Expression innerExpression = null;
foreach (var member in members)
{
innerExpression = innerExpression == null
? (Expression)Expression.Call(member, containsMethod, Expression.Constant(filter))
: Expression.OrElse(innerExpression, Expression.Call(member, containsMethod, Expression.Constant(filter)));
}
orExpression = orExpression == null
? innerExpression
: Expression.AndAlso(orExpression, innerExpression);
}
return Expression.Lambda<Func<T, bool>>(orExpression, new[]{parameter});
}
usage :
var result = <yourSource>.Where(Helper.BuildPredicateForFilter<TableName>("tom XX"));
for example, with "tom XX", the orExpression will look like
((((m.FirstName.ToUpper().Contains("TOM") OrElse
m.LastName.ToUpper().Contains("TOM")) OrElse
m.Phone.ToUpper().Contains("TOM"))
OrElse m.Email.ToUpper().Contains("TOM"))
AndAlso
(((m.FirstName.ToUpper().Contains("XX") OrElse
m.LastName.ToUpper().Contains("XX")) OrElse
m.Phone.ToUpper().Contains("XX")) OrElse
m.Email.ToUpper().Contains("XX")))
EDIT
or you could change the method to
public static IQueryable<T> FilterFor(this IQueryable<T> queryable, string filterString) {
//same
var predicate = Expression.Lambda<Func<T, bool>>(orExpression, new[]{parameter});
return queryable.Where(predicate);
}
then usage would simply be
<yourSource>.FilterFor("tom XX");
So what we're looking to do here is search through all of the fields in a type for a given value, doing a Contains search. We can write a method to do this.
First we'll need to use a PredicateBuilder, as we'll be dynamically generating a number of expressions that we want to OR together. Here is my definition of a PredicateBuilder capable of doing that:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
This uses the following helper method/class to replace all instances of one Expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Another tool that we'll use to solve this problem is a Compose method. It will take one expression, then another expression that takes as input the output of another, and produces a new expression that takes the input of the first and produces the output of the last.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Thanks to all of these tools, what's left is actually quite straightforward. We'll accept a query, a string to search for, and a series of selectors, each selecting out a field to search through. Then we initialize a filter, go through every single selector, use Compose to turn each selector into a predicate that performs a Contains check on the relevant search text, and then ORs that to the existing filter.
public static IQueryable<T> AnyFieldContains<T>(
this IQueryable<T> query,
string searchText,
params Expression<Func<T, string>>[] fieldSelectors)
{
var filter = PredicateBuilder.False<T>();
foreach (var selector in fieldSelectors)
{
filter = filter.Or(selector.Compose(
value => value.Contains(searchText)));
}
return query.Where(filter);
}
Now that we have all of this we can split the input that you have, and for each of those expressions we can call this method. Then you simply need to supply selectors for the fields that need to be searched through:
IQueryable<Foo> query = db.Foo;
string searchText = "wil tom";
var searchExpressions = searchText.Split(' ');
foreach (var expression in searchExpressions)
{
query = query.AnyFieldContains(expression,
foo => foo.FirstName,
foo => foo.LastName,
foo => foo.Phone);
}
var result = query.Any();
If you're really sure that you want to search every field (and I'm not sure if you are, it's likely many tables will have fields that shouldn't be searched, or have fields that will need some sort of work on your end to transform them into an appropriate string worth searching), then you can use reflection to generate all of the selectors, rather than typing out explicitly what you want to have searched. We can simply create an additional overload such that if no selectors are provided it will use "everything":
public static IQueryable<T> AnyFieldContains<T>(
this IQueryable<T> query,
string searchText)
{
return AnyFieldContains(query, searchText,
typeof(T).GetProperties()
.Select(prop => CreateSelector<T>(prop))
.ToArray());
}
private static Expression<Func<T, string>> CreateSelector<T>(PropertyInfo prop)
{
var param = Expression.Parameter(typeof(T));
Expression body = Expression.Property(param, prop);
if (prop.PropertyType == typeof(decimal?))
body = Expression.Call(body, typeof(SqlFunctions)
.GetMethod("StringConvert", new[] { typeof(decimal?) }));
else if (prop.PropertyType == typeof(double?))
body = Expression.Call(body, typeof(SqlFunctions)
.GetMethod("StringConvert", new[] { typeof(double?) }));
return Expression.Lambda<Func<T, string>>(body, param);
}

Pass Lambda expression to generic repository

I am attempting to use a web grid helper in conjunction with a generic repository to add column sorting. THe action result form the view with the grid helper has a parameter for the sort column (string). In my generic method signature I need to pass in a lambda expression based on the property name of the domain model (see below).
public IEnumerable<T>GetAllPagingAndSorting<TKey>(out int totalRecords,
int pageSize, int pageIndex, Expression<Func<T, TKey>> orderingKey,
SortDirection sortOrder,
params Expression<Func<T, object>>[] includes)
{}
So for example I want to map a property name of "Name" and type of "string" to m=>m.Name.
I have tries using a dictionary as in the following way but it throws an error when calling the repository method as the type is now object instead of int,string etc....
private IDictionary<string,Expression<Func<MyModel,object>>> _orderings =
new Dictionary<string, Expression<Func<MyModel,object>>>
{
{"Id",(m=>m.Id)},
{"Name",m=>m.UserName},
{"DateRequired",m=>m.DateRequired},
{"AssignedTo",m=>m.TeamMember.MemberName},
{"RequestedBy",m=>m.RequestedBy},
};
Should I use a method instead? In either case how can I use the above to match the input property and return the Lambda expression with the correct type?
Update:
Here's my Action in the controller....thought I'd try and get the ordering key as Lambda here as I use generic repository....
Generic respoitory method defined:
IEnumerable GetAllPagingAndSorting(out int totalRecords, int pageSize, int pageIndex,Expression> orderingKey, SortDirection sortOrder, params Expression>[] includes);
public ActionResult ServerPagingAndSorting(int page = 1, string sort = "Id", string sortDir = "Ascending")
{
int totalRecords;
var viewModel =new SupportRequestsIndexVM(supportrequestRepository.GetAllPagingAndSorting(out totalRecords, PageSize,page - 1,_orderings[sort] ,GetSortDirection(sortDir),(m=>m.TeamMember)))
{PageSize = PageSize, PageNumber = page, TotalRows = totalRecords};
return View(viewModel);
}
The problem is that the expression (m=>m.Id), which is of type Expresion<Func<MyModel, int>> will automatically receive an additional cast to object to match Expresion<Func<MyModel, object>>. You don't see the cast in the code, but you can observe it analyzing the Expression Tree.
My approach is to
encapsulate all query parameters, such as paging and sort order into a class
encapsulate the query result in a class (total records, selected records)
Thus my solution looks like this
public class QueryResult<T> {
public int TotalRecords;
public List<T> Records;
}
public QueryResult<T> GetRecords<T>(QueryParams p)
{
IEnumerable<T> q = BuildQueryWithConditions<T>(p);
var result = new QueryResult<T> { TotalRecords = q.Count() };
q = ApplySortOrder(p);
q = ApplyPaging(p);
result.Records = q.ToList();
return result;
}
The ApplySortOrder is a per-entity function interpreting SortColumn and SortOrder:
switch (p.SortColumn)
{
case "Column1":
if (desc)
queryDef = queryDef.OrderByDescending(record => record.Column1);
else
queryDef = queryDef.OrderBy(record => record.Column1);
break;
....
}
To handle sorting per entity, you need to pass an IEnumerable<T> to a function and return an IOrderedEnumerable<T>. Since we cannot have generic types in a dictionary that covers different entities, the signature looks like this:
Dictionary<Type, Expression<Func<IEnumerable, IEnumerable>>>
Additionally define a method Add<T>(Expression<Func<IEnumerable<T>, IOrderedEnumerable<T>>>) to add to the dictionary, and Get() to retrieve the sort expression.
I am now using this code to apply the sorting taken form another stack overflow q. I pass in the string to the generic repository and then call this method as follows:
Here's my repository method:
public IEnumerable<T> GetAllPagingAndSorting(out int totalRecords, int pageSize, int pageIndex, string orderingKey, string sortDir, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> results = Entities;
totalRecords = results.Count();
// apply any includes
if (includes != null)
{
results = includes.Aggregate(results, (current, include) => current.Include(include));
}
// apply sorting
results = GetSortDirection(sortDir) == SortDirection.Ascending ? ApplyOrder(results, orderingKey, ORDERBY) : ApplyOrder(results, orderingKey, ORDERBYDESC);
if (pageSize > 0 && pageIndex >= 0)
{
// apply paging
results = results.Skip(pageIndex * pageSize).Take(pageSize);
}
return results.ToList();
}
protected static IOrderedQueryable<T> ApplyOrder(IQueryable<T> source, string property, string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "m");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IOrderedQueryable<T>)result;
}

Categories

Resources