4

I have this simple model:

class Parent
{
   public int Id { get; set; }

   public virtual ICollection<Child> Children { get; set; }
}

class Child
{
   public int Id { get; set; }
   public int ParentId { get; set; }

   public virtual Parent Parent { get; set; }
}

class MyContext : DbContext
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Child>().HasRequired(s => s.Parent).WithMany(s => s.Children).HasForeignKey(s => s.ParentId);

        base.OnModelCreating(modelBuilder);
    }
}

And when I use MyContext as below, I get null reference exception because child.Parent is null

var context = new MyContext();
var child = context.Children.First();
var parentId = child.Parent.Id;              // Parent == null

To solve this problem I have to change the access modifier of Parent and Child classes to be public.

Why is that required ? Or is this just a bug ?

KeyBored
  • 601
  • 4
  • 14
  • Not really a duplicate because it does answer "why" but similar problem nonetheless... http://stackoverflow.com/questions/7619955/mapping-private-property-entity-framework-code-first – blins Jan 20 '16 at 13:38

2 Answers2

5

That is not a bug, you are using a feature of Entity Framework called Lazy Loading, and to use it you need to meet some requirements that you can find in this link. One of those requirements is that your entity classes must be public. In that link you will find a proper explanation of why you should meet those requirements, but in summary your issue is because EF can't create a proxy class from your entity, due to that you can't use lazy loading. You already are meeting the main requirement for lazy loading which is your navigation properties must be virtual, but first you must meet the requirements that EF needs to create a proxy class.

As an additional resource I suggest you look this msdn page where you can find all the ways to load related entities using EF.

ocuenca
  • 38,548
  • 11
  • 89
  • 102
  • As a side note, a good rule of thumb is that you will want to have your class to have the same access level as the highest access level of the properties and methods of the class. – Nathan C Jan 20 '16 at 13:30
  • 2
    @NathanC Not necessarily. Imagine a private nested class. If all its members would also be private the class could not be access by the containing class. However you MAY change members to internal or even public in the sence of an API to provide access within the containing class. – MakePeaceGreatAgain Jan 20 '16 at 13:32
  • @HimBromBeere That is a fair counterpoint. I was thinking of a general rule of thumb, not a hard rule. If one is going to have members with a looser access than the class itself, it should be a purposeful decision. – Nathan C Jan 20 '16 at 13:57
  • 1
    extremely useful link explaining the rules for lazy loading/proxy creation. I've had some subtle bugs the last few days involving private constructors. by now I've solved it, but I'm definitely saving this for future reference. – sara Jan 20 '16 at 14:34
  • @octavioccl Thanks alot, that was so helpful. – KeyBored Jan 20 '16 at 15:14
  • 1
    @NathanC In particular what you´ve said does not apply when your class implements an interface forcing all its members to be public. However you may want to limit the class-access to `internal` or even `private`. – MakePeaceGreatAgain Jan 21 '16 at 15:22
2

First, to clear something up: your classes aren't private, they're internal. You can't declare a top-level type (a non-nested type) to be private (see MSDN Access Modifiers for more info).

Second, Lazy Loading is enabled by default for code first. As octaviocci mentioned, if you're expecting to use Lazy Loading you need to declare them public: it's just part of the requirements. By declaring your Navigation properties as virtual you're telling EF that you expect to use lazy loading. If you don't want to, you can (should) remove the virtual keyword. Then, when getting your entities, use the Include method to eagerly load related entities.

var context = new MyContext();
var child = context.Children.Include(c => c.Parent).First();
var parentId = child.Parent.Id;
DrewJordan
  • 5,266
  • 1
  • 25
  • 39