k.c.
k.c.

Reputation: 1835

Lazyloading of EntityCollection

I’m using entity framework 5. I’m trying to lazy load an entity collection, I stripped the model to the bare bones to have a simple runable sample.

This is my model:

public class A
{

    public int Id { get; set; }

    public EntityCollection<B> Bs
    {
        get { return bs; }
        set { bs = value; }
    }
    private EntityCollection<B> bs;

    public A()
    {
        bs = new EntityCollection<B>();
    }
}

public class B
{
    public int Id { get; set; }
    public A A { get; set; }
}

public class DbModel : DbContext
{
    public DbSet<A> As { get; set; }
    public DbSet<B> Bs { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<A>()
                    .HasKey( t => t.Id )
                    .HasMany(a => a.Bs)
                    .WithRequired(b => b.A);
        modelBuilder.Entity<A>()
                    .Property(t => t.Id)
                    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        modelBuilder.Entity<B>()
                    .HasKey(b => b.Id)
                    .HasRequired(b => b.A);
        modelBuilder.Entity<B>()
                    .Property( t => t.Id )
                    .HasDatabaseGeneratedOption( DatabaseGeneratedOption.Identity );
    }

This is the test demonstrating my problem:

   [TestInitialize]
    public void Initialize()
    {
        model = new DbModel();
        model.Configuration.ProxyCreationEnabled = false;

        if (model.Database.Exists()) model.Database.Delete();

        model.Database.Create();
        A a = model.As.Create();
        model.As.Add(a);
        B b = model.Bs.Create();
        a.Bs.Add(b);

        model.ChangeTracker.DetectChanges();
        model.SaveChanges();
    }

   [TestMethod]
    public void TestMethod1()
    {
        // arrange
        DbModel tModel = new DbModel();
        A a = tModel.As.First();

        // act
        a.Bs.Load();
    }

This is the result:

Test method TestProject1.UnitTest1.TestMethod1 threw exception:

System.InvalidOperationException: Requested operation is not allowed when the owner of this RelatedEnd is null. RelatedEnd objects that were created with the default constructor should only be used as a container during serialization.

Upvotes: 1

Views: 653

Answers (3)

k.c.
k.c.

Reputation: 1835

As it turns out I was using classes from different paradigms of the ADO family. As pointed out by Gert Arnold EF doesn’t work with EntityCollection it uses a plain list for association collections.

public class A
{

    public int Id { get; set; }

    public List<B> Bs
    {
        get { return bs; }
        set { bs = value; }
    }
    private List<B> bs = new List<B>();
}

As I did not want the proxies, the objects have no “knowledge” of the model. So the model itself has to be used to load associations for an entity. For this sample I added the following method to the model:

public class DbModel : DbContext
{
    public DbSet<A> As { get; set; }
    public DbSet<B> Bs { get; set; }

//…

    public A LoadAWithAssociations(A targetA)
    {
        return As.Include("Bs").First(a => a.Id.Equals(targetA.Id));
    }
}

I adapted the test to use the new method

  [TestMethod]
    public void TestMethod1()
    {
        // arrange
        DbModel tModel = new DbModel();
        A a = tModel.As.First();

        // act
        A result =  tModel.LoadAWithAssociations(a);

        // assert 
        Assert.IsNotNull(result);
        Assert.IsTrue(result.Bs.Count > 0);
    }

It gives a green now

Upvotes: 0

Gert Arnold
Gert Arnold

Reputation: 109252

EntitySet is part of the linq-to-sql API. That does not explain this particular exception, but a less than smooth cooperation with DbContext is no surprise. When you use DbContext API it is common to define a 1:n navigation property as an interface like ICollection:

public virtual ICollection<B> Bs { get; set; }

(virtual to enable proxy creation for lazy loading)

As an aside: you can use model.Database.Delete() without checking whether the database exists. And DetectChanges() right before SaveChanges() is redundant.

Upvotes: 1

Paul Taylor
Paul Taylor

Reputation: 5761

A bit convoluted, this code. Do you really want a class (B) that contains a reference to class A that in turn has a collection of B? Maybe it's just the obfuscated names, but I can't imagine a scenario for an object model like this.

In any case, my instinct would be to look for a circular reference or another condition that is preventing an object property to be instantiated on one of your classes.

Also, you are using Eager loading in the test method using a.Bs.Load(). Lazy Loading is disabled when you set ProxyCreationEnabled = false.

Upvotes: 0

Related Questions