60

I have the following tables

  1. ClassRoom (ClassID,ClassName)
  2. StudentClass (StudentID,ClassID)
  3. Student (StudentID,StudentName,Etc..)
  4. StudentDescription. (StudentDescriptionID,StudentID,StudentDescription)

I want to retrieve all the information on student==1

In sql I would do something like BELOW and get all the info about a student.

 select * from Student s
 join StudentClass sc on s.StudentID=sc.StudentID
 join ClassRoom c on sc.ClassID=c.ClassID
 left join StudentDescription sd on s.StudentID=sd.StudentID
 where s.StudentID=14

Now my problem.Using EF4 I did something like this but cannot make it work. Also can you do an include and a left join

Attempt 1

private static StudentDto LoadStudent(int studentId)
    {
        StudentDto studentDto = null;
        using (var ctx = new TrainingContext())
        {
            var query = ctx.Students
                .Include("ClassRooms")
                .Include("StudentDescriptions")
                .Where(x=>x.StudentID==studentId)
                .SingleOrDefault();

            studentDto = new StudentDto();
            studentDto.StudentId = query.StudentID;
            studentDto.StudentName = query.StudentName;
            studentDto.StudentDescription = ??

        }

        return studentDto;
    }

Attempt 2 again incomplete and wrong

using (var ctx = new TrainingContext())
         {
             var query = (from s in ctx.Students
                             .Include("ClassRooms")
                         join sd in ctx.StudentDescriptions on s.StudentID equals sd.StudentID into g
                         from stuDesc in g.DefaultIfEmpty()
                         select new
                                    {
                                        Name=s.StudentName,
                                        StudentId=s.StudentID,

         }).SingleOrDefault();

As you can see I dont know what I am doing here. How can I convert that Sql into a EF Query?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
user9969
  • 15,632
  • 39
  • 107
  • 175

5 Answers5

49

Yes, it is possible.

Firstly, .Include does a LEFT OUTER JOIN, using the navigational property you pass through.

This is how you would explicitly do a LEFT JOIN between Student and StudentDescription:

var query = from s in ctx.Students
            from sd in s.StudentDescriptions.DefaultIfEmpty()
            select new { StudentName = s.Name, StudentDescription = sd.Description };

As you can see, it's performing the JOIN based on the entity association between Students and StudentDescriptions. In your EF model, you should have a navigational property called StudentDescriptions on your Student entity. The above code is simply using that to perform the join, and defaulting if empty.

The code is basically identical to .Include.

Please don't get confused with LEFT JOIN vs LEFT OUTER JOIN.

They are the same thing.

The "OUTER" keyword is optional, i believe it is there for ANSI-92 compatability.

Just .Include everything you need in your query:

using (var ctx = new TrainingContext())
        {
            studentDo = ctx.Students
                .Include("ClassRooms")
                .Include("StudentDescriptions")
                .Where(x=>x.StudentID==studentId)
                .Select(x => new StudentDto
                        {
                            StudentId = x.StudentId,
                            StudentName = x.StudentName
                            StudentDescription = x.StudentDescription.Description
                        })
                .SingleOrDefault();
        }

Basically, make sure all your FK's are expressed as navigational properties on your model, then if so, you don't need to do any joins. Any relationships you require can be done with .Include.

RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • 1
    thanks for your time and reply.The Include example works!! however the first one produces a cross join.I might be totally wrong but when you do 2 froms doesnt it produces a cross join? – user9969 Nov 29 '10 at 14:39
  • 2
    I did not understand this statement: "Firstly, .Include does a LEFT OUTER JOIN,". As far as I can tell .Include performs an INNER JOIN. – Dror Jul 07 '14 at 21:30
  • No, ``.Include`` does an OUTER JOIN. If you include a property that happens to be empty (not there), the original one is still returned. – Jacek Gorgoń Nov 04 '14 at 13:52
  • 28
    Well, the real answer is it depends. -It depends on the nullability of the included link. If nullable then left join, if not nullable then inner join. – larsts Dec 09 '14 at 12:05
  • 27
    This answer is straight up wrong as is and needs to be updated. @larsts is correct. If the navigation property's key is nullable and the entity mapping uses WithOptional, then the query EF uses will use a LEFT JOIN. If navigation property's key is not nullable or the entity mapping uses WithRequired then it uses INNER JOIN. – Rhyous Sep 14 '16 at 22:04
  • It also does an INNER JOIN if you mark the FK property with the [Required] attribute (this only matters if it is NULL in the database, when there is a mismatch between the DB schema and the entity class). – Eduardo Hernández Jul 02 '19 at 10:13
  • Thanks @Rhyous. The answer wasn't making sense as I could clearly see my INCLUDE was generating an INNER join. I'm guessing the database is incorrectly defined, which doesn't surprise me in the least... – Auspex Oct 15 '20 at 12:55
39

I just had this problem, in my case it was the EntityTypeConfiguration that was wrong

I had:

   HasRequired(s => s.ClassRoom)
                .WithMany()
                .HasForeignKey(student => student.ClassRoomId);

Instead of:

   HasOptional(s => s.ClassRoom)
                .WithMany()
                .HasForeignKey(student => student.ClassRoomId);

It seems HasRequired makes a INNER JOIN while HasOptional makes a LEFT JOIN.

jBelanger
  • 1,526
  • 18
  • 11
  • 4
    It also does an INNER JOIN if you mark the FK property with the [Required] attribute (this only matters if it is NULL in the database, when there is a mismatch between the DB schema and the entity class). – Eduardo Hernández Jul 02 '19 at 10:12
20

Exactly:

  1. If StudentDescription.StudentId is nullable -> EF performs a LEFT JOIN, i.e. select * from Student s LEFT JOIN StudentDescription sd on s.StudentID=sd.StudentID.
  2. Otherwise EF does INNER JOIN.
Dummy00001
  • 16,630
  • 5
  • 41
  • 63
Lapenkov Vladimir
  • 3,066
  • 5
  • 26
  • 37
  • 1
    You should set the foreign key as nullable on both the database table and the entity class. – d.i.joe Apr 19 '20 at 16:22
  • This answer isn't true. EF always generates outer joins when including *collections* (which, lacking the classes code, we have to assume `StudentDescriptions` is). – Gert Arnold Jul 17 '21 at 17:30
7

The behavior of .Include:

  • The property is requeired: Always translates to INNER JOIN;
  • The type of foreign key is not nullable: translates to INNER JOIN in default, but you can add .IsRequired(false) with .HasForeignKey to turn it to be LEFT OUT JOIN.
  • The type of property is collection will always translates to LEFT OUT JOIN.
0

If you want a 1 to 1 relation, you can simply map your foreign Id and make it nullable.

public int? MyForeignClassId { get; set; }
public MyForeignClass MyForeignClass { get; set; }
  • This doesn't make sense. A FK is a 1:n relationship by default. Making it 1:1 requires more than this. Also, OP doesn't want a 1:1 relationship. – Gert Arnold Jul 16 '21 at 06:33