5

Summary

When configuring EF Core to lazily resolve navigation properties of entities, the navigation property - and any backing field it might have - are null until the property is first accessed. This makes sense.

However, this can be problematic if you need to access the backing field within the entity itself before the navigation property has been accessed. In this case, the field will be null and a NullReferenceException will be thrown.

In some cases it's necessary to perform operations using the backing field instead of the property. In these case, I'm wondering if there is a clean way to ensure that backing fields get loaded correctly without having to either:

  1. Sacrifice good encapsulation of the entity
  2. Litter the entity with code that's only there to let it play well with the underlying persistence logic

Example

A common pattern with collection navigations is to have a backing field of a mutable collection type and to expose it as a read-only collection. For example, consider an entity like this:

public class ProductConsultant
{
    private readonly List<Discretionary> _discretionaries = null!;

    protected ProductConsultant()
    {
        // Required by EF
    }

    // Public constructor goes here

    public virtual IReadOnlyCollection<Discretionary> Discretionaries => _discretionaries.AsReadOnly();

    public void RemoveDiscretionaryWithId(int discretionaryId)
    {
        _discretionaries.RemoveAll(x => x.Id == discretionaryId);
    }
}

If I were to call RemoveDiscretionaryWithId before Discretionaries was first accessed, the code would throw a NullReferenceException. I can't directly use Discretionaries within that method because I need to mutate the collection, and the read-only wrapper prevents that.

To work around this, I'd apparently need to do something really hacky like this:

public class ProductConsultant
{
    private readonly List<Discretionary> _discretionaries = null!;

    protected ProductConsultant()
    {
        // Required by EF
    }

    // Public constructor goes here

    private List<Discretionary> PrivateDiscretionaries
    {
        get
        {
            Discretionaries.ToList();
            return _discretionaries;
        }
    }

    public virtual IReadOnlyCollection<Discretionary> Discretionaries => _discretionaries.AsReadOnly();

    public void RemoveDiscretionaryWithId(int discretionaryId)
    {
        PrivateDiscretionaries.RemoveAll(x => x.Id == discretionaryId);
    }
}

But I'd be very reluctant to have to do that for every collection navigation of all my entities.

I've seen this question already, but it doesn't address this particular issue as the property in that scenario is exposed with a mutable collection type.

Tagc
  • 8,736
  • 7
  • 61
  • 114
  • @Evk oops so i was, thanks – stuartd Oct 22 '20 at 14:03
  • 1
    Here is efcore github issue discussing this: https://github.com/dotnet/efcore/issues/22752 – Evk Oct 22 '20 at 14:06
  • @Evk Thanks, that addresses my question perfectly. Unfortunately, there doesn't seem to be a cleaner way to do it other than injecting an `Action lazyLoader` into my entities, so I'll probably move away from using lazy-loading proxies altogether when .NET Core 5 / EF Core 5 drops next month (in which case I can use split queries again). – Tagc Oct 22 '20 at 14:32

0 Answers0