linq skip count when count is zero - c#

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?

Related

Linq query to filter on most recent value / record

I have a 'complex' linq query I would like to improve and to understand.
(from x in tblOrder
orderby x.OrderNo
// where x.Filename is most recent filename for this order
group x by new { x.OrderNo, x.Color } into groupedByColorCode
select new
{
OrderNo = groupedByColorCode.Key.OrderNo,
ProductRef = groupedByColorCode.FirstOrDefault().ProductRef,
Color = groupedByColorCode.Key.Color,
Packing = groupedByColorCode.FirstOrDefault().Packing,
TotalQuantity = groupedByColorCode.Sum(bcc => bcc.OriQty).ToString()
}
x is an Order. I also would like to filter by Filename. Filename is a variable from tblOrder. Actually I would like to keep and keep only the orders from the most recent file.
What 'where' clause should I add to my linq query to be able to filter these last file name.
Thank you
First it's better to use orderby in the end of the query, because sorting will work quicker on the smaller set of data.
Second you should use where in the top of query, it will make smaller your set before grouping and sorting (set it after from line)
At last grouping creates dictionary with Key = new { x.OrderNo, x.Color } (in this keys) and Value = IEnumerable, and then groupedByColorCode becomes IEnumerabler of {Key, Value}. So it should stand in the end before orederby
there is MaxBy() or MinBy() if you need max or min by some criteria

C# LINQ .Contains returns empty?

For a school project I need to filter students who have signed up for multiple courses at the same timeblock. Instead of querying the DB via procedures/views I want to use LINQ to filter it in memory for learning purposes.
Everything seems alright according to the debugger however the result of my linq query is 0 and I can't figure out how.
Here's the code:
foreach (Timeblock tb in ctx.Timeblocks)
{
List<Student> doublestudents = new List<Student>();
//Get the schedules matching the timeblock.
Schedule[] schedules = (from sched in ctx.Schedules
where sched.Timeblock.Id == tb.Id
select sched).ToArray();
/\/\/\Gives me 2 schedules matching that timeblock.
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Contains(schedules[0].Course) && s.Courses.Contains(schedules[1].Course)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
While debugging it seems everything should work alright.
Each student has a List and each Course hsa a List
schedules[0].Course has Id 1
schedules[0].Course has Id 6
The student with Id 14 has both these courses in it's list.
Still the linq query does not return this student. Can this be because it's not the same reference of course it wont find a match at the .Contains()?
It's driving me totally crazy since every way I try this it wont return any results while there are matches...
You are comparing on Course which is a reference type. This means the objects are pointing to locations in memory rather than the actual values of the Course object itself, so you will never get a match because the courses of the student and the courses from the timeblock query are all held in different areas of memory.
You should instead use a value type for the comparison, like the course ID. Value types are the actual data itself so using something like int (for integer) will let the actual numerical values be compared. Two different int variables set to the same number will result in an equality.
You can also revise the comparison to accept any number of courses instead of just two so that it's much more flexible to use.
if (schedules.Count() > 1)
{
var scheduleCourseIds = schedules.Select(sch => sch.Course.Id).ToList();
doublestudents = (from s in ctx.Students
let studentCourseIds = s.Courses.Select(c => c.Id)
where !scheduleCourseIds.Except(studentCourseIds).Any()
select s).ToList();
Console.WriteLine(doublestudents.Count);
}
Some notes:
Compare the Course IDs (assuming these are unique and what you use to match them in the database) so that you're comparing value types and will get a match.
Use the let keyword in Linq to create temporary variables you can use in the query and make everything more readable.
Use the logic for one set containing all the elements of another set (found here) so you can have any number of duplicated courses to match against.
The problem is that your schedule[0].Course object and the s.Courses, from the new query, are completely different.
you may use the element's key to evaluate your equality condition/expression, as:
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Any(x=> x.Key == schedules[0].Course.Key) && s.Courses.Any(x=> x.Key == schedules[1].Course.Key)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
In order to achieve this you will need to include
using System.Linq
As you have guessed, this is probably related to reference equality. Here is a quick fix:
doublestudents =
(from s in ctx.Students
where s.Courses.Any(c => c.Id == schedules[0].Course.Id) &&
s.Courses.Any(c => c.Id == schedules[1].Course.Id)
select s).ToList();
Please note that I am assuming that the Course class has a property called Id which is the primary key. Replace it as needed.
Please note that this code assumes that there are two schedules. You need to work on the code to make it work for any number of schedules.
Another approach is to override the Equals and GetHashCode methods on the Course class so that objects of this type are compared based on their values (the values of their properties, possibly the ID property alone?).

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)

linq difference between two select count queries

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

How to get unique value with LINQ>SQL?

Using LINQ to SQL, how do I get the row with 1, 21? I'm looking for
SomeId==1
and
SecondId is a unique entry
SomeId SecondId
0 20
1 21
1 22
1 22
EDIT:
Ok, sorry. That wasn't clear. What I'm trying to do is generically find that row. There might be another entry that looks like this:
1 25
And that is the only 25. So I would get back two rows. Without referencing specific Ids, how do I find these two rows?
EDIT: Okay, it was really unclear what you meant before, but now I think I see what you mean, and you want something like:
var query = from row in table
where row.SomeId == targetId
group row by row.SecondId into g
where g.Count() == 1
select g.Single();
In other words:
Filter by SomeId first
Group by SecondId
Filter so that only groups with a single entry for that SecondId are returned
Select the sole entry from that group
There can be multiple such groups, of course - so you would get (1, 21) and (1, 25) in your example.
EDIT: If you saying you would like to find any combination of SomeId & SecondId where there are more than one row for that combination? then you could do the following:
var results = source.Where(x => x.SomeId == 1).GroupBy(x => x.SecondId).Where(g => g.Count > 1);
This will give you groups of results, and only return those that have more than one row. So in your example, you would get a group that returns 1,22...
If you are looking for the case where you only have rows in which there is a single entry in the table with that combination (the opposite of what I'm returning) you can change the comparison operator from '>' to '==' and another answer-er has also shown this possibility.

Categories

Resources