Petr Mánek
Petr Mánek

Reputation: 1066

Weird lazy loading in one-to-many relationship

I'm having a weird problem and I need some help. I am trying to do a simple one-to-many relationship in EF Code First. The idea is to have one CardCategory have multiple instances of CardQuery.

Here is my definition of CardCategory:

public abstract class CardCategory
{
    [Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
    public int Number { get; set; }

    [Required]
    public string Title { get; set; }

    [InverseProperty("Category")]
    public virtual ICollection<CardQuery> DataQueries { get; set; }

    public CardCategory()
    {
        // this ensures that the collection is never null (avoiding NullReferenceException).
        // it's no problem, because the constructor gets called before
        // data mapping occurs.
        DataQueries = new List<CardQuery>();
    }
}

and here is the CardQuery:

public abstract class CardQuery
{
    [Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
    public int Number { get; set; }

    [InverseProperty("DataQueries")]
    public virtual CardCategory Category { get; set; }
}

The weird thing is that when I try this with my data sample (few categories with few queries associated), it works only one way. I can obtain the parent category of each query like this:

var query = myContext.CardQueries.Find(1);
var category = query.Category; // this works alright

But when I try this in the opposite way (to get all child queries for a category), all I get is an empty collection.

var category = myContext.CardCategories.Find(1);
var queries = category.DataQueries; // here is the problem, I get null or empty collection

Perhaps you're wondering if the safety line in CardCategory constructor has anything to do with this. It doesn't, believe me. The first thing I tried was to remove it, all I got was a null instead of empty collection.

The irony is that the "DataQueries" navigation property works in a different case:

var query = myContext.CardQueries.Find(1);
var category = query.Category;
var queries = category.DataQueries; // this works, I don't know what's different

It seems like the EF somehow fails to populate the property but I can't find out why. This has never happened to me before and I'm kind of confused. I'd appreciate any suggestions.

There is one last thing to clear out, my data context is configured like this:

LazyLoadingEnabled = true;
ProxyCreationEnabled = true;
AutoDetectChangesEnabled = true;

Thanks for your help!

Upvotes: 1

Views: 934

Answers (3)

Georg Patscheider
Georg Patscheider

Reputation: 9463

I had a similar, but unrelated problem. I tried to lazy load some nested objects, e.g. access Foo.Bar.Baz My problem was that Baz was never lazy loaded, even though the Collection on Bar was virtual.

It turned out that the problem was caused by the ctor of Bar, which was private. Changing the ctor to protected allowed EF to correctly create the proxy class for Bar and enable lazy loading of the Baz collection.

Upvotes: 1

Rune G
Rune G

Reputation: 1537

I would not recommend using Include, it causes serious preformance hits in large bases.

Your problem here is that DataQueries is an array, simply calling category.DataQueries will not execute any queries against the storage (the same way that you cannot write myContext.CardQueries and automatically get the result). You need to explicity invoke an initiator e.g. ToList() or FirstOrDefault().

So, instead of using .Include try this:

var category = myContext.CardCategories.Find(1);
var queries = category.DataQueries.ToList();

This will initiate the enumerator and you will get the result of DataQueries.

As a sidenote: You can even filter your category.DataQueries by e.g. using Where statement.

Upvotes: 1

Daniel Imms
Daniel Imms

Reputation: 50149

Try using IQueryable<T>.Include():

var category = myContext.CardCategories
    .Include(e => e.DataQueries)
    .Find(1);

Upvotes: 0

Related Questions