17

I'm trying to write a relational database application using Entity Framework 6. I have classes analogous to:

public class Subject
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Student> Students { get; set; }
}

public class Student
{
    public int ID { get; set; }
    public int SubjectID { get; set; }
    public string Name { get; set; }
    public virtual Subject Subject { get; set; }
}

(OK this is a bad example because in reality you'd want each student to be in more than one subject but let's ignore this for now as it was the best example that I could think of.)

The problem is that, whenever there's a subject with no students, instead of subjectInstance.Students returning an empty collection it instead returns null. This means that I cannot call subjectInstance.Students.Add(studentInstance) to add the first student. I instead have to add the student separately, by calling contextInstance.Students.Add(studentInstance) after manually setting the SubjectID field on studentInstance. Once there's one or more students already associated with the subject, subjectInstance.Students is no longer null and I can add further students in the expected way.

What I've already tried:

  • Removing virtual from public virtual ICollection<Student> Students { get; set; } - no change

  • Calling contextInstance.Entry(subjectInstance).Collection("Students").Load() before attempting to access the collection - works but it's messy and breaks separation of concerns (the modules that work with the data shouldn't have to concern themselves with loading the data)

  • Calling contextInstance.Subjects.Include("Students") at some point before creating subjectInstance - no change

micheal65536
  • 558
  • 3
  • 6
  • 26
  • You can initialize the collection property (inside the constructor or with initializer) to `new HashSet` or `new List`. – Ivan Stoev Sep 10 '17 at 13:32
  • 1
    You should **always** initialize collection navigation properties inside constructor. In your example you should do: `Students = new HashSet();` inside your `Subject` constructor (and, of course, keep the property as `virtual`). – Federico Dipuma Sep 10 '17 at 13:33
  • @IvanStoev I thought that interferes with lazy loading? I thought that Entity Framework needed to use its own subclass/implementation of `ICollection`. – micheal65536 Sep 10 '17 at 13:46
  • @FedericoDipuma I thought that interferes with lazy loading? I thought that Entity Framework was supposed to create its own subclass/implementation of `ICollection` (as it does when the database is non-empty), and that it wouldn't function correctly if you tried to assign your own instance. I also haven't seen this mentioned in any tutorials or examples anywhere, the constructor is always left blank and it's up to Entity Framework to create and assign stuff. – micheal65536 Sep 10 '17 at 13:47
  • Actually, no. As stated in [this](https://stackoverflow.com/a/20773057/7662085) thread, it you *should* initialize collections, but it is not strictly necessary. – stelioslogothetis Sep 10 '17 at 13:51
  • Not at all. Even the [official documentation](https://msdn.microsoft.com/en-us/library/jj713564(v=vs.113).aspx) reccomends it. Entity framework will fill `Students` property only if there is at least a student, else it will leave the property as is (null if you have not initialized it). – Federico Dipuma Sep 10 '17 at 13:52
  • I've added a constructor that assigns an empty list to each of those properties. Amazingly enough, it works, and existing data is still exposed through the collection. But it feels wrong to me, because my understanding is that Entity Framework creates its own `ICollection` that it can "fiddle with" in order to expose the data and track what's going on internally. I don't understand how it's possible for me to call `.Add()` on an `ICollection` that I've created and have Entity Framework detect this and add the data to the database. I'd appreciate an explanation of how this works. – micheal65536 Sep 10 '17 at 13:53
  • @FedericoDipuma Thanks for the link and clarification. I still don't understand exactly how this works internally but I guess if it works and the documentation demonstrates it in this way then it doesn't matter too much. If you can post this solution (of assigning an empty list inside the constructor) with the link to the official documentation then I'll accept it as an answer. – micheal65536 Sep 10 '17 at 13:57

1 Answers1

30

As the official documentation demonstrates, you should always initialize your collection navigation properties inside the entity constructor if you want to prevent a Null reference exception.

public Subject()
{
    Students = new HashSet<Student>(); // you may also use List<Student>, but HashSet will guarantee that you are not adding the same Student mistakenly twice
}

Entity framework will fill Students property (using a proxy) only if there is at least a student, else it will leave the property as is (null if you have not initialized it).

When the entity is not a proxy, then Entity Framework tracks its changes only when calling SaveChanges() on the context, using its original entity state for comparison. This answer will further clarify this behavior.

Federico Dipuma
  • 17,655
  • 4
  • 39
  • 56
  • Thanks, very helpful explanation of how Entity Framework detects changes made using my own instance and the difference between `List` and `HashSet` as it relates to this situation. – micheal65536 Sep 10 '17 at 14:08
  • Is this valid for Entity Framework Core? Also, if you initialize a virtual collection from the constructor you are faced with this: https://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor – petko May 31 '22 at 04:53
  • Took me a second to find where in the docs this was described, and it is literally a demonstration (as in you won't find "constructor" or "initialize" in a description on the page; the suggestion is limited to a line of sample code): `this.Courses = new HashSet();` Not a critique of the answer here, instead a signpost for whomever looks for it next so you know it's not an explicitly described best practice [and is limited to an implied, but very smart (imo ymmv), one]. – ruffin Jun 13 '22 at 18:24