Reflection Linq-Entity "IN()" function for "contains" searching group of integers - c#

I'm building my own reflection functions for certain types of searches.
The problem is that I want to search a group of IDs within a list of IDs and filter my search/select query to have only these specific objects.
This is the same as using "IN()" in Linq-Entity framework. But I can't use a.objid.
return query.Where(a => ObjectsToFind.Contains(a.objid));
However, "a.objid" is causing errors because I use T Template.
So a is "T a" instead of "MyTable a" so that I can call it's "objid" property.
I know there is a way to do this with parameter expressions. However, I can't figure it out.
Here's what I tried to replace that above line with:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, string contains)
{
var ObjectsToFind = new List<int>(); // I want to search IN() this function that gets filled in here.
ObjectsToFind = FillObjectsToFind(); // just the object id integers I want to filter
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, "objid"); // I look for this
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(int) }); // int type
var vars = Expression.Variable(List<int>,); // I can't use "constant", but what do I use here?
var containsExpression = Expression.Call(propertyExpression, method, vars);
return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
}
Replacing "T" with the actual table entity, causes a lot of problems, so I decided to keep the T.
else if (s.ST == "function")
{ // search func
myTable a;
DC2 = MyUtility.WhereFunctionContains(DC2, a => a.objid, s.S);
// s.S is my search query string.
// s.ST means I'm searching for functions (from another associated table)
// I don't understand how a.objid gets called, I wanted to use Template/Reflections.
}
Here's how I call Zev's function.
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T, int>> converter, string contains)
{
FunctionsClass fc = new FunctionsClass();
var ObjectsToFind = new List<int>();
ObjectsToFind = fc.SearchContainFunction(contains); // I grabbed my list of IDs to search
return query.Where(t => ObjectsToFind.Contains(converter(t)));
}

If I understand you correctly, you have a number of queries on different types:
IQueryable<Person> personsQry = ...
IQueryable<Sale> salesQry = ...
IQueryable<Order> ordersQry = ...
and you have a method that generates a List<int>, called FillObjectsToFind:
public List<int> FillObjectsToFind()
{
//code here
}
You want to be able to limit each of the above queries to only have the id in the returned list. As an added bonus, this should be an extension method, so you can call it like this:
var personsFiltered = personsQry.WhereFunctionContains(...);
var salesFiltered = salesQry.WhereFunctionContains(...);
var ordersFiltered = ordersQry.WhereFunctionContains(...);
The problem is each query is of a separate type, and you would prefer to write one method that covers all of them.
The first part of the solution is to define a generic method:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query)
{
//code here
}
but there is still a problem: the only type we know of is type T which is not a real type, but a placeholder for actual types. Since these types could be anything -- string, System.Random, SqlConnection, an ASP.NET Label, a WPF TextBlock -- there is no way of knowing how to compare each object to a List of ints.
The most straightforward solution is to define an interface:
interface IHasObjID
{
int ObjID {get;set;}
}
Then each type should implement this interface:
class Person : IHasObjID
{
int objID;
int ObjID
{
get {return objID;}
set {objID = value;}
}
}
//implement sales and orders similarly
Once that is done, you can define a constraint on the types allowed by the method. Now that the type definitely has an ObjID property, we can query on that:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query) where T : IHasObjID
{
var intsToFind = FillObjectsToFind();
return query.Where(t => intsToFind.Contains(t.ObjID));
}
This is what King King was telling you in this comment.
I propose that when calling this function, you also pass in how to get at the integer from the type:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T,int>> converter)
{
var intsToFind = FillObjectsToFind();
return query.Where(t => intsToFind.Contains(converter(t)));
}
However, I haven't tested this code, and since we are working with Entity Framework and expressions I suspect there is still an issue: An expression cannot be "called" within an expression.
I wanted to suggest the above, but it doesn't compile with the following error -- 'converter' is a 'variable' but used like a 'method'.
After all that, the solution is straightforward, using Join:
public static IQueryable<T> WhereFunctionContains<T>(this IQueryable<T> query, Expression<Func<T,int>> converter)
{
var ints = new List<int>() { 1, 2, 3, 4, 5 };
return query.Join(ints,converter,i=>i,(t,i) => t);
}
to be called like this:
var filteredPersons = query.WhereFunctionContains(p => p.PersonID);
If this is only used with a single type MyTable:
public static IQueryable<MyTable> WhereFunctionContains(this IQueryable<MyTable> query)
{
var ints = new List<int>() { 1, 2, 3, 4, 5 };
return query.Join(ints, mt=>mt.objid, i=>i, (t,i) => t);
}
Some links from the C# Programming Guide (in order of the answer):
Extension methods
Generics
Generic methods
Generic constraints
Interfaces
LINQ inner joins
Also, see here for a nice overview of LINQ operators, such as Select, Where, Count and ToList.

Related

How to use Queryable.Where when type is set at runtime?

I'm implementing the back end of a search/filter UI for an app using EF6. I have code that builds an Expression to use with Queryable.Where for a given DbSet, where the type of the DbSet is determined at runtime (the DBContext has a lot of them, and they may change). The call to Where works fine if I cheat by casting the Expression to a specific type first. Otherwise, I get this error:
'The best overloaded method match for 'System.Linq.Queryable.Where(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' has some invalid arguments'
I'm struggling to find a way to filter the DbSet like this where the underlying 'table' type is provided at runtime. Here's a greatly simplified version of the code to illustrate:
void ProcessFilter(AppDbContext context, NameValueCollection filters, Type tableType)
{
// If tableType == typeof(Organisation), expression is a Expression<Func<Organisation, bool>>
var expression = GetFilterExpression(filters);
var dbset = Set(context, tableType);
dynamic dynamicSet = dbset;
// This fails
var results = Queryable.Where(dynamicSet, expression);
// see https://stackoverflow.com/questions/4285598/iqueryable-non-generic-missing-count-and-skip-it-works-with-iqueryablet
// Suppose tableType == typeof(Organisation)
// This works
var typedExpression = expression as Expression<Func<Organisation, bool>>;
var typedResults = Queryable.Where(dynamicSet, typedExpression);
}
public static IQueryable Set(DbContext context, Type T)
{
// Similar to code in
// https://stackoverflow.com/questions/21533506/find-a-specified-generic-dbset-in-a-dbcontext-dynamically-when-i-have-an-entity
var method = typeof(DbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Set" && x.IsGenericMethod).First();
// Build a method with the specific type argument
method = method.MakeGenericMethod(T);
return method.Invoke(context, null) as IQueryable;
}
Answering you concrete question. Given
IQueryable source
LambdaExpression predicate
how to call the static generic method
Queryable.Where<T>(IQueryable<T> source, Expression<Func<T, bool>> predicate)
It can be done using (A) reflection, (B) DLR dynamic dispatch and (C) Expression.Call.
What you are trying to do is option (B). However
var result = Queryable.Where((dynamic)source, predicate);
does dynamic search for method having second argument of type LambdaExpression, which of course fails.
In order to be able to dynamically match the target method, you need to make the second argument dynamic as well:
var result = Queryable.Where((dynamic)source, (dynamic)predicate);
The equivalent option (C) implementation of the above is:
var result = source.Provider.CreateQuery(Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType },
source.Expression, predicate));
congrats on your first question.
Let's begin by looking at an approach for filtering a collection of data based on some custom filters. I will assume that the NameValueCollection Type you prefer to pass in your filters, holds PropertyNames as Keys and PropertyValues as Value.
Before we go forth filtering an entire collection, let's first figure out how to determine whether one object has properties that match our filters. And since we do not know the Type of our object till runtime, we will need to use Generics in C# to accomplish this.
Step 1
- Get All Class Properties
We will need to get all properties of our generic class, e.g <TClass>. Doing this using Reflection is deemed as slow and Matt Warren explains Why Reflection is slow in .NET and how to work around it. We shall therefore implement caching of class component model to get its PropertyDescriptorCollection which exists in the namespace System.ComponentModel.PropertyDescriptorCollection.
Components Cache
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache
= new Dictionary<string, PropertyDescriptorCollection>();
The key of our Dictionary represents the name of the generic class and the value holds the PropertyDescriptorCollection of that given class.
internal static bool InnerFilter<T>(T obj, NameValueCollection filters)
where T : class
{
Type type = typeof(T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey(type.Name))
typeDescriptor = _componentsCache[type.Name];
else
{
typeDescriptor = TypeDescriptor.GetProperties(type);
_componentsCache.Add(type.Name, typeDescriptor);
}
}
Step 2
- Loop through filters
After we have gotten the PropertyDescriptorCollection for the generic class T in the variable typeDescriptor as shown above, now let's loop through our filters and see if any of its property names match any of our filter keys. If T has a property name that matches any of our filter keys, now we inspect if the actual value of the property matches our filter value. To improve the quality of our search/filter function, we are going to use Regular Expressions in C# to determine whether a comparison is a hit or a miss.
for (int i = 0; i < filters.Count; i++)
{
string filterName = filters.GetKey(i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else
{
string propValue = propDescriptor.GetValue(obj).ToString();
bool isMatch = Regex.IsMatch(propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
Step 3
- Implement Extension Methods.
To make the code that we've written easy to use and re-use, we are going to implement Extension Methods in C# so that we can better re-use our functions anywhere within our project.
- Generic Collection Filter Function that Uses the above Function.
Since an IQueryable<T> can be converted to an IEnumerable<T> by the .Where() function in System.Linq, we are going to utilize that in our function call as shown below.
public static IEnumerable<T> Filter<T>(this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where(x => x.InnerFilter(filters));
}
Step 4
Put everything together.
Now that we have everything we need, let's look at how the final/full code looks as one block of code in a single static class.
public static class Question54484908
{
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache = new Dictionary<string, PropertyDescriptorCollection> ();
public static IEnumerable<T> Filter<T> (this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where (x => x.InnerFilter (filters));
}
internal static bool InnerFilter<T> (this T obj, NameValueCollection filters)
where T : class
{
Type type = typeof (T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey (type.Name))
typeDescriptor = _componentsCache[type.Name];
else {
typeDescriptor = TypeDescriptor.GetProperties (type);
_componentsCache.Add (type.Name, typeDescriptor);
}
for (int i = 0; i < filters.Count; i++) {
string filterName = filters.GetKey (i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else {
string propValue = propDescriptor.GetValue (obj).ToString ();
bool isMatch = Regex.IsMatch (propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
return false;
}
}
FINALLY
Filtering IEnumerable<T>, List<T>, Arrays
This is how you are going to use the above code anywhere in your project.
private IEnumerable<Question> _questions;
_questions = new List<Question>()
{
new Question("Question 1","How do i work with tuples"),
new Question("Question 2","How to use Queryable.Where when type is set at runtime?")
};
var filters = new NameValueCollection
{
{ "Description", "work" }
};
var results = _questions.Filter(filters);
Filtering DbSet<T>
Every DbContext has a function .Set<T> that returns a DbSet<T> that can be used as an IQueryable<T> and thus our function can be used as well as shown below.
Example
_dbContext.Set<Question>().Filter(filters);
Hope this answers your question or rather points you in the right direction.

Trying to use parent property as parameter in child collection expression; LinqKit throws "Unable to cast MethodCallExpressionN to LambdaExpression"

I'm trying to dynamically construct an expression similar to the one below, where I can use the same comparison function, but where the values being compared can be passed in, since the value is passed from a property 'higher-up' in the query.
var people = People
.Where(p => p.Cars
.Any(c => c.Colour == p.FavouriteColour));
I believe I've constructed the query correctly, but the ExpressionExpander.VisitMethodCall(..) method throws the following exception when I try to use it:
"Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'"
In real-world code, using Entity Framework and actual IQueryable<T>, I often get:
"Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'" as well.
I've constructed a LinqPad-friendly example of my problem, as simple as I could make it.
void Main()
{
var tuples = new List<Tuple<String, int>>() {
new Tuple<String, int>("Hello", 4),
new Tuple<String, int>("World", 2),
new Tuple<String, int>("Cheese", 20)
};
var queryableTuples = tuples.AsQueryable();
// For this example, I want to check which of these strings are longer than their accompanying number.
// The expression I want to build needs to use one of the values of the item (the int) in order to construct the expression.
// Basically just want to construct this:
// .Where (x => x.Item1.Length > x.Item2)
var expressionToCheckTuple = BuildExpressionToCheckTuple();
var result = queryableTuples
.AsExpandable()
.Where (t => expressionToCheckTuple.Invoke(t))
.ToList();
}
public Expression<Func<string, bool>> BuildExpressionToCheckStringLength(int minLength) {
return str => str.Length > minLength;
}
public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
// I'm passed something (eg. Tuple) that contains:
// * a value that I need to construct the expression (eg. the 'min length')
// * the value that I will need to invoke the expression (eg. the string)
return tuple => BuildExpressionToCheckStringLength(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);
}
If I'm doing something obviously wrong, I'd really appreciate a nudge in the right direction! Thanks.
Edit: I know that the following would work:
Expression<Func<Tuple<string, int>, bool>> expr = x => x.Item1.Length > x.Item2;
var result = queryableTuples
.AsExpandable()
.Where (t => expr.Invoke(t))
.ToList();
However, I'm trying to separate the comparison from the location of the parameters, since the comparison could be complex and I would like to re-use it for many different queries (each with different locations for the two parameters). It is also intended that one of the parameters (in the example, the 'min length') would actually be calculated via another expression.
Edit: Sorry, I've just realised that some answers will work when attempted against my example code since my example is merely masquerading as an IQueryable<T> but is still a List<T> underneath. The reason I'm using LinqKit in the first place is because an actual IQueryable<T> from an EntityFramework DbContext will invoke Linq-to-SQL and so must be able to be parsed by Linq-to-SQL itself. LinqKit enables this by expanding everything to expressions.
Solution! Thanks to Jean's answer below, I think I've realised where I'm going wrong.
If a value has come from somewhere in the query (i.e. not a value that is known before-hand.) then you must build the reference/expression/variable to it into the expression.
In my original example, I was trying to pass the 'minLength' value taken from within the expression and pass it to a method. That method call could not be done before-hand, since it used a value from the expression, and it could not be done within the expression, since you can't build an expression within an expression.
So, how to get around this? I chose to write my expressions so that they can be invoked with the additional parameters. Though this has the downside that the parameters are no longer 'named' and I could end up with an Expression<Func<int, int, int, int, bool>> or something down the line.
// New signature.
public Expression<Func<string, int, bool>> BuildExpressionToCheckStringLength() {
// Now takes two parameters.
return (str, minLength) => str.Length > minLength;
}
public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
// Construct the expression before-hand.
var expression = BuildExpressionToCheckStringLength();
// Invoke the expression using both values.
return tuple => expression.Invoke(tuple.Item1 /* string */, tuple.Item2 /* the length */);
}
OK, so what you are trying to do (the transformation from a function that takes a single argument, that returns another function that takes a single argument f(x)(y) into a function that takes two arguments f(x, y)) is known as uncurrying. Look it up! :)
Now, the issue that you have in your code is that, in the expression returned by BuildExpressionToCheckTuple, there is a method call to BuildExpressionToCheckStringLength, which is not resolved. And you cannot resolve it because it takes an argument that is embedded in the tuple parameter.
The solution is, instead of using a method call, to use a lambda expression that will be equivalent to that method call.
That is:
public Expression<Func<int, Func<string, bool>>> ExpressionToCheckStringLengthBuilder() {
return minLength =>
str => str.Length > minLength;
}
public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
// I'm passed something (eg. Tuple) that contains:
// * a value that I need to construct the expression (eg. the 'min length')
// * the value that I will need to invoke the expression (eg. the string)
// Putting builder into a variable so that the resulting expression will be
// visible to tools that analyze the expression.
var builder = ExpressionToCheckStringLengthBuilder();
return tuple => builder.Invoke(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);
}
So you are looking for something like this:
public static class Program
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static IQueryable<T> WherePropertyEquals<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
{
var result = src.Where(e => property.Invoke(e).Equals(value));
return result;
}
public static IQueryable<T> WhereGreater<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
where TProperty : IComparable<TProperty>
{
var result = src.Where(e => property.Invoke(e).CompareTo(value) > 0);
return result;
}
public static IQueryable<T> WhereGreater<T, TProperty>(
this IQueryable<T> src, Expression<Func<T, TProperty>> left, Expression<Func<T, TProperty>> right)
where TProperty : IComparable<TProperty>
{
var result = src.Where(e => left.Invoke(e).CompareTo(right.Invoke(e)) > 0);
return result;
}
public static void Main()
{
var persons = new List<Person>()
{
new Person
{
FirstName = "Jhon",
LastName = "Smith"
},
new Person
{
FirstName = "Chuck",
LastName = "Norris"
},
new Person
{
FirstName = "Ben",
LastName = "Jenkinson"
},
new Person
{
FirstName = "Barack",
LastName = "Obama"
}
}
.AsQueryable()
.AsExpandable();
var chuck = persons.WherePropertyEquals(p => p.FirstName, "Chuck").First();
var ben = persons.WhereGreater(p => p.LastName.Length, 6).First();
var barack = persons.WhereGreater(p => p.FirstName.Length, p => p.LastName.Length).First();
}

LINQ: use Expression inside Where with anonymous type

Goal: call a method that returns an Expression I can use to chain methods on an anonymous IQueryable.
Example:
var allProducts = from p in ctx.Products;
var customWhere = Product.GiveMeYourQuery();
var productsIWant = allProducts.Where(customWhere);
Console.Writeline("yeaaaaah!");
This is what I've come up with as to now, which, of course, doesn't work:
class MainClass
{
public static void Main(string[] args)
{
var list = new [] {
new { Name = "ciao", Age = 18 },
new { Name = "prova", Age = 28 },
new { Name = "mah", Age = 38 },
new { Name = "boh", Age = 48 }
};
var myAnon = list.Where(x => x.Name.Length > 2).AsQueryable();
var thisthat = new MainClass();
var subset = myAnon.Where(thisthat.Where);
}
public Expression<Func<T, bool>> Where<T>(T anon){
var expr = Expression.Lambda(Expression.Equal(Expression.Constant(anon), Expression.Constant(anon)));
return (Expression<Func<T, bool>>) expr;
}
}
Compiler wisdom:
../Program.cs(24,24): Error CS0407: A method or delegate 'System.Linq.Expressions.Expression LINQInjector.MainClass.Where(anonymous type)' return type does not match delegate `bool System.Func(anonymous type)' return type (CS0407) (LINQInjector)
I feel I'm pretty close, but I cannot really see the path.
Unfortunately I cannot just cast the a' object to a type I create because what the program should output is the SQL (yes, I'm basically building a querybuilder that feeds another tier the queries to execute) and because I'd have to create too many types anyway (each type for each possible chain of joins between tens of tables).
EDIT:
While I appreciate you trying to show me alternatives and workarounds, at this point I think there's none. This is my question: how to inject Expressions into an anonymous type. If this cannot be done just say so.
Since you want to access a property known at compile time of an object outside of the scope in which it's created, you don't want to use an anonymous object. An anonymous object would be appropriate if you're accessing it using lambdas created in the same scope in which the anon object is created, but you don't want to do that. You want to have another method statically access a property of that object. Just give it a name, it'll make everything super easy:
public class Foo
{
public string Name{get;set;}
//...
}
public static Expression<Func<Foo, bool>> NameEquals(string name)
{
return foo => foo.Name == name;
}

Generic List of OrderBy Lambda

I am trying to build a generic query mechanism to access my repository. I wish to use Lambda expressions to filter and sort the query. I am struggling to find a way to pass a list of generic Lambda expressions in, specifically for the order-by, and would appreciate help in doing so.
EDIT: 2 requirements I am trying to meet is, not expose IQueryable beyond the repository, but still be able to carry out some filtering and sorting at database level.
To better illustrate this let me show you the code
public class Query<T>
{
public class OrderBy<T>
{
public Expression<Func<T, **int**>> Clause { set; get; } // Order By clause
public bool Descending = true;
}
public Expression<Func<T, bool>> Where { set; get; } // Where clause
public IList<OrderBy<T>> OrderBys { set; get; } // Where clause
public Query()
{
OrderBys = new List<OrderBy<T>>();
}
}
public IEnumerable<Person> FindBy(Query<Person> query)
{
IQueryable<Person> Temp = GetObjectSet();
if (query.Where != null)
Temp = Temp.Where(query.Where);
foreach (var OrderByThis in query.OrderBys)
{
if (OrderByThis.Descending)
Temp = Temp.OrderByDescending(OrderByThis.Clause);
else
Temp = Temp.OrderBy(OrderByThis.Clause);
}
return Temp.ToList<Person>();
}
This is all very nice, BUT Expression< Func< T, int>> is not generic. I need to be able to do something like:
Query<Person> query = new Query<Person>();
Query<Person>.OrderBy<Person> clause1 = new Query<Person>.OrderBy<Person>();
clause1.Clause = m => m.Username;
Query<Person>.OrderBy<Person> clause2 = new Query<Person>.OrderBy<Person>();
clause2.Clause = m => m.DateOfBirth;
query.OrderBys.Add(clause1);
query.OrderBys.Add(clause2);
i.e. adding a number of different fields of different types.
I imagine there must be a way to store these as generic Lambda functions, and then in the repository convert then to the strongly typed Lambda function it needs.
How can I do this?
As I noted in my answer to your other question, I would discourage this approach in general. It makes more sense just to expose IQueryable<T>/IOrderedQueryable<T>.
That being said, there is a solution along the lines of your intention available in the selected answer to How to pass multiple Expressions to OrderBy for EF? .
It allows you to use a syntax like:
var query = context.Users ... ;
var queryWithOrderBy = ApplyOrderBy(query,
new OrderByExpression<User, string>(expression: u => u.UserName, descending: false), // a string, asc
new OrderByExpression<User, int>(expression: u => u.UserId, descending: true)); // an int, desc
var result = queryWithOrderBy.ToList(); // didn't throw an exception for me
Elaborating on my comment, I don't see why you need to construct your own intermediate query object out of Expressions and then reconstruct Expressions from that intermediate object, when you could just skip that translation altogether.
Given your example query:
repository.FindBy(people => people.OrderBy(p => p.Username).ThenBy(p => p.DateOfBirth));
Take note that you can still build up the queries incrementally, if it is being done based on user selections, for example. The following query is equivalent to the above:
Func<IEnumerable<Person>, IEnumerable<Person>> query = people => people.OrderBy(p => p.Username);
query = query.ThenBy(p => p.DateOfBirth);
I understand that you don't want to expose IQueryable beyond the repository, but you can still use LINQ with a signature such as:
public IEnumerable<Person> FindBy(Func<IEnumerable<Person>, IEnumerable<Person>> query)
{
return query(GetObjectSet()).ToList();
}
Speaking to your actual question, however, you can achieve your OrderBy task by using Expression<Func<T, object>> for the Clause property type, or if that unsettles you, you could constrain it a bit more by using IComparable instead of object, as it is really all you need for ordering, and strings and numeric types all implement it.

Sorting/ordering List<object> with composite members and Lambda expressions

I am attempting to Sort a List of objects with composite members. Although I can get this to work for member basic types (Person.EmployeeName), I do not know how to impalement this for composite memebers (Person.Employee.CompanyName). I am creating a Lambda Expression var, and am sending that to into Order by.
Edit: I should add: This is called on the GridView Sort method. I did implement this using [ If (e.SortExpression == "Employee.CompanyName") { ... OrderBy(..Employee.CompanyName) } ] however it has been suggested (by my boss) to keep this dynamic.
public class Company {
public string CompanyName;
}
public class Person {
public Company Employee;
public string EmployeeName;
}
public void LinqExpressionTest() {
// Create List of people with Employeers
List<Person> people = new List<Person>();
people.Add(new Person() { Employee = new Company { CompanyName = "Apple" } });
people.Add(new Person() { Employee = new Company { CompanyName = "Microsoft" } });
// Create Linq Lambda expression for sort based on Person.Employee.CompanyName
// Error on next line because "Employee.CompanyName" is 'not defined for type Expression.Property'
var param = Expression.Parameter(typeof(Person), "Employee.CompanyName");
Expression linqExpression = Expression.Property(param, "Employee.CompanyName");
UnaryExpression unaryExpression = Expression.Convert(linqExpression, typeof(object));
var sortExpression = Expression.Lambda<Func<Person, object>>(unaryExpression, param);
var sortedPeople = people.AsQueryable<Person>().OrderBy(sortExpression).ToList();
}
I'm not sure I see the benefits of making this 'dynamic' as you say.
How about the following which uses generics and a Func delegate - seems a bit extreme though and I have no idea if it'll work - i've just taken my previous query and created a method that'll pass in the key you want to order the list by:
public IEnumerable<TSource> OrderList<TSource, TKey>(
IEnumerable<TSource> list,
Func<TSource, TKey> sortFunction)
{
return from element in list
orderby sortFunction
select element;
}
Then you would call it like this:
OrderList<Person, string>(people, (x)=>(x.Employee.CompanyName));
I don't see the benefit of using that over a Linq query though.
Basically you just want to sort your collection by Employee.CompanyName right? Will the following work?
var sortedList = (from person in people
orderby person.Employee.CompanyName
select person).ToList()
Have a look at some of the samples over at 101Linq Samples - this one is an example of Linq orderby.

Categories

Resources