Noel Heesen
Noel Heesen

Reputation: 243

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

enter image description here

Upvotes: 0

Views: 901

Answers (3)

Mani Gandham
Mani Gandham

Reputation: 8292

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.

Upvotes: 1

Yacoub Massad
Yacoub Massad

Reputation: 27861

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

Upvotes: 0

Richard
Richard

Reputation: 136

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

Upvotes: 0

Related Questions