0

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...

enter image description here

Noel Heesen
  • 223
  • 1
  • 4
  • 14

3 Answers3

1

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:

  1. 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.
  2. Use the let keyword in Linq to create temporary variables you can use in the query and make everything more readable.
  3. 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.
Community
  • 1
  • 1
Mani Gandham
  • 7,688
  • 1
  • 51
  • 60
0

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?).

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Thanks! They were both different objects indeed. It feels like very bad practice to override the Equals or GetHashCode for this purpose. .Any does the trick :) – Noel Heesen Mar 13 '16 at 16:33
  • You are welcome. You are right, I wouldn't have overridden these methods for this case too. – Yacoub Massad Mar 13 '16 at 17:16
0

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
Richard
  • 128
  • 2
  • 3
  • 10