Conditional Order By in Lambda expression? - c#

I have the following code:
if(result)
{
var query = people.OrderByDescending(person => person.Name)
.Select(person => person.Name);
}
else {
var query = people.OrderBy(person => person.Name)
.Select(person => person.Name);
}
The only difference between this, is that if result is true, it will OrderByDescending, else OrderBy.
Is there a way to clean this up and have less redundant code?

Use ternary operator, and change the order of the extension methods:
var query = people.Select(person => person.Name);
query = result ? query.OrderByDescending(p => p)
: query.OrderBy(p => p);
If you are asking for a way to actually avoid either calling OrderBy or OrderByDescending based on result, no there is not. If you have this type of code sprinkled all over the place, then I'd advise you create a helper extension method to encapsulate this:
public static IEnumerable<Q> OrderBy<T,Q>(
this IEnumerable<T> source,
Func<T,Q> keySelector,
bool descending)
{
var query = source.Select(keySelector);
return descending ? query.OrderByDescending(p => p)
: query.OrderBy(p => p);
}

Related

Chaining IQueryable expression trees

I want to be able to add ordering to my queries dynamically:
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order1 = e => e.OrderBy(x => x.Weight);
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order2 = e => e.OrderByDescending(x => x.Weight).ThenBy(x => x.Price);
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order3 = e => e.OrderBy(x => x.Category).ThenBy(x => x.Price);
IQueryable<MyEntity> query = any EF query;
var transformedQuery = query.Transform(order1/order2/order3);
How do I implement Transform() ?
public static IQueryable<T> Transform<T>(this IQueryable<T> query, Expression<Func<IQueryable<T>, IOrderedQueryable<T>>> orderExpr)
{
// ??????????????????
}
My problem is that I don't want to have 2 overloads for ascending/descending ordering. I need to apply whatever OrderBy()/OrderByDescending() expression or their combination is passed.
This is one case where you don't need an expression tree. Or even a helper method.
Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>> order = e => e.OrderBy(x => x.Weight);
IQueryable<MyEntity> query = any EF query;
var transformedQuery = order(query);
This works because when order is passed in a query, it can itself call the appropriate Queryable.OrderBy overload that takes an expression tree.

C# predicate list passed to Linq Where clause

I have a long Linq Where clause that I would like to populate with a predicate list.
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
filters.Add(p => p.Title != null && p.Title.ToLower().Contains(searchString));
filters.Add(p => p.Notes != null && p.Notes.ToLower().Contains(searchString));
filters.Add(GlobalSearchUser((List < User > users = new List<User>() { p.user1, p.user2, p.user3, p.user4 }), searchString));
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
However I'm getting this error:
cannot convert from 'System.Linq.Expressions.Expression<System.Func<project.Contracts.DTOs.Note,bool>>[]' to 'System.Func<project.Contracts.DTOs.Note,bool>'
Which is an error on the .where clause. Pulling out the .where compiles just fine.
I think great answer from Hogan can be simplified and shorten a bit by use of Any and All Linq methods.
To get items that fulfill all the conditions:
var resultAll = listOfItems.Where(p => filters.All(f => f(p)));
And to get the items that fulfill any condition:
var resultAny = listOfItems.Where(p => filters.Any(f => f(p)));
There are at least two errors in your code:
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
change it to
List<Func<Note, bool>> filters = new List<Func<Note, bool>>();
You don't need Expression trees here. You are using IEnumerable<>, not IQueryable<>
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
There .Where() accepts a single predicate at a time. You could:
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(x => filters.All(x)).Take(10).ToList();
or various other solutions, like:
var notesEnu = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.AsEnumerable();
foreach (var filter in filters)
{
notesEmu = notesEmu.Where(filter);
}
notes = notesEnu.Take(10).ToList();
Because all the .Where() conditions are implicitly in &&.
You have to loop over your filters and run a test on each one.
You can do it with linq like this to return true if any of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == true) return(true); return(false)})
or like this to to return true if all of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == false) return(false); return(true)})
You can't just pass an array of predicates to the where method. You need to either iterate over the array and keep calling Where() for each expression in the array, or find a way to merge them all together into one expression and use that. You'll want to use LinqKit if you go the second route.

Running the same linq query on multiple IQueryable in parallel?

Situation: I have a List<IQueryable<MyDataStructure>>. I want to run a single linq query on each of them, in parallel, and then join the results.
Question: How to create a linq query which I can pass as a parameter?
Example code:
Here's some simplified code. First, I have the collection of IQueryable<string>:
public List<IQueryable<string>> GetQueries()
{
var set1 = (new List<string> { "hello", "hey" }).AsQueryable();
var set2 = (new List<string> { "cat", "dog", "house" }).AsQueryable();
var set3 = (new List<string> { "cat", "dog", "house" }).AsQueryable();
var set4 = (new List<string> { "hello", "hey" }).AsQueryable();
var sets = new List<IQueryable<string>> { set1, set2, set3, set4 };
return sets;
}
I would like to find all the words which start with letter 'h'. With a single IQueryable<string> this is easy:
query.Where(x => x.StartsWith("h")).ToList()
But I want to run the same query against all the IQueryable<string> objects in parallel and then combine the results. Here's one way to do it:
var result = new ConcurrentBag<string>();
Parallel.ForEach(queries, query =>
{
var partOfResult = query.Where(x => x.StartsWith("h")).ToList();
foreach (var word in partOfResult)
{
result.Add(word);
}
});
Console.WriteLine(result.Count);
But I want this to be a more generic solution. So that I could define the linq operation separately and pass it as a parameter to a method. Something like this:
var query = Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName);
var queries = GetQueries();
var result = Run(queries, query);
But I'm at loss on how to do this. Any ideas?
So the first thing that you wanted was a way of taking a sequence of queries, executing all of them, and then getting the flattened list of results. That's simple enough:
public static IEnumerable<T> Foo<T>(IEnumerable<IQueryable<T>> queries)
{
return queries.AsParallel()
.Select(query => query.ToList())
.SelectMany(results => results);
}
For each query we execute it (call ToList on it) and it's done in parallel, thanks to AsParallel, and then the results are flattened into a single sequence through SelectMany.
The other thing that you wanted to do was to add a number of query operations to each query in a sequence of queries. This doesn't need to be parallelized (thanks to deferred execution, the calls to Where, OrderBy, etc. take almost no time) and can just be done through Select:
var queries = GetQueries().Select(query =>
query.Where(x => x.FirstName.StartsWith("d")
&& !x.IsRemoved)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName));
var results = Foo(queries);
Personally I don't really see a need to combine these two methods. You can make a method that does both, but they're really rather separate concepts so I don't see a need for it. If you do want them combined though, here it is:
public static IEnumerable<TResult> Bar<TSource, TResult>(
IEnumerable<IQueryable<TSource>> queries,
Func<IQueryable<TSource>, IQueryable<TResult>> selector)
{
return queries.Select(selector)
.AsParallel()
.Select(query => query.ToList())
.SelectMany(results => results);
}
Feel free to make either Foo or Bar extension methods if you want. Also, you really better rename them to something better if you're going to use them.
First - given your current implementation, there is no reason to use IQueryable<T> - you could just use IEnumerable<T>.
You could then write a method which takes an IEnumerable<IEnumerable<T>> and a Func<IEnumerable<T>, IEnumerable<U>>, to build a result:
IEnumerable<IEnumerable<U>> QueryMultiple<T,U>(IEnumerable<IEnumerable<T>> inputs, Func<IEnumerable<T>,IEnumerable<U>> mapping)
{
return inputs.AsParallel().Select(i => mapping(i));
}
You could then use this as:
void Run()
{
IEnumerable<IEnumerable<YourType>> inputs = GetYourObjects();
Func<IEnumerable<YourType>, IEnumerable<YourType>> query = i =>
i.Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false)
.Select(x => x.FirstName)
.OrderBy(x => x.FirstName);
var results = QueryMultiple(inputs, query);
}

How do I define a SELECT TOP using LINQ with a dynamic query?

I want to pass dynamic lambda expressions to the function below, but I'm not sure how to define the .Take() or .OrderByDescending() on the expression object.
If I want to call the function below, then I want to be able to do this:
dbprovider.Query = (x => x.ConfigurationReference == "172.16.59.175")
.Take(100)
.OrderByDescending(x.Date)
FindEntities(db, dbprovider.Query)
But I can't (this syntax is invalid). Any ideas?
public static List<T> FindEntities<T>(TrackingDataContext dataContext, System.Linq.Expressions.Expression<Func<T, bool>> find) where T : class
{
try
{
var val = dataContext.GetTable<T>().Where(find).ToList<T>();
return val;
}
catch (Exception ex)
{
throw ex;
}
}
The parameter is of type:
System.Linq.Expressions.Expression<Func<T, bool>> find
That means it can take a predicate (the "where" clause), and only a predicate. Thus the only bit you can pass in there is the filter:
x => x.ConfigurationReference == "172.16.59.175"
To do what you want, you would need to add the rest of the code in FindEntities, so that it becomes:
var val = dataContext.GetTable<T>().Where(find)
.OrderByDescending(x => x.Date).Take(100).ToList<T>();
(note also that the Take should really be after the OrderByDescending)
One way you could do that would be:
public static List<T> FindEntities<T>(TrackingDataContext dataContext,
System.Linq.Expressions.Expression<Func<T, bool>> find,
Func<IQueryable<T>, IQueryable<T>> additonalProcessing = null
) where T : class
{
var query = dataContext.GetTable<T>().Where(find);
if(additonalProcessing != null) query = additonalProcessing(query);
return query.ToList<T>();
}
and call:
var data = FindEntities(db, x => x.ConfigurationReference == "172.16.58.175",
q => q.OrderByDescending(x => x.Date).Take(100));
However, frankly I'm not sure what the point of this would be... the caller could do all of that themselves locally more conveniently, without using FindEntities at all. Just:
var data = db.GetTable<T>()
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or even:
var data = db.SomeTable
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or just:
var data = (from row in db.SomeTable
where row.ConfigurationReference == "172.16.58.175"
orderby row.Date descending
select row).Take(100).ToList();

How can I specify to use Linq ThenBy clause only when there is a tie?

I have a linq query (not database-related) with OrderBy and ThenBy
var sortedList = unsortedList
.OrderBy(foo => foo.Bar) //this property access is relatively fast
.ThenBy(foo => foo.GetCurrentValue()) //this method execution is slow
getting foo.Bar is fast, but executing foo.GetCurrentValue() is very slow. The return value only matters if some members have equal Bar values, which happens rarely but important to be considered in case it happens. Is it possible to choose to only execute the ThenBy clause when it's necessary to tie-break in case of equal Bar values? (i.e. will not be executed if foo.Bar values are unique).
Also, actually Bar is also a bit slow, so it is preferred not to invoke it twice for the same object.
Since you are not in a database, and you need a tight control over the sorting, you could use a single OrderBy with a custom IComparer that accesses only what it needs, and does not perform unnecessary evaluations.
This is a bit clumsy, but I'm sure it can be improved - maybe it won't be done in one linq statement, but it should work:
var sortedList2 = unsortedList
.OrderBy(foo => foo.Bar)
.GroupBy(foo => foo.Bar);
var result = new List<Foo>();
foreach (var s in sortedList2)
{
if (s.Count() > 1)
{
var ordered = s
.OrderBy(el => el.GetCurrentValue());
result.AddRange(ordered);
}
else
{
result.AddRange(s);
}
}
UPDATE:
We can argue if that's an improvement, but it looks more concise at least:
var list3 = (from s in sortedList2
let x = s.Count()
select x == 1
? s.Select(el => el)
: s.OrderBy(el => el.GetCurrentValue()))
.SelectMany(n => n);
UPDATE2:
You can use Skip(1).Any() instead of Count() - this should avoid the enumeration of the whole sequence I guess.
var query = unsortedList
.GroupBy(foo => foo.Bar)
.OrderBy(g => g.Key)
.SelectMany(g => g.Skip(1).Any() ? g.OrderBy(foo => foo.GetCurrentValue()) : g);
This has the obvious downside of not returning IOrderedEnumerable<Foo>
I changed Joanna Turban's solution and developed the following extension method:
public static IEnumerable<TSource> OrderByThenBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> orderBy, Func<TSource, TKey> thenBy)
{
var sorted = source
.Select(s => new Tuple<TSource, TKey>(s, orderBy(s)))
.OrderBy(s => s.Item2)
.GroupBy(s => s.Item2);
var result = new List<TSource>();
foreach (var s in sorted)
{
if (s.Count() > 1)
result.AddRange(s.Select(p => p.Item1).OrderBy(thenBy));
else
result.Add(s.First().Item1);
}
return result;
}
Try this one
var sortedList = unsortedList.OrderBy(foo => foo.Bar);
if(some_Condition)
{
sortedList = sortedList.OrderBy(foo => foo.GetCurrentValue());
}

Categories

Resources