Code :
IList<Evento> Eventi = new List<Evento>() { };
Eventi = (from Evento ae in new Eventi()
select ae).ToList();
if (strNome != "")
{
Eventi = Eventi.Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "").ToList();
}
if (strComune != "")
{
Eventi = Eventi.Where(e => e.Comune != null && e.IDComune == strComune).ToList();
}
if (strMesi != "")
{
Eventi = Eventi.Where(e => MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())).ToList();
}
I know all query are merged, during running time of code, in only 1 LINQ statement. But, as you can see, I convert the List -> ToList() many times. This, I think, here, is the only part when I waste time, right? How can I avoid this and improve performance?
Why so many lists/ToLists? What's wrong with IEnumerable/IQueryable?
var eventi = (from Evento ae in new Eventi()
select ae);
if (strNome != "")
{
eventi = eventi.Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "");
}
if (strComune != "")
{
eventi = eventi.Where(e => e.Comune != null && e.IDComune == strComune);
}
if (strMesi != "")
{
eventi = eventi.Where(e => MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString()));
}
// if you do need a list, then do so right at the end
var results = eventi.ToList();
EDIT: To clarify some principles for Caesay
Caesay, thank you for taking the time to test the implementation to confirm the deferred loading works as intended; much kudos!
I wanted to explain why I disagree with your comment about the above approach being optimized at run-time whilst yours being optimized at compile time.
The above approach is, for lack of a better description, the intended approach. This is because the assignments to eventi are correctly appending expressions to the source of the IEnumerable/IQueryable.
Your approach is only supported by certain providers, such as Linq to Entities, which expect a Func(Of T, TResult) to be passed to their Select, Where, etc Extensions. Many providers, such as Entity Framework and Linq to Sql provider, provide IQueryable, which implements IEnumerable. The difference here, however, is that IQueryable's Select, Where, etc, expect you to pass an Expression(Of Func(Of T, TResult)).
In those cases, your code will not behaveas expected (or at least as I would expect), because Expression does not support multi-line lambda, where as the compiler will correctly interpret my statements and compile them into Expression>.
As a simple example:
public void Test<T1, T2>(System.Linq.Expressions.Expression<Func<T1, T2>> arg)
{
throw new NotImplementedException();
}
public void Test()
{
Test((string x) => x.ToLower());
Test((string x) =>
{
return x.ToLower();
});
}
In the above example, the first expression is absolutely fine. The second, which is based loosely on your example, will fail with the exception:
A lambda expression with a statement body cannot be converted to an expression tree
The compiler may recognise your statements as a Func which it knows is supported on IEnumerable. The result would be that the query to the database would not include any of your Where expressions, returning the whole data source. Once the data source was in-memory, it would then apply your IEnumerable Where clauses. Personally, I much prefer passing these kind of things to the database so that I'm not wasting bandwidth on returning much more data than I need, and I can utilise my Data Sources ability to filter data which is likely (and vastly in Sql Server's case) better than doing so in-memory.
I hope that makes sense and is of some use to you?
You can use fluent syntax to start with (to avoid List).
Alternatively you can combine the condition in one query.
Eventi =(new Eventi()).Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "" && e.Comune != null && e.IDComune == strComune &&MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())).ToList();
I have assumed Eventi implements IEnumerable < Evento > as you have used similarly in query syntax
Given that you test for strNome not being empty, the second half of your first Where clause will never be called.
So e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != ""
can be written e.Titolo.ToLower().Contains(strNome.ToLower())
You could also calculate strNome.ToLower() outside the lambda to ensure it is calculated just once.
In addition you can streamline the Where clauses and remove the ToList() as others have suggested.
Another alternative you should be aware of is LinqKit which allows you to combine lambda expressions more easily which although not needed in this case since Where ... Where is good enough for an 'And', you might one day need an 'Or' and then you need a different solution.
Or, better yet, use the method explained here to create your own And and Or methods that perform Expression 'magic' to give you a single expression that you can hand off to Linq-to-Sql or any other Linq provider.
Combine the whole thing into 1 linq query like this:
var eventi = from Evento e in new Eventi() select e;
eventi = eventi.Where(e =>
{
if (strNome != "")
{
if(!(e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != ""))
return false;
}
if (strComune != "")
{
if(!(e.Comune != null && e.IDComune == strComune))
return false;
}
if (strMesi != "")
{
if(!(MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())))
return false;
}
return true;
});
var results = eventi.ToList();
This is logically equivalent to your code, but should be much faster. Although I was not able to test it, because I can't compile it.
Related
I am using EF4.3 with a unit-of-work + repository pattern.
I have a method which act as a base of some other methods, here's how the code looks.
This is my 'base' method:
public static IQueryable<Deal> FindActive()
{
var r = new ReadRepo<Deal>(Local.Items.Uow.Context);
return r.Find(d =>
d.ActiveFrom <= DateTime.Now &&
(d.ActiveUntilComputed == null || d.ActiveUntilComputed > DateTime.Now) &&
d.Published);
}
Here is one of the methods that calls the base method:
public static IQueryable<Deal> FindActiveByStore(int storeId)
{
Guard.Default(storeId, "storeId");
return FindActive().Where(d => d.StoreId == storeId);
}
As you can see in FindActiveByStore, I first call FindActive, which then chains Find(). FindActive is followed by a Where() to add a secondary predicate (excuse the terminology).
I wondered if it was possible to pass a predicate to FindActive instead of using a Where(), and in fact whether it would make a difference in terms of performance.
Like this:
FindActive(d => d.StoreId == storeId)
FindActive already passes a predicate to Find() so it would need to combine both.
I'm guessing that the answers I get back will be along the lines 'its not worth it' in terms of effort or performance but I thought i'd ask the experts anyway.
You can use this code (You reduce number lines of code)
public IQueryable<Deal> FindActiveByStore(Expression<Func<Deal,bool>> predicate)
{
var r = new ReadRepo<Deal>(Local.Items.Uow.Context);
return r.Find(d => d.ActiveFrom <= DateTime.Now
&& (d.ActiveUntilComputed == null || d.ActiveUntilComputed > DateTime.Now)
&& d.Published)
.Where(predicate);
}
I am implementing sort based on parameter passed to ascending or descending OrderBy method
else if (showGrid.Sortdir == "DESC")
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderByDescending(a => showGrid.Sort);
}
else
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderBy(a => showGrid.Sort);
}
In case of ascending order sorting it works fine but for descending order sorting doesn't work. I debugged the code and I found that list is not revered its same as ascending order. Please help me
Ok. I've written a small test. It is funny, but your code can actually compile and work, but very differently from what you expect :)
Obviously showGrid is not of type Alert, it is an instance of some other class, that incidentally have the same propery as Alert, called Sort.
First I was confused, because expected this code to fail to compile.
// The signature of OrderBy
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
// In your case it will result in
public static IOrderedQueryable<Alert> OrderBy<Alert, string>(this IQueryable<Alert> source, Expression<Func<Alert, string>> keySelector)
//when you call it like you do
DB.Incidents.OfType<Alert>().OrderByDescending(a => showGrid.Sort);
// You supply a property from object of type different from your entity.
// This is incorrect usage, the only object you can use here is the
// "a" argument. Like this:
DB.Incidents.OfType<Alert>().OrderByDescending(a => a.Sort);
// Because anything else does not make any sense to entity provider.
So your order by simply does not work.
As far as I understood, what you want is to perform sorting based on selection in UI. This is not easily achieved in strongly-typed LINQ. Because as I showed above, you send a property, not a value to the OrderBy. It does not care about the value inside the prop. So there are several solutions to the problem:
Write a big switch, that will check every possible Sort value, and will append appropriate 'OrderBy(a => a.YouPropToSort)' to the query. This is straitforward, and you should begin with this. Of course this is a static way, and will require to change code everytime you want new columns to be added for sorting.
Create argument for your OrderBy using 'LINQ Expression Trees'. For you case it should not be very hard to do. Look for the term, you will find a lot of examples.
Try to use Dynamic LINQ. I did not not use it myself, just looked at the docs. This seems to be an extension to the normal LINQ which allows you to write parts of queries as strings, to overcome limitations like the current one with dynamic sorting.
Here's my solution to sorting based on user selections:
Create your base query
var query = DB.Incidents.OfType<Alert>.Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching);
and then apply your sort using a case statement
bool desc = showGrid.SortDir = "DESC";
switch(showGrid.Sort)
{
case "col1":
query = desc ? query.OrderByDescending( a => a.Col1 ) : query.OrderBy( a => a.Col1 );
break;
case "col2":
query = desc ? query.OrderByDescending( a => a.Col2 ) : query.OrderBy( a => a.Col2 );
break;
...
}
var results = query.ToList();
Is there a (logical/performance) difference to writing:
ATable.Where(x=> condition1 && condition2 && condition3)
or
ATable.Where(x=>condition1).Where(x=>condition2).Where(x=>condition3)
I've been using the former but realised that with the latter, I can read and copy parts of a query out to use somewhere else easier.
Any thoughts?
Short answer
You should do what you feel is more readable and maintainable in your application as both will evaluate to the same collection.
Long answer quite long
Linq To Objects
ATable.Where(x=> condition1 && condition2 && condition3)
For this example Since there is only one predicate statement the compiler will only needs to generate one delegate and one compiler generated method.
From reflector
if (CS$<>9__CachedAnonymousMethodDelegate4 == null)
{
CS$<>9__CachedAnonymousMethodDelegate4 = new Func<ATable, bool>(null, (IntPtr) <Main>b__0);
}
Enumerable.Where<ATable>(tables, CS$<>9__CachedAnonymousMethodDelegate4).ToList<ATable>();
The compiler generated method:
[CompilerGenerated]
private static bool <Main>b__0(ATable m)
{
return ((m.Prop1 && m.Prop2) && m.Prop3);
}
As you can see there is only one call into Enumerable.Where<T> with the delegate as expected since there was only one Where extension method.
ATable.Where(x=>condition1).Where(x=>condition2).Where(x=>condition3) now for this example a lot more code is generated.
if (CS$<>9__CachedAnonymousMethodDelegate5 == null)
{
CS$<>9__CachedAnonymousMethodDelegate5 = new Func<ATable, bool>(null, (IntPtr) <Main>b__1);
}
if (CS$<>9__CachedAnonymousMethodDelegate6 == null)
{
CS$<>9__CachedAnonymousMethodDelegate6 = new Func<ATable, bool>(null, (IntPtr) <Main>b__2);
}
if (CS$<>9__CachedAnonymousMethodDelegate7 == null)
{
CS$<>9__CachedAnonymousMethodDelegate7 = new Func<ATable, bool>(null, (IntPtr) <Main>b__3);
}
Enumerable.Where<ATable>(Enumerable.Where<ATable>(Enumerable.Where<ATable>(tables, CS$<>9__CachedAnonymousMethodDelegate5), CS$<>9__CachedAnonymousMethodDelegate6), CS$<>9__CachedAnonymousMethodDelegate7).ToList<ATable>();
Since we have three chained Extension methods we also get three Func<T>s and also three compiler generated methods.
[CompilerGenerated]
private static bool <Main>b__1(ATable m)
{
return m.Prop1;
}
[CompilerGenerated]
private static bool <Main>b__2(ATable m)
{
return m.Prop2;
}
[CompilerGenerated]
private static bool <Main>b__3(ATable m)
{
return m.Prop3;
}
Now this looks like this should be slower since heck there is a ton more code. However since all execution is deferred until GetEnumerator() is called I doubt any noticeable difference will present itself.
Some Gotchas that could effect performance
Any call to GetEnumerator in the chain will cause a the collection to be iterated. ATable.Where().ToList().Where().ToList() will result in an iteration of the collection with the first predicate when the ToList is called and then another iteration with the second ToList. Try to keep the GetEnumerator called to the very last moment to reduce the number of times the collection is iterated.
Linq To Entities
Since we are using IQueryable<T> now our compiler generated code is a bit different as we are using Expresssion<Func<T, bool>> instead of our normal Func<T, bool>
Example in all in one.
var allInOneWhere = entityFrameworkEntities.MovieSets.Where(m => m.Name == "The Matrix" && m.Id == 10 && m.GenreType_Value == 3);
This generates one heck of a statement.
IQueryable<MovieSet> allInOneWhere = Queryable.Where<MovieSet>(entityFrameworkEntities.MovieSets, Expression.Lambda<Func<MovieSet, bool>>(Expression.AndAlso(Expression.AndAlso(Expression.Equal(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(MovieSet), "m"), (MethodInfo) methodof(MovieSet.get_Name)), ..tons more stuff...ParameterExpression[] { CS$0$0000 }));
The most notable is that we end up with one Expression tree that is parsed down to Expression.AndAlso pieces. And also like expected we only have one call to Queryable.Where
var chainedWhere = entityFrameworkEntities.MovieSets.Where(m => m.Name == "The Matrix").Where(m => m.Id == 10).Where(m => m.GenreType_Value == 3);
I wont even bother pasting in the compiler code for this, way to long. But in short we end up with Three calls to Queryable.Where(Queryable.Where(Queryable.Where())) and three expressions. This again is expected as we have three chained Where clauses.
Generated Sql
Like IEnumerable<T> IQueryable<T> also does not execute until the enumerator is called. Because of this we can be happy to know that both produce the same exact sql statement:
SELECT
[Extent1].[AtStore_Id] AS [AtStore_Id],
[Extent1].[GenreType_Value] AS [GenreType_Value],
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[MovieSet] AS [Extent1]
WHERE (N'The Matrix' = [Extent1].[Name]) AND (10 = [Extent1].[Id]) AND (3 = [Extent1].[GenreType_Value])
Some Gotchas that could effect performance
Any call to GetEnumerator in the chain will cause a call out to sql, e.g. ATable.Where().ToList().Where() will actually query sql for all records matching the first predicate and then filter the list with linq to objects with the second predicate.
Since you mention extracting the predicates to use else where, make sure they are in the form of Expression<Func<T, bool>> and not simply Func<T, bool>. The first can be parsed to an expression tree and converted into valid sql, the second will trigger ALL OBJECTS returned and the Func<T, bool> will execute on that collection.
I hope this was a bit helpful to answer your question.
Whats the use of using custom comparers in Linq queries? Are they beneficial or are they just an overload on the server.
So i am talking about queries like
IEnumerable<Class> GetMatch(Class comparerObject)
{
return Session.Linq<Class>().Where(x=>new StringComparer<Class>().Equals(x,comparerObject))
}
and this is how my stringcomparer class looks like
public class StringComparer<T> : IEqualityComparer<T>
where T : class, IStringIdentifiable
{
public bool Equals(T x, T y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Id.Equals(y.Id, StringComparison.OrdinalIgnoreCase);
}
So I was wondering how is this query run against the db? I think linq handles this internally where in it sends a request to the db only after all the cases in the comparere are run.
Edit:
Well if you are finding it hard to believe that the above will not work then take a simple example like
return Session.Linq<Class>().Where(x=>x.Id.Equals(comparerObject,StringComparison.InvariantCultureIgnoreCase))
Then what do you think is the expected behavior?
Thanks.
For LINQ to SQL, I'd expect that to fail at execution time - the query translator won't know what to do with your StringComparer<T> class.
In the case you've given, you should just use:
string idToMatch = comparerObject.Id;
return Session.Linq<Class>().Where(x => x.Id == idToMatch);
More complicated cases may or may not be feasible depending on exactly what you want to achieve.
With Linq, can I use a conditional statement inside of a Where extension method?
var query = someList.Where(a => (someCondition)? a == "something" : true);
so, if 'someCondition' is false, 'Where' will be skipped.
Yes you can like:
var query = someList.Where(a => a == "something");
if (condition)
{
query = query.Where(b => b == "something else");
}
var result = query.ToList();
Because Where is producing an IQueryable, the execution is deferred until the ToList in my example so you can chain Wheres together as much as you want and then just execute it after you have passed all your conditions.
Make use of WhereIf extenstion method avaialbe in linq
Example
if (SearchControlMain.PostingID.HasValue)
query = query.Where(q => q.PostingID == SearchControlMain.PostingID);
instead of above go for the below
query = query.WhereIf(SearchControlMain.CategoryID.HasValue, q => q.CategoryID == SearchControlMain.CategoryID);
LINQ WhereIf Extension Method
LINQ to SQL Where Clause Optional Criteria
Not sure if this is appropriate but it is quite useful, you can use ifs quite handily with conditional where clauses:
var r = (from p in productinfo.tblproduct
where p.Accountid == accountid
select p);
if (uuf1 != null)
r = r.Where(p => p.UnitUserField1 == uuf1);
if (uuf2!= null)
r = r.Where(p => p.UnitUserField2 == uuf2);
So the where clause will be amended according to what is in UUF1 or UUF2 i.e. you might have only UUF1 with info, in which case it will take that and ignore the UUF2 where clause, you might have both in which it will take both or you might not have anything in UUF1 or 2 and your where clause will just take the accountid as the where clause.
In my case there were two "conditional" where depending on search keys, so I did:
var query = db.Package.Include("SomeThing")
.Where(item => searchString1 == null || searchString1 == "" || item.Contains(searchString1))
.Where(item => searchString2 == null || searchString2 == "" || item.Contains(searchString2));
...
from item in items
where condition1
&& (condition2 ? true : condition3)
select item
This is how can you can do it with the noob Linq syntax.
This applies the condition3 only if condition2 is false.
If condition2 is true, you are essentially doing && true which has no effect on the where clause.
So it is essentially doing this:
if(condition2)
{
from item in items
where condition1
select item
else
{
from item in items
where condition1
&& condition3
select item
}
I had a scenario like this where I had to check for null within the list itself. This is what I did.
items = from p in items
where p.property1 != null //Add other if conditions
select p;
// Use items the way you would use inside the if condition
But as Kelsey pointed out this would work too -
items = items.Where(a => a.property1 != null);
I'm not sure what the question is, but a possible answer could be:
Yes,
list.Where(item => { if (Foo(item)) return true; else return false; });
It would be a complicated way of saying something simple, though.
In my case, I wanted to keep the elements which met my criteria and log the ones that didn't without iterating multiple times.
var merchantsWithLocations = allMerchants.Where(m =>
{
if (m.Locations?.Any() != true)
{
_logger.Log("Merchant {merchantId} has no locations", m.Id);
return false;
}
return true;
};
Any time you want to do a side-effect per element (such as logging), breaking out the lambda into a statement body makes it easy to reason about.