Einer der größten Vorteile von LINQ to Objects ist es, dass man typsicher auch sehr komplexe Abfragen über Objektstrukturen durchführen kann. Zuweilen möchte man aber dem Anwender die Möglichkeit bieten, selbst zu bestimmen, welche Operationen mit den Objektstrukturen durchgeführt werden. Als Beispiel soll hier die dynamische Filterung und Sortierung anhand der Member des Objekts dienen. Spätestens zu diesem Zeitpunkt wird eines der größten Pro's von LINQ zum Verhängnis - die Typsicherheit.
Um hier eine gewisse Dynamik unterzubringen muss man zunächst wissen, wie LINQ-Queries aufgebaut sind. Sie bestehen aus sogenannten Expression Trees, die dann ausgeführt werden. Liegt ein LINQ-Query vor, kann man sich z.B. mit dem Expression Tree Viewer aus den C# Samples den Expression-Tree ansehen.

Um hier dynamische Filterung und/oder Sortierung zu implementieren, gilt es, einen solchen Baum dynamisch aufzubauen. Das ist gar nicht so kompliziert, wie es sich auf den ersten Blick anhört.

Angenommen, es liegt eine generische List mit Adressen vor (im Beispiel Restaurant-Adressen) - mit den für Adressdaten typischen Membern (Name, Straße, Postleitzahl, Ort). Per Parameter soll definiert werden können, welche Eigenschaft gefiltert werden soll und wie das Filterkriterium ist -gleiches gilt für die Sortierung.
Im Beispiel definiert die Klasse RestaurantRepository Zugriffsmethoden für den Zugriff auf Listen vom Typ Restaurant. Dabei kann mittels GetRestaurants eine komplette Liste abgerufen werden die Methode GetFilteredRestaurants implementiert den dynamischen Aufbau. Als Parameter dient hier zunächst ein Dictionary, dessen Key den zu filternden Membernamen und dessen Value den zu filternden Wert beinhaltet - sowie eine Liste, deren Inhalt definiert nach welchen Membern sortiert werden soll.
public static IEnumerable<Restaurant> GetFilteredRestaurants(Dictionary<string, string> filter, List<string> sort)
{
IEnumerable<Restaurant> restaurants = GetRestaurants();
#region get lambdas
// where
ParameterExpression pe = Expression.Parameter(typeof(Restaurant), "address");
Expression whereExpression = null;
foreach (string filterAttribute in filter.Keys)
{
Expression left = MemberExpression.Property(pe, filterAttribute);
string filterValue = filter[filterAttribute];
Expression right = Expression.Constant(filterValue);
if (whereExpression == null)
{
whereExpression = Expression.Equal(left, right);
}
else
{
whereExpression = Expression.And(whereExpression, Expression.Equal(left, right));
}
}
Expression<Func<Restaurant, bool>> whereCondition = Expression.Lambda<Func<Restaurant, bool>>(whereExpression, new ParameterExpression[] { pe });
var filteredItems = restaurants.AsQueryable().Where(whereCondition);
// order by
Expression queryExpr = filteredItems.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (string sortCriteria in sort)
{
var type = typeof(Restaurant);
var property = type.GetProperty(sortCriteria);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
queryExpr = Expression.Call(typeof(Queryable), methodAsc,
new[] { type, property.PropertyType },
queryExpr, Expression.Quote(orderByExpression));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
#endregion
return (filteredItems.Provider.CreateQuery(queryExpr)).ToList();
}
Zunächst wird eine ParameterExpression instanziiert. Da Member mit Konstanten verglichen werden sollen, wird für jedes Filterkriterium eine MemberExpression und eine Konstante initiiert. Beides wird mit Expression.Equal verkettet. Mit Hilfe von Expression.And können mehrere solcher Vergleiche aneinandergekettet werden.
Ist die Filterung durchgeführt, kann dann noch die Sortierung bearbeitet werden. Auch hier wird wieder eine Expression mit MemberAccess initiiert. Anhand des Wertes kann dann mit Hilfe von Expression.Call die Sortierung durchgeführt werden.
Ein kleines Testprogramm zeigt dann, dass der Code auch funktioniert...
