Can linq predicates be combined here? - linq

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);
}

Related

LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[System.Int32]

I have the following error:
LINQ to Entities does not recognize the method
'System.Collections.Generic.List`1 [System.Int32] get_st_past_enrollment_success()'
method, and this method cannot be translated into a store expression.
This is caused by the following linq
IEnumerable<subject> _subjects = (from subject in context.subjects
where
subject.enrollments.Count() < subject.sj_max_enrollment
&& subject.sj_availability == true
&& !this.get_st_past_enrollment_success().Contains(subject.sj_subject_id)
select subject);
get_st_past_enrollment_success() returns a List:
public List<int> get_st_past_enrollment_success()
{
return this.enrollments.Where(e => e.em_enrolled == false && e.em_result >= 50).Select(e => e.em_subject_id).ToList();
}
What am i doing wrong here?
Your query itself contains the method call - and Entity Framework doesn't know what to do with that. Try extracting the list fetch to before the query:
var enrollments = get_st_past_enrollment_success();
var _subjects = from subject in context.subjects
where subject.enrollments.Count() < subject.sj_max_enrollment
&& subject.sj_availability
&& !enrollments.Contains(subject.sj_subject_id)
select subject;
Also note that get_st_past_enrollment_success violates .NET naming conventions - that won't affect whether the code works, but it'll look odd to other developers who are used to the normal conventions.

Is there a way to improve this LINQ?

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.

Reversing IQueryable based on passed property for sorting logic

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();

Convert an ordinary LINQ query to lambda expression

from _tupleRows in this.TupleSet
from _member in _tupleRows.Members
where (_member.HasChildMembers && !_member.DrilledDown)
select new
{
_member1 = _member,
_member2 = (from _searched in this.TupleSet
from _compareMember in _searched.Members
where (_member.UniqueName == _compareMember.UniqueName &&
_member.LevelDepth == _compareMember.LevelDepth &&
_compareMember.DrilledDown)
select _compareMember).FirstOrDefault()
};
I need to convert this simple LINQ expression to an equivalent lambda expression
from _tupleRows in this.TupleSet
from _member in _tupleRows.Members
where (_member.HasChildMembers && !_member.DrilledDown)
select new
{
_member1 = _member,
_member2 = (from _searched in this.TupleSet
from _compareMember in _searched.Members
where (_member.UniqueName == _compareMember.UniqueName
&& _member.LevelDepth == _compareMember.LevelDepth
&& _compareMember.DrilledDown)
select _compareMember).FirstOrDefault()
};
becomes:
this.TupleSet
.SelectMany(tupleRows =>
tupleRows.Members
.Where(member => member.HasChildMembers && !member.DrilledDown)
.Select(member => new
{
_member1 = member,
_member2 = this.TupleSet
.SelectMany(searched =>
searched.Members
.Where(compareMember =>
member.UniqueName == compareMember.UniqueName
&& member.LevelDepth == compareMember.LevelDepth
&& compareMember.DrilledDown))
.FirstOrDefault(),
}));
They're really not hard to convert. Just start at the top and append as you go.
Something like:
this.TupleSet.SelectMany(ts => ts.Members)
.Where(m => m.HasChildMembers && !m.DrilledDown)
.Select(m => new
{
_member1 = m,
_member2 = this.TupleSet.SelectMany(ts => ts.Members)
.Where(other => m.UniqueName == other.UniqueName
&& m.LevelDepth == other.LevelDepth
&& other.DrilledDown
)
.FirstOrDefault()
});
No guarantees this is perfect or will even compile, but then I'm not trying to do your work for you, I'm trying to give you a demonstration of how you might do these conversions yourself.
Also, do yourself a favor and use simpler identifiers in lambdas when you can. They're very limited scope, and you're chaining calls to make one big statement, so often it will usually be very obvious in context what a variable means. I may not have the best example here, but the original variable names were way too verbose, IMO.
The nested query also seems a little strange. If there is a way to structure your data so you don't have to do that nested query I think you'll get better perf and simpler queries.

Linq queries on dB and using custom Comparers

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.

Resources