Ho una serie di criteri di ricerca in questa forma:
member | value | operator
--------+---------+---------
height | 10 | >
height | 2 | <
name | Carl | ==
E voglio interrogare tutti gli oggetti che corrispondono a uno qualsiasi di questi criteri.
In questo momento, lo faccio da:
Conoscete un metodo più semplice per filtrare dinamicamente una raccolta IQueryable usando OR consecutivi?
Basato sulla soluzione IlyaBuiluk @ CodeProject
// The structure used by the new extension method
public struct SearchCriteria
{
public string Column;
public object Value;
public WhereOperation Operation;
}
// How to convert the rules structure to the search criteria structure
var searchCriterias = grid.Where.rules.Select(Rule => new SearchCriteria
{
Column = Rule.field,
Operation =
(WhereOperation)
StringEnum.Parse(
typeof (WhereOperation),
Rule.op),
Value = Rule.data
}).ToArray();
// Usage:
query = query.WhereOr(searchCriterias);
// Implementation
public static IQueryable<T> WhereOr<T>( this IQueryable<T> Query, SearchCriteria [ ] Criterias )
{
if( Criterias.Count( ) == 0 )
return Query;
LambdaExpression lambda;
Expression resultCondition = null;
// Create a member expression pointing to given column
ParameterExpression parameter = Expression.Parameter( Query.ElementType, "p" );
foreach( var searchCriteria in Criterias )
{
if( string.IsNullOrEmpty( searchCriteria.Column ) )
continue;
MemberExpression memberAccess = null;
foreach( var property in searchCriteria.Column.Split( '.' ) )
memberAccess = MemberExpression.Property
( memberAccess ?? ( parameter as Expression ), property );
// Change the type of the parameter 'value'. it is necessary for comparisons (specially for booleans)
ConstantExpression filter = Expression.Constant
(
Convert.ChangeType( searchCriteria.Value, memberAccess.Type )
);
//switch operation
Expression condition = null;
switch( searchCriteria.Operation )
{
//equal ==
case WhereOperation.Equal:
condition = Expression.Equal( memberAccess, filter );
break;
//not equal !=
case WhereOperation.NotEqual:
condition = Expression.NotEqual( memberAccess, filter );
break;
// Greater
case WhereOperation.Greater:
condition = Expression.GreaterThan( memberAccess, filter );
break;
// Greater or equal
case WhereOperation.GreaterOrEqual:
condition = Expression.GreaterThanOrEqual( memberAccess, filter );
break;
// Less
case WhereOperation.Less:
condition = Expression.LessThan( memberAccess, filter );
break;
// Less or equal
case WhereOperation.LessEqual:
condition = Expression.LessThanOrEqual( memberAccess, filter );
break;
//string.Contains()
case WhereOperation.Contains:
condition = Expression.Call( memberAccess,
typeof( string ).GetMethod( "Contains" ),
Expression.Constant( searchCriteria.Value ) );
break;
default:
continue;
}
resultCondition = resultCondition != null ? Expression.Or( resultCondition, condition ): condition;
}
lambda = Expression.Lambda( resultCondition, parameter );
MethodCallExpression result = Expression.Call(
typeof( Queryable ), "Where",
new [ ] { Query.ElementType },
Query.Expression,
lambda );
return Query.Provider.CreateQuery<T>( result );
}
Se hai un set fisso di operatori e un set fisso di membri, puoi scriverlo quasi senza occuparti direttamente degli alberi delle espressioni. L'idea è quella di creare semplici espressioni lambda per varie parti di codice (ad esempio Expression<Func<Entity, string>>
per leggere le proprietà di un membro e simili per gli operatori) e quindi compilarle per creare un albero di espressioni. Ho descritto la soluzione qui . L'unico problema è che la composizione delle espressioni non è direttamente supportata in C #, quindi è necessario un po 'di pre-elaborazione (vedere la sezione "Utilità espandibili").
Quindi è possibile memorizzare le funzioni di base in un dizionario e selezionare quella giusta (o una loro combinazione) in base a ciò che l'utente seleziona. Ad esempio qualcosa del tipo:
NorthwindDataContext db = new NorthwindDataContext();
// A query that tests whether a property
// (specified by 'selector' matches a string value
var queryBuilder = Linq.Func
((Expression<Func<Customer, string>> selector, string val) =>
from c in db.Customers.ToExpandable()
where selector.Expand(c).IndexOf(val) != -1
select c);
// Dictionary with supported members...
var dict = new Dictionary<string, Expression<Func<Customer, string>>>
{ { "CompanyName", c => c.CompanyName },
{ "Country", c => c.Country },
{ "ContactName", c => c.ContactName } };
// Ask user for a property name & value and Build the query
string field = Console.ReadLine();
string value = Console.ReadLine();
var q = queryBuilder(dict[field], value);
L'articolo contiene anche un esempio con la composizione dinamica delle condizioni OR o AND. Non ho aggiornato il codice per un po ', quindi ha bisogno di un po' di lavoro, ma credo che il progetto LINQ KIT contenga anche una versione di questa idea.