Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framwork 5: Why is my entity's collection property empty?

I'm currently learning the Entity Framework (EF) through the Code First approach. I want to model a simple one-to-many relationship between Professors and Classes on a newly created database (i.e. created by the EF).

In my model a Professor has a collection of Classes. A Class always has one Professor associated with it (i.e. the corresponding property is never null). I can create and store a Class and an associated Professor, but when I access the Classes property of a Professor retrieved from the database, it is empty. Navigating from a Class to its Professor, however, is possible.

Consider the following model:

class Class
{
    public int Id { get; set; }
    public string Title { get; set; }
    public virtual Professor Professor { get; set; }
}

class Professor
{
    public int Id { get; set; }
    public string Name { get; set; }
    private ICollection<Class> classes = null;

    public virtual ICollection<Class> Classes
    {
        get { return classes ?? (classes = new HashSet<Class>()); }
        set { classes = value; }
    }
}

class ClassManager : DbContext
{
    public DbSet<Professor> Professors { get; set; }
    public DbSet<Class> Classes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Class>().HasRequired(c => c.Professor);
    }
}

In my main method I create and access my data like so:

    using (ClassManager db = new ClassManager())
    {
        Professor p = new Professor() { Name = "McTest" };
        Class c = new Class() { Title = "Testing Tests", Professor = p };

        p.Classes.Add(c);

        db.Professors.Add(p);
        db.Classes.Add(c);

        Console.WriteLine(db.SaveChanges() + " change(s) saved.");
    }

    using (ClassManager db = new ClassManager())
    {
        foreach (Professor p in db.Professors)
            Console.WriteLine("Prof. " + p.Name + " gives " + p.Classes.Count 
                + " classes.");

        foreach (Class c in db.Classes)
            Console.WriteLine(c.Title + " (Prof. " + c.Professor.Name + ")");
    }

The output is:

3 change(s) saved.
Prof. McTest gives 0 classes.
Testing Tests (Prof. McTest)

The generated DB schema is:

table Classes
    column Id (PK, int, not null)
    column Title (nvchar(max), null)
    column Professor_Id (FK, int, not null)
table Professors
    column Id (PK, int, not null)
    column Name (nvchar(max), null)

As you can see, the Classes collection property does not appear anywhere in the schema. Is that intended?

Can someone please shed some light into this? All the examples I've found wouldn't go into detail about collection properties.

like image 934
Good Night Nerd Pride Avatar asked Dec 06 '12 13:12

Good Night Nerd Pride


2 Answers

The two separated using statements in my main code are intentional. If I merge them together, the Classes property suddenly is not empty, but I don't understand why.

If the 2 are merged into 1 then you "query" the local entities that are stored in-memory, not the database.

in your first context you do not need to add both professor and class to the context. just add the professor and the class will be saved too.

check the DB schema and make sure the entities are properly mapped to the database.

finally, change the Professor class to

class Professor
{
    public Professor()
    {
         Classes = new List<Class>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Class> Classes {get; set;}
}

I suspect the lazy loading is being trumped by the is null logic to return a hashed set.

like image 28
Jason Meckley Avatar answered Oct 02 '22 14:10

Jason Meckley


Ok, after hours of searching I found the solution. It seems like one has to instruct the EF explicitly to load the collection properties of an entity when querying the database:

foreach (Professor p in db.Professors.Include("Classes"))
    Console.WriteLine("Prof. " + p.Name + " gives " + p.Classes.Count
        + " classes.");

The Include method does that.

like image 160
Good Night Nerd Pride Avatar answered Oct 02 '22 16:10

Good Night Nerd Pride