I can not make the correct linq request - c#

I form a request, it works.
var query = db.Persons.Where(p => p.Date == "27.02.2020").Where(p => p.Country == "USA");
But since I will need to use it in if-else
I changed it a little and now he just selects the last Where.
How can this problem be solved?
var query = db.Persons;
query.Where(p => p.Date == "27.02.2020");
query.Where(p => p.Country == "USA");

The Where method, like all LINQ methods, does not modify the source enumerable in-place, but instead returns a new enumerable that has the operation, or in this case filter, applied to it.
That means you need to assign the result of each operation back into your variable for it to persist. The following should be equivalent to your original snippet:
IQueryable<Person> query = db.Persons;
query = query.Where(p => p.Date == "27.02.2020");
query = query.Where(p => p.Country == "USA");
As pointed out in the comments, the query variable type now needs to be compatible with both the type of db.Persons and the return type of Where.

Related

Retrieve the item from the list

I am trying to get the list of Courses from the courseData list item.
However, the following returns me boolean.
var course = courseData.Courses.Select(x => x.Entities.Select(a => a.courseId == courseDto.Id)).FirstOrDefault();
Currently, course is of type IEnumerable<bool> because in your inner Select clause you're projecting from some type a and returning a.courseId == courseDto.Id (bool) which results in an IEnumerable<bool> after the provided lambda is executed for each element of x.Entities.
Once the execution of the outer Select clause is performed this then results in having a type IEnumerable<IEnumerable<bool>> which when you then call FirstOrDefault() upon; simply results in retrieving the first IEnumerable<bool> element.
Now, you're most likely looking for the Where clause so that you can retain all the elements that pass the predicate a => a.courseId == courseDto.Id. We will then flatten the IEnumerable<IEnumerable<Entity>> into IEnumerable<Entity> and then collect to a list or retrieve the first element if exists else the default for reference types (null).
To retrieve a list of courses.
var course =
courseData.Courses
.SelectMany(x => x.Entities.Where(a => a.courseId == courseDto.Id))
.ToList();
To retrieve the first item from the list of courses.
var course =
courseData.Courses
.SelectMany(x => x.Entities.Where(a => a.courseId == courseDto.Id))
.FirstOrDefault();
var Result = Course_Object.Where(u => u.Id == 1).FirstOrDefault();

Why AsEnumerable also execute filter on server

I'm using Linq to Entities in my program, and i have the following two simple queries:
var result = dbContext.Customers.Where(c => c.Name == "Mostafa").ToList();
var result2 = dbContext.Customers.Where(c => c.Name == "Mostafa").AsEnumerable().ToList();
when i run the SQL Profiler, i found that the generated SQL Query is the same for both of the queries !
the questions is why in the second query, although i'm using asEnumerable, executes the filtering on the server side ?
Update:
The filtering was applied on memory instead of the server when changing the second query to be like this:
var result2 = dbContext.Customers.AsEnumerable().Where(c => c.Name == "Mostafa").ToList();
Thanks to #Willem Van Onsem
AsEnumerable() makes the remainder of the query execute locally. Anything earlier than the AsEnumerable() is still part of the IQueryable execution flow. For example, think about this (imagining an Age property):
var result = dbContext.Customers
.Where(c => c.Name == "Mostafa")
.Where(c => c.Age == 18)
.ToList();
That would result in SQL which filtered by name and age. Compare that with this:
var result = dbContext.Customers
.Where(c => c.Name == "Mostafa")
.AsEnumerable()
.Where(c => c.Age == 18)
.ToList();
That would filter by name in the SQL, but it would filter by age locally (in memory).

Fluent nHibernate Query => QueryOver, Join, Distinct

I try to change that query to QueryOver<> to be able to do the Distinct operation yet inside the (generated sql) query
var result = (from x in Session.Query<Events>()
join o in Session.Query<Receivers>() on x.ID equals o.ID
where x.Owner.ID == 1 //the user is the owner of that Event (not null)
||
x.EVType.ID == 123 //(not null)
||
x.Receivers.Count(y => y.User.ID == 1) > 0 //the user is one of the Event Receivers
select x.StartDate)
.Distinct();
I tried something like that
Events x = null;
List<Receivers> t = null;
var result = Session.QueryOver<Events>(() => x)
.JoinAlias(() => x.Receivers, () => t)
.Where(() => x.Owner.ID == 1
||
x.EVType.ID == 123
||
t.Count(y => y.User.ID == 1) > 0)
.TransformUsing(Transformers.DistinctRootEntity)
.Select(a => a.StartDate)
.List();
but then I got the Value can not be null. Parameter name: source exception. Any ideas how can I fix that query ?
edit
thanks to the xanatos' answer, the final SQL query is correct (I used his 2nd approach):
SELECT distinct this_.StartDate as y0_
FROM Events this_
WHERE
(
this_.UserID = ?
or
this_.EventTypeID = ?
or
exists (SELECT this_0_.ID as y0_
FROM Receivers this_0_
WHERE this_0_.UserID = ?)
)
"In QueryOver, aliases are assigned using an empty variable. The variable can be declared anywhere (but should be empty/default at runtime). The compiler can then check the syntax against the variable is used correctly, but at runtime the variable is not evaluated (it's just used as a placeholder for the alias)." http://nhibernate.info/blog/2009/12/17/queryover-in-nh-3-0.html
Setting List<Receivers> t to empty collection as you did (as you have mentioned in comments) means that you check is event id in local empty collection - doesn't have sense at all.
You can try do your query with subquery (should work but i'm not sure, I wrote it without testing, "by hand"):
Receivers receiversSubQueryAlias = null;
var subquery = session.QueryOver<Events>()
.JoinQueryOver<Receivers>(x => x.Receivers, () => receiversSubqueryAlias, JoinType.Inner)
.Where(()=> receiversSubQueryAlias.UserId == 1)
.Select(x => x.Id)
.TransformUsing(Transformers.DistinctRootEntity);
Events eventsAlias = null;
var mainQueryResults = session.QueryOver<Events>(() => eventsAilas)
.Where(Restrictions.Disjunction()
.Add(() => eventAlias.OwnerId == 1)
.Add(() => eventAlias.EVType.Id == 123)
.Add(Subqueries.WhereProperty<Events>(() => eventAlias.Id).In(subquery))
).Select(x => x.StartDate)
.TransformUsing(Transformers.DistinctRootEntity)
.List();
As written by #fex, you can't simply do a new List<Receivers>. The problem is that you can't mix QueryOver with "LINQ" (the t.Count(...) part). The QueryOver "parser" tries to execute "locally" the t.Count(...) instead of executing it in SQL.
As written by someone else, TransformUsing(Transformers.DistinctRootEntity) is client-side. If you want to do a DISTINCT server-side you have to use Projections.Distinct .
You have to make an explicit subquery. Here there are two variants of the query. the first one is more similar to the LINQ query, the second one doesn't use the Count but uses the Exist (in LINQ you could have done the same by changing the Count(...) > 0 with a Any(...)
Note that when you use a .Select() you normally have to explicitly tell the NHibernate the type of the .List<something>()
Events x = null;
Receivers t = null;
// Similar to LINQ, with COUNT
var subquery2 = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.ToRowCountQuery();
var result2 = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereValue(0).Lt(subquery2))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
// With EXIST
var subquery = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.Select(t1 => t1.ID);
var result = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereExists(subquery))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
Note that you'll have to set "manually" the JOIN condition in the subquery.
Hopefully this answer can help others. This error was being caused by declaring
List<Receivers> t = null;
followed by the query expression
t.Count(y => y.User.ID == 1) > 0
The QueryOver documentation states "The variable can be declared anywhere (but should be empty/default at runtime)." Since in this case, the place holder is a List, you must initialize it as an empty list.
List<Receivers> t = new List<Receivers>();
Otherwise, when you try to reference the Count method, or any other method on the placeholder object, the source (t) will be null.
This however still leaves a problem as #fex and #xanatos, in which it makes no sense to reference Count() from the alias List t, as it won't convert into SQL. Instead you should be creating a subquery. See their answers for more comprehensive answer.

Understanding the processing pipeline

Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
It works fine. Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
//.Where(p => p.ProcessName.Length < 9);
query = query.Where(p => p.ProcessName.Length < 9);
This does not work. I do not understand why the first method works. In my mind these queries are the same. ThenByDescending returns IOrderedEnumerable<T> which is piped into Where(). The first method should not work because Where only works with IEnumerable<T>. Alas...it does work.
How does this processing pipeline function?
The difference is because of a misunderstanding of the var keyword and LINQ queries.
var (C# reference)
Using the var keyword is the same as specifying the same type as the right side of the assignment. It does not mean that you can assign any type to the variable.
In LINQ queries, most of the basic expressions return an IEnumerable, but it many cases, they do not return just an IEnumerable. Instead, they return an type that inherits from IEnumerable.
In this case, you are doing the equivalent of this:
IEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
and
IOrderedEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
// Won't work because Where doesn't return an IOrderedEnumerable.
query = query.Where(p => p.ProcessName.Length < 9);
The reason that the first snippet works is because IOrderedEnumerable inherits from IEnumerable, so you can use it as such.
To fix the problem in the second example, you need to explicitly declare query as IEnumerable<Process>.

Linq-To-SQL Marc Gravell's InRange Extension

I'm tring to use the InRange extension Marc Gravell wrote here:
LINQ Expression to return Property value?
This is my code so far:
// Fetch list of visit Ids and distinct Ips that fall into the date range
var q = (from c in db.tblTrackerVisits where c.Date >= MinDate select new { c.ID, c.IPID });
List<int> VisitIDs = q.Select(c => c.ID).ToList();
List<int> DistinctIPs = q.Select(c => c.IPID).Distinct().ToList();
// List of all campaigns that have visitors
var Campaigns = db.tblTrackerVariables
.Where(c =>
c.TypeID == Settings.CampaignTrackerVariableTypeID
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
&& db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs)
)
.Select(c => new { c.ID, c.Name }).OrderBy(c=>c.Name);
However this throws:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<tblTrackerVisitVariable>' to 'bool'
I'm not sure if I'm using it correctly, can anyone give me some pointers? The commented out && above it:
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
Is the old working code (but it throws the too many params error so I have to resort to this extension method).
Mark's extension method returns an IEnumerable, not a bool, and therefore cannot be included in a logical expression. I you want to check if the result is non-empty try the Any method:
db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs).Any()

Categories

Resources