Question: Best practices for providing encapsulation of collection navigation properties with lazy loading #22752
Description
Question
Dear EF Core community,
I would like to provide encapsulation with my collection navigation properties while using lazy loading. For that purpose, I usually expose an IReadOnlyList
for my public property while being backing by a backing field. EF Core does not lazy load the value of my backing field, so querying it result of an empty list. What are the best practices in EF Core 3 or 5 to gatekeep the alterations of my collections while using lazy loading? I provide a small example below to work on.
Code
public class Foo
{
private readonly List<Bar> _bars; // I would like to gatekeep the changes of that collection
public Foo(long id)
{
Id = id;
_bars = new List<Bar>();
}
public virtual IReadOnlyList<Bar> Bars => _bars.AsReadOnly();
public long Id { get; set; }
public int NumberOfBars => _bars.Count; // A dummy example of extracting some info from the backing field. Note that in reality, this may alter the collection.
public void AddBar(Bar bar) // This method gatekeeps the changes in the collection
{
_bars.Add(bar);
}
}
public class Bar
{
public long Id { get; set; }
public Bar(long id)
{
Id = id;
}
}
private static void Main(string[] args)
{
using (var db = new DbContext())
{
var foo = new Foo(1);
foo.AddBar(new Bar(1));
foo.AddBar(new Bar(2));
db.Foos.Add(foo);
db.SaveChanges();
}
using (var db = new DbContext())
{
var f = db.Foos.First();
Console.WriteLine(f.NumberOfBars); // Print 0, while I would like 2 to be printed
}
}
Output
The application prints 0, while 2 is printed when querying the navigation property instead. I understand this is due to how lazy loading is working, by subclassing Foo
at runtime. I would like to know the recommended way to structure my classes to provide encapsulation while still using lazy loading. Feel free to change my structure to orient me in the right direction.
Version information
EF Core version: 3.1.8
Database provider: PostgreSQL
Target framework: .NET Core 3.1
Operating system: Windows
IDE: Visual Studio 2019 16.7.2
Thanks!