0

I have 2 entity classes with a many-to-many relationship class that is not modeled as a weak entity:

public class Foo 
{
    public Guid Id { get; private set; }
    public ICollection<Baz> Bazs { get; private set; }
}

public class Bar 
{
    public long Id { get; private set; }
}

public class Baz 
{
    public Guid Id { get; private set; }
    public Foo Foo { get; private set; }
    public Bar Bar { get; private set; }
    // other things
}

My EntityTypeConfigurations are built like this:

public class FooConfiguration : EntityTypeConfiguration<Foo> 
{
    HasMany(f => f.Bazs).WithRequired(b => b.Foo).Map(c => c.ToTable("FooBars"));
}

public class BarConfiguration : EntityTypeConfiguration<Bar> 
{
    //no navigation mapping
}

public class BazConfiguration : EntityTypeConfiguration<Baz>
{
    HasRequired(b => b.Foo).WithMany(f => f.Bazs);
    HasRequired(b => b.Bar).WithMany();
}

The problem I'm encountering is that when I load a Foo, the Bar side of the Baz relationship doesn't get loaded. I've changed the properties on Baz to have backing fields and set breakpoints on the setters, and the setter for Foo gets called, but the setter for Bar does not. I've also tried making the Bar property on Baz virtual to see if this was a case where eager loading was required for some reason. An example of this usage:

var foo = Set<Foo>.Single(f => f.Id == Id);
var barIds = foo.Bazs.Select(b => b.Bar.Id).ToList(); 
// the projection lambda throws because b.Bar is null

However, if I load the Bars explicitly, they do get populated:

var bars = Set<Bar>().ToList();
var foo = Set<Foo>().Single(f => f.Id == Id);
var barIds = foo.Bazs.Select(b => b.Bar.Id).ToList(); 
// barIds now contains the correct IDs for the associated Bars

My questions, then, are:

  1. Is the workaround of loading the Bars before loading the Foo the only way to get my entities populated?
  2. If not, what am I missing?

Additional information

I've tried using an ICollection<Bar> directly on Foo, which works for loading the Foo->Bar relationship, but because Baz is not a weak entity this setup fails on SaveChanges().

I've tried mapping the collection both ways, but that's apparently not possible either.

Community
  • 1
  • 1
Matt Mills
  • 8,692
  • 6
  • 40
  • 64

2 Answers2

2

In your example that "works" EF is just populating the properties because it has them in it's local cache, however this is not the way to do what you need.

You either need to explicitly include Bazs.Bar in your query as below which will eager load them.

var foo = Set<Foo>().Include("Bazs.Bar").Single(f => f.Id == Id);

Alternatively if you declare your navigation properties as virtual then this will enable lazy loading.

public class Foo 
{
    public Guid Id { get; private set; }
    public virtual ICollection<Baz> Bazs { get; private set; }
}

public class Baz 
{
    public Guid Id { get; private set; }
    public virtual Foo Foo { get; private set; }
    public virtual Bar Bar { get; private set; }
    // other things
}
Ben Robinson
  • 21,601
  • 5
  • 62
  • 79
  • 1
    Do I have to make *both* navigation properties virtual for the relationship to be lazy loaded? I tried making `Bar` virtual, and that did not work. – Matt Mills Nov 25 '14 at 15:45
  • 1
    Making both navigation properties virtual also did not solve the issue. – Matt Mills Nov 25 '14 at 16:50
0

One thing that is quite important to notice (while not necessary fixing the issue of the OP, but because code sample is Foo-Bared, it might be overly simplified - anyway, it's been 4 years so I'm sure OP does not care anymore).

EF requires the entity classes to have at least a protected parameterless constructor. If your parameter constructor is private, the entity will load, but the lazy loading of navigation properties will not work - regardless if it's virtual or not.

The navigation properties would have to be loaded explicitly (e.g. by using Include).

Soo:

public class Foo 
{
    //private constructor won't lazy-load navigation properties (virtual or not).
    //change to protected 
    private Foo(){}  
    public Foo(string name)
    { 
        this.Name = name; 
    }
    public string Name {get;set;}
    public Guid Id { get; private set; }
    public virtual ICollection<Baz> Bazs { get; private set; }
}
Bartosz
  • 4,406
  • 7
  • 41
  • 80