linq difference between two select count queries - c#

I'm trying to learn about linq queries. I have a list _taskDetail which contains 8 elements. I do not understand why the first query below returns the answer 8? Yes the list contains 8 elements but all the td.Names are different and I have specified td.Name == taskName so why is it returning everything even elements where the td.Name does not equal taskName?
The second query gives me the expected and correct answer of 1.
var Ans1 = _taskDetail.Select(td => td.Name == taskName).Count();
var Ans2 = (from tdList in _taskDetail
where tdList.Name == taskName
select tdList).Count();
Ans1 = 8
Ans2 = 1

The first version doesn't make sense, you need a Where, and not a Select which is a projection, not a filter.
The Select, in the first version, will return you an IEnumerable<bool> (for each item in the list, it will return true if Name == taskName and false if not. So all items will be returned, and the count will give you... the count of the list.
so
_taskDetail.Where(td => td.Name == taskName).Count();
By the way, you can simply do
_taskDetail.Count(td => td.Name == taskName);
Detail to maybe understand better
The second (wrong) version (query syntax) corresponding to your actual (wrong) first version (method syntax) would be
(from tdList in _taskDetail
select tdList.Name == taskName).Count();

It's because the first query is wrong. Select only produces a projection, it does NOT filter the results. The correct way to execute is using Where instead of Select...
var Ans1 = _taskDetail.Where(td => td.Name == taskName).Count();

Related

Linq IN Clause in Where

I want to know how to use IN clause in Linq. Here is my Code :-
int empCount = ctx.tblEmpTran
.Count(
e => e.Id == Id &&
e.Month == selectedMonth &&
e.Year == selectedYear &&
e.employeeId.contains()
);
The Following query is supposed to be in IN
SELECT A.Id FROM dbo.EmployeeDetail A WHERE A.CompanyId = 1 AND A.COLS > 0
In the above code, contains method do not popup in intellisense.
This is because you are trying to convert from SQL to Linq, and you couldn't try a worse approach.
You should try to write your LINQ query starting from what you need it to do, forgetting SQL altogether.
In Linq there is no "IN" operator, to achieve the same thing you take your collection and check if it Contains the value.
So in your scenario you should simply generate your collection of valid values and then in your query do:
myCollection.Contains(e.employeeId)
It is "collection CONTAINS value" the logic, not "value is found IN collection". Again if you insist to start from SQL when using Linq you will always incur in this kind of problems.
Check Albahari tutorial on how to approach Linq correctly and your productivity will skyrocket.
You should create a collection of employee IDs that you want to check, and the code would be
employees.contains(e.employeeId)
Instead of this e.employeeId.contains(), you should use this:
listOfIds.Contains(e.employeeId)
where listOfIds would be a list of int, List<int> and would contain the ids you would place between the parentheses after IN(...).
considering that you have a tblEmployeeDetail in the same DbSet and them having a relation through employeeId you can write you query like.
var q = from e in ctx.tblEmployeeDetail where e.Transactions.Any(t => t.Month == selectedMonth &&
t.Year == selectedYear);
int empCount = q.Count();
this is pseudo-code but this is how you would use the LINQ effectively, (Exists is better than In check)

Problem with linq query

I have a self referencing table "Product" with the following structure (where D = Draft and A = Approved)
ID ParentID Status Name
---------------------------
1 NULL A Foo
2 1 A Foo2
3 NULL D Bar
4 1 D Foo3
A row can either be "new" (where ParentID == null) or can be a version of an existing row. So we can see from the table that there are 3 versions for the item "Foo" and only 1 for "Bar".
I need a way of returning the latest versions of each item based on whether the user is able to see only "Approved" items or is able to see "Draft" as well. So for example
Users who can see "D" would have:
3 NULL D
4 1 D
The "latest" row for "Foo" and "Bar".
Users who can see "A" would have:
2 1 A
ie. only the "Approved" versions.
Thanks in advance,
Jose
Here is the Linq query that should work for you:
bool hasDraftAccess = false;
var query = DataContext.Records.AsQueryable();
if (!hasDraftAccess) {
query = query.Where(r => r.Status == 'A');
}
var seriesQuery = query.Select(r => new { Record = r, SeriesID = r.ParentID ?? r.ID });
var latestQuery = seriesQuery.GroupBy(s => s.SeriesID).Select(g => g.OrderByDescending(s => s.Record.ID).First());
var resultsQuery = latestQuery.Select(s => s.Record);
var results = resultsQuery.ToArray();
Here's what's happening:
First, add a WHERE clause to filter out draft records if the user doesn't have access to them
Then add a pseudo column called 'SeriesID' that groups all the related versions into that one column. That is, make it easy to group parent and related children.
Group the related records and then pick whichever record is most recent
Select the Linq Entity from the anonymous type so that it is updatable
I should note that if you have the ability to change your data schema you should consider adding a column called InsertDate or something to that effect. Right now I am assuming that whatever record has the highest ID is the latest. It is often better to add a DateTime field and sort on that instead.
I apologize that this isn't actually using Linq syntax--I prefer fluent coding styles--but it could be easily translated to Linq syntax if you preferred it.
Totally untested - but something like this might work, if I've understood the question correctly.
Can see approved
context.Table.Where(p => p.Status == "A")
Can see approved and draft
context.Table.Where(p => p.Status == "D" || (p.Status == "A" && !context.Table.Any(q => q.Status == "D" && q.Parent == p.Parent)))

linq skip count when count is zero

I'm doing a linq-to-sql query and I wish the LastOrDefault operator were available but it's not. So, I'm writing the query like this:
TheUserNote = ((from note in MyDC.UserNotes
where note.UserID == TheUserID
select note.NoteText).Skip(
(from n in MyDC.UserNotes
where n.UserID == TheUserID
select n.NoteID).Count() - 1)).SingleOrDefault(),
Basically, I want to use Skip and Count to get to the last item: count how many items there are, substract 1, and skip for that number.
It's not working and I'm looking to fix it. The problem is that sometimes Count can be 0 so I get an error saying parameters are not valid since in that case Count will be -1.
Any suggestions?
Thanks.
You might try .Reverse().FirstOrDefault(), or if you have a date column or primary key column try .OrderByDescending(...).FirstOrDefault().
Using your variable names and comment:
var TheUserNote
= MyDC
.UserNotes
.Where(x => x.UserId == TheUserID)
.OrderByDescending(x => x.NoteDateTime)
.FirstOrDefault()
;
Is there a specific order they are coming back in? I assume so otherwise why would it matter if it was the last or the first one you got?
Can't you just order by whatever in the opposite direction and use FirstOrDefault?

LINQ query to select top five

I have a LINQ query:
var list = from t in ctn.Items
where t.DeliverySelection == true && t.Delivery.SentForDelivery == null
orderby t.Delivery.SubmissionDate
select t;
How can I modify this query to select just five results from the database?
var list = (from t in ctn.Items
where t.DeliverySelection == true && t.Delivery.SentForDelivery == null
orderby t.Delivery.SubmissionDate
select t).Take(5);
The solution:
var list = (from t in ctn.Items
where t.DeliverySelection == true && t.Delivery.SentForDelivery == null
orderby t.Delivery.SubmissionDate
select t).Take(5);
This can also be achieved using the Lambda based approach of Linq;
var list = ctn.Items
.Where(t=> t.DeliverySelection == true && t.Delivery.SentForDelivery == null)
.OrderBy(t => t.Delivery.SubmissionDate)
.Take(5);
[Offering a somewhat more descriptive answer than the answer provided by #Ajni.]
This can also be achieved using LINQ fluent syntax:
var list = ctn.Items
.Where(t=> t.DeliverySelection == true && t.Delivery.SentForDelivery == null)
.OrderBy(t => t.Delivery.SubmissionDate)
.Take(5);
Note that each method (Where, OrderBy, Take) that appears in this LINQ statement takes a lambda expression as an argument. Also note that the documentation for Enumerable.Take begins with:
Returns a specified number of contiguous elements from the start of a
sequence.
Additional information
Sometimes it is necessary to bind a model into a view models and give a type conversion error. In this situation you should use ToList() method.
var list = (from t in ctn.Items
where t.DeliverySelection == true && t.Delivery.SentForDelivery == null
orderby t.Delivery.SubmissionDate
select t).Take(5).ToList();
Just thinking you might be feel unfamiliar of the sequence From->Where->Select, as in sql script, it is like Select->From->Where.
But you may not know that inside Sql Engine, it is also parse in the sequence of
'From->Where->Select', To validate it, you can try a simple script
select id as i from table where i=3
and it will not work, the reason is engine will parse Where before Select, so it won't know alias i in the where. To make this work, you can try
select * from (select id as i from table) as t where i = 3

Return Top X Results From Linq To Objects Query in Order

PREVIOUSLY: In this question someone told me how to use CompareTo to return surnames within a particular range ordered alphabetically using LINQ to Objects.
The rest of my question, which seems to have missed the initial question asking feeding frenzy, actually arose after I tested this solution. In the previous eaxample I had a list of names:
Adams
Bentham
Bickford
Gillies
Kelly
Moore
Peters
Rutherford
Smith
Taylor
Williams
And I wanted to be able to query them for all the names between Gillies and Moore for example and get:
Gillies
Kelly
Moore
This is all well and good if you want every single solitary name inbetween the goalposts returned no matter what. The problem comes in when you have a huge quantity of surnames and you want a maximum of four of the names between Gillies and Taylor returned in alphabetical order, for example.
So the desired output is:
Gillies
Kelly
Moore
Peters
However just returning four results between Gillies and Taylor could return Kelly, Peters, Smith and Taylor, or Gillies, Moore, Rutherford and Smith. Basically, the query takes you at your word and just selects any old four between the goalposts.
So, how can I get the top 4 results alphabetically. I could write a second query of course and return a subset and then select from within that... but shouldn't there be a way to integrate this behaviour into the initial query?
I've tried a couple of things with OrderBy and so far they just don't work. So it's over to you guys.
EDIT: for those two of you who've suggested using "take" I already am using take. It doesn't "take" in order, even if you use OrderBy or at least it doesn't in my query. Here it is:
var allIDs = (from cust in dc.orders
join item in dc.order_items on cust.orderid equals item.orderid
join del in dc.deliveries on cust.deliveryid equals del.deliveryid
join dt in dc.deliverytypes on del.deliverytype equals dt.deliverytypeid
where eventcode == item.itemcode
&& dt.description == "Secure Post"
&& (cust.status == "OK" || cust.status == "PB")
&& cust.surname.CompareTo(surfrom ?? " ") >= 0
&& cust.surname.CompareTo(surto ?? "zzz") <= 0
&& (cust.trackingcode == null ? false : (bool)cust.trackingcode)==false
orderby cust.surname, cust.initials, cust.ordercode
select cust.orderid).Distinct().Take(ordermax);
That just returns four names from between the names you've selected, not a specific four names.
From your edit it looks like you're doing the orderby and distinct in a strange order:
This works for me (where "allMyNames" is just a List<string>).
var ofInterest = allMyNames
.Distinct()
.Where(x => x.CompareTo(from) >= 0 && x.CompareTo(to) <= 0)
.OrderBy(x => x)
.Take(4);
I'm much happier using the extension form of LINQ :)
Use the "Take" LINQ method to take the first 4 records:
var query = (from name in originalList
where name.CompareTo(fromName) >= 0 && name.CompareTo(toName) <= 0
orderby name
select name).Take(4);
GetNames(...).Take(4);
Will take the first four items in the enumerable.

Categories

Resources