What determines what the Where in a Linq statement is able to parse?

72 views Asked by At

In the Where clause of a Linq statement, like this,

  var myClasses = (from b in db.MyRecords
                   join p in db.People on b.PersonId equals p.PersonId
                   join cse in db.Courses on b.CourseId equals cse.CourseId
                   where (b.Active == 1)
                   select new { b });

The expression b.Active==1 works fine. But if I do this,

Expression<Func<MyRecords, bool>> filter = my => my.Active == 1;

[Update: Because of Hans' answer I have kept this as the original, but in reality, I mistyped here. The Expression is actually using the underlying type, not the EF generated plural like the query Linq. I actually have this,

Expression<Func<MyRecord, bool>> filter = my => my.Active == 1;

]

And try this,

  var myClasses = (from b in db.MyRecords
                   join p in db.People on b.PersonId equals p.PersonId
                   join cse in db.Courses on b.CourseId equals cse.CourseId
                   where (filter)
                   select new { b });

The compiler complains,

Cannot convert query expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type

I have seen lots of SO question with this, but I do not understand why it will not work. I am misunderstanding fundamental, so I'm not really asking anyone to write the code. I want to know what Linq wants so I have a better understanding of Linq, not just making something work. This works, for example,

  var myClasses = (from b in db.MyRecords.Where(filter)
                   join p in db.People on b.PersonId equals p.PersonId
                   join cse in db.Courses on b.CourseId equals cse.CourseId
                   select new { b });

I want to know why it works there and not in the Where after the joins. If I do the Where at the end of the joins, the compiler is still aware of the MyRecords fields, including Active.

I think this is what I'm after for a proper description, as it seems to fit my probably pretty well.

http://www.albahari.com/nutshell/linqkit.aspx

1

There are 1 answers

1
Rafal On

Your filter is just of wrong type to be applied in the first example. The blue linq syntax does a lot of magic for you to make this query easy to read. What actually happens under the hood is creation of some anonymous types that contain references to your items. Note that in that where clause you can use p and cse variables as well. And when you factor that to your expression you might figure it to be like this Expression<Func<Tuple<MyRecords, People, Courses>> in reality it will not be a tuple but some anonymous type.

When you ask resharper to convert blue syntax to method chain it generates it like this:

(db.MyRecords.Join(
     db.People, 
     b => b.PersonId,
     p => p.PersonId,
     (b, p) => new {b, p})
  .Join(
       db.Courses,
       t => t.b.CourseId, 
       cse => cse.CourseId,
       (t, cse) => new {t, cse})
   .Where(t => (t.t.b.Active == 1))
   .Select(t => new {t.t.b}));

And actually you cannot use the filter variable in the blue syntax like you did:

... join cse in db.Courses on b.CourseId equals cse.CourseId
               where (filter)

You would have to switch to method call:

(from b in db.MyRecords
                join p in db.People on b.PersonId equals p.PersonId
                join cse in db.Courses on b.CourseId equals cse.CourseId
                select b)
            .Where(filter)

now just because we trimmed the inner query to just b you can apply you filter.