Use OrderBy in a LINQ predicate? - c#

In my code I need to sort a collection either by Price or by Rating.TotalGrade and as you can see both LINQ querys are almost the same statement with only a minor difference.
I was thinking about using a LINQ predicate instead but as you can see the the orderby is the main difference and I found no sample using orderby in a query. Is it possible or are there other ways to shorten my code, Maybe there will be even more conditions in the future.
if (CurrentDisplayMode == CRSChartRankingGraphDisplayMode.Position)
{
this.collectionCompleteSorted = new List<Result>(from co in collection
where co.IsVirtual == false
orderby co.Price, co.CurrentRanking
select co);
}
else if (CurrentDisplayMode == CRSChartRankingGraphDisplayMode.Grade)
{
this.collectionCompleteSorted = new List<Result>(from co in collection
where co.IsVirtual == false
orderby co.Rating.TotalGrade, co.CurrentRanking
select co);
}

You can easily make use of deffered nature of LINQ and ability to easily compose queries.
Probably using code like this:
var baseQuery = from co in collection where !co.IsVirtual select co; // base of query
IOrderedEnumerable<Result> orderedQuery; // result of first ordering, must be of this type, so we are able to call ThenBy
switch(CurrentDisplayMode) // use enum here
{ // primary ordering based on enum
case CRSChartRankingGraphDisplayMode.Position: orderedQuery = baseQuery.OrderBy(co => co.Price);
break;
case CRSChartRankingGraphDisplayMode.Grade: orderedQuery = baseQuery.OrderBy(co => co.TotalGrade);
break;
}
this.collectionCompleteSorted = orderedQuery.ThenBy(co => co.CurrentRanking).ToList(); // secondary ordering and conversion to list
Its easy to understand and avoids converting to list until the very end.

If just your order by is different, than return your result into this.collectionCompleteSorted, and then do this.collectionCompleteSorted.OrderBy() when you enumerate through the data.
this.collectionCompleteSorted = new List<Result>(from co in collection
where co.IsVirtual == false
select co);
foreach (var obj in this.collectionCompleteSorted.OrderBy(c => c.Price).ToList())
{
// do something
}
And remove the order by remove your current linq query.
If the query is just being executed once, than you can leave off the .ToList() from the linq query in the example above. When ToList is called, this causes the query to be executed immediately, where if the OrderBy is implemented in a later call to the collection and a ToList is also, than the query would actually be executed on the database server with an order by statement, offloading the ordering from the code to the database. See http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx

Why not just grab the values (less the sort), then (as it appears) use a case to order the results?
// build your collection first
var items = from co in collection
where !co.IsVirtual
select co;
// go through your sort selectors
select (CurrentDisplayMode)
{
case CRSChartRankingGraphDisplayMode.Position:
this.collectionCompleteSorted = items.OrderBy(i => i.Price).ThenBy(j => j.CurrentRanking).ToList();
break;
case CRSChartRankingGraphDisplayMode.Grade:
this.collectionCompleteSorted = items.OrderBy(i => i.TotalGrade).ThenBy(j => j.CurrentRanking).ToList();
break;
...
//default: // maybe you want this, too
}

var q = collection.Where(co => !co.IsVirtual);
if (CurrentDisplayMode == CRSChartRankingGraphDisplayMode.Position)
{
q = q.OrderBy(co => co.Price).ThenBy(co => co.CurrentRanking);
}
else if (CurrentDisplayMode == CRSChartRankingGraphDisplayMode.Grade)
{
q = q.OrderBy(co => co.Rating.TotalGrade).ThenBy(co => co.CurrentRanking);
}
this.collectionCompleteSorted = q.ToList();

Related

Linq with Optional Where clauses

I have a Linq statement which has two optional Where clauses but I cannot work out how to implement this linq (at the moment I have 4 linq statements in this method which off course is not good!):
//Params passed into my method:
int StoreId = 0;
bool ShowEnabledTills = true;
var query = (from Transactions in db.Transactions
join Tills in db.Tills on new { TillId = Convert.ToInt32(Transactions.TillId) } equals new { TillId = Tills.TillId }
join CompanyAddresses in db.CompanyAddresses on new { CompanyId = Convert.ToInt32(Transactions.StoreID) } equals new { CompanyId = CompanyAddresses.CompanyId }
where
CompanyAddresses.CompanyId == StoreId <===== OPTIONAL
&& Tills.Active == ShowEnabledTills <===== OPTIONAL
select new
{
Tills.TillId,
Tills.ComputerName,
Tills.Description,
CompanyAddresses.CompDescription
}).Distinct();
I took a look at PredicateBuilder but I couldn't quite get my head around this but if I can create some re-usable code this would be great!
// get these from params to decide optional or not
var validateStoredId = false;
var validateActiveStatus = false;
And in your where clause do something like:
where
(!validateStoreId || CompanyAddresses.CompanyId == StoreId)
&& (!validateActiveStatus || Tills.Active == ShowEnabledTills)
I have a little to contribute here I think. I came across the same problem, and implemented a similar solution. HOWEVER! This wont short-circuit as you would hope (or at least it didn't in mine!)
This led to me looping through a list of 10k items checking if true == false effectively, which whilst not an expensive check is a check you don't want to do thousands of times nevertheless.
For anyone else coming to this, I'd recommend splitting your queries down into subqueries wrapped in if checks, that is far more performant when dealing with large datasets :)
Edit : I built this little helper for doing exactly this, hope someone else finds it useful, I enjoyed not having to break my method chains :)
public static IEnumerable<T> OptionalWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool excecuteFilter)
{
if (!excecuteFilter)
{
return source;
}
else
{
return source.Where(predicate);
}
}
Try this:
var query = db.Transactions.Join(
db.Tills, //Join to this table
transaction => transaction.TillId //Outer key
, till => till.TillId, //Inner key
(transaction, till) => new {transaction, till}) //Result of join
.Join(db.CompanyAddresses, //Join to this table
r => r.till.StoreId, //Outer key
a => a.CompanyId, //Inner key
(r, address) => new {r.transaction, r.till, address}); //Result of join
if(StoreId != null)
query = query.Where(d => d.address.StoreId == StoreId);
if(ShowEnabledTills)
query = query.Where(d => d.till.Active == true);
var items = query.Select(d => new {
d.Till.TillId,
d.Till.ComputerName,
d.Till.Description,
d.address.CompDescription
}).Distinct();
I did it without knowing much about your schema, but this should give you a good idea.
I disliked the "SQL style" syntax for this very reason (confusing at times). I started using method calls like this and never had a problem since.

Clear an IQueryable from its orderby clauses

Is there a way to clear an IQueryable from its ordeyby clauses?
For example, we have a function that returns an ordered query:
public IQueryable<SomeType> GetOrderedQuery()
{
var query = from item in db.itemsTable
where item.x != null
orderby item.y, item.z
select item;
return query;
}
And we have another function that needs to use the same query, but it needs to have it unordered:
public IQueryable<SomeType> GetUnorderedQuery()
{
var query = GetOrderedQuery();
query.RemoveOrders(); // How to implement a RemoveOrders function?
return query;
}
How can a RemoveOrders function be implemented? (Doesn't matter if as an extension method or not)
If you don't want it ordered; don't order it. There's no robust way to walk back through an IQueryable<T> to get earlier states, let alone remove individual bits out of the middle. I suspect you want two queries:
public IQueryable<SomeType> GetUnorderedQuery()
=> db.itemsTable.Where(item => item.x != null);
public IOrderedQueryable<SomeType> GetOrderedQuery()
=> GetUnorderedQuery().OrderBy(item => item.y).ThenBy(item => item.z);

changing linq to sql query

I have a question regarding a Linq to SQL query.
I have following situation:
I have a search with lots of options, like location, availability, name, language etc ...
For this options i have to execute a query to retrieve the results according to options selected, how can i best do it, i cannot write a linq query like for each possibility and combination of options, but i cannot write one for all of them as it will not work, for example:
from p in context.people where p.location==model.location && p.availability==model.availability .... select p
In this case imagine availability is not selected and should not be searched for, but in this case it will be passed as false, or if location is not set and is null so it will only search for empty locations, although i just need all.
So my question is how do people handle this kind of behaviour with queries?
As you long as you do not execute the linq query immediately you can just add where clauses to it. You can do this for example:
var query = from p in context.people;
if(searchOnLocation)
{
query = query.where(p => p.location == model.location);
}
if(otherSearch)
{
query = query.where(p => p.someOtherProperty == someotherValue);
}
var result = query.ToList();
As long you don't call ToList() on your IQueryable, the linq will not be translated into SQL. It's only in the last call, that the linq will be translated and executed against the database
IQueryable<Person> query = context.people;
if(model.location != null)
query = query.Where(x => x.location == model.location);
if(model.availability != null)
query = query.Where(x => x.availability == model.availability);
// etc
Basically, you can compose more and more restrictions as you go.
If you want to implement query without if condition than you can use following syntax:
var query = context.people.
where(p => p.location == (model.location ?? p.location)
&& p.availability == (model.availability ?? p.availability))
.ToList();

Linq: how to exclude condition if parameter is null

I have some table and the following condition of query: if parameter A is null take all, if not, use it in the query. I know how to do that in 2 steps:
List<O> list = null;
if (A = null)
{
list = context.Obj.Select(o => o).ToList();
}
else
{
list = context.Obj.Where(o.A == A).ToList();
}
Is it possible to have the same as one query?
Thanks
How about:
list = context.Obj.Where(o => A == null || o.A == A)
.ToList();
You can do it in one query but still using a condition:
IEnumerable<O> query = context.Obj;
if (A != null)
{
query = query.Where(o => o.A == A);
}
var list = query.ToList();
Or you could use a conditional operator to put the query in a single statement:
var query = A is null ? context.Obj : context.Obj.Where(o => o.A == A);
var list = query.ToList();
I would personally suggest either of the latter options, as they don't require that the LINQ provider is able to optimise away the filter in the case where A is null. (I'd expect most good LINQ providers / databases to be able to do that, but I'd generally avoid specifying a filter when it's not needed.)
I opted for
var list = context.Obj.Where(o => A.HasValue ? o.a == A : true);
I would probably write the query like this:
IQueryable<O> query = context.Obj;
if (A != null)
query = query.Where(o => o.A == A);
var list = query.ToList()
It's not one expression, but I think it's quite readable.
Also, this code assumes that context.Obj is IQueryable<O> (e.g. you are using LINQ to SQL). If that's not the case, just use IEnumerable<O>.

Why does this LINQ-to-SQL query get a NotSupportedException?

The following LINQ statement:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
return (from t in db.Tasks
where searchTerms.All(term =>
t.Title.ToUpper().Contains(term.ToUpper()) &&
t.Description.ToUpper().Contains(term.ToUpper()))
select t).Cast<Item>().ToList();
}
}
gives me this error:
System.NotSupportedException: Local
sequence cannot be used in LINQ to SQL
implementation of query operators
except the Contains() operator.
Looking around it seems my only option is to get all my items first into a generic List, then do a LINQ query on that.
Or is there a clever way to rephrase the above LINQ-to-SQL statement to avoid the error?
ANSWER:
Thanks Randy, your idea helped me to build the following solution. It is not elegant but it solves the problem and since this will be code generated, I can handle up to e.g. 20 search terms without any extra work:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
switch (searchTerms.Count())
{
case 1:
return (db.Tasks
.Where(t =>
t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0])
)
.Select(t => t)).Cast<Item>().ToList();
case 2:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
)
.Select(t => t)).Cast<Item>().ToList();
case 3:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
&&
(t.Title.Contains(searchTerms[2])
|| t.Description.Contains(searchTerms[2]))
)
.Select(t => t)).Cast<Item>().ToList();
default:
return null;
}
}
}
Ed, I've run into a similiar situation. The code is below. The important line of code is where I set the memberList variable. See if this fits your situation. Sorry if the formatting didn't come out to well.
Randy
// Get all the members that have an ActiveDirectorySecurityId matching one in the list.
IEnumerable<Member> members = database.Members
.Where(member => activeDirectoryIds.Contains(member.ActiveDirectorySecurityId))
.Select(member => member);
// This is necessary to avoid getting a "Queries with local collections are not supported"
//error in the next query.
memberList = members.ToList<Member>();
// Now get all the roles associated with the members retrieved in the first step.
IEnumerable<Role> roles = from i in database.MemberRoles
where memberList.Contains(i.Member)
select i.Role;
Since you cannot join local sequence with linq table, the only way to translate the above query into SQL woluld be to create WHERE clause with as many LIKE conditions as there are elements in searchTerms list (concatenated with AND operators). Apparently linq doesn't do that automatically and throws an expception instead.
But it can be done manually by iterating through the sequence:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
IQueryable<Task> taskQuery = db.Tasks.AsQueryable();
foreach(var term in searchTerms)
{
taskQuery = taskQuery.Where(t=>t.Title.ToUpper().Contains(term.ToUpper()) && t.Description.ToUpper().Contains(term.ToUpper()))
}
return taskQuery.ToList();
}
}
Mind that the query is still executed by DBMS as a SQL statement. The only drawback is that searchTerms list shouldn't be to long - otherwise the produced SQL statement won'tbe efficient.

Categories

Resources