3

I am having issues with entity framework not properly handling collections that I manually initialize with a few objects.

I am using code based off any of the numerous examples of using a private field to back a public collection property. e.g. as per this question

However I am running into issues if I latter want to use this field with LINQ queries.

My model structure is as follows.

public abstract class Parent
{
    [Key]
    public int Id { get; set; }

    public virtual List<Child> Childeren

    ...
}

public abstract class Child
{
    [Key]
    public int Id { get; set; }

    protected List<Grandchild> _grandchilderen;

    public virtual List<Grandchild> Grandchilderen
    {
        get
        {
            if (_grandchilderen== null)
                SpawnGrandchilderen();
            return _grandchilderen;
        }
        set { _grandchilderen= value; }
    }

    private void SpawnGrandchilderen(){
        _grandchilderen=new List<Grandchild>(){new Grandchild(), new Grandchild()};
    }

    ...
}

public abstract class Child
{
    [Key]
    public int Id { get; set; }

    ...
}

If I use a query like

parent_instance.Childeren.SelectMany(C=>C.Grandchilderen).ToList()

SpawnGrandchilderen() is called on every child overwriting data that should have been loaded from the database.

If I place a breakpoint at the start of the getter, I can see that _grandchilderen is always null.

I am able to get the code to work as expected if I place a breakpoint before the linq query and manually explore childeren using the watch window before continuing execution.

I believe this is an issue to do with lazy-loading but I am not sure how to go about solving this issue efficiently.


Edit: I have done a bit more poking around and all I have found is:

When it is first accessed the Grandchilderen list is ALWAYS null.

If I replace the call SpawnGrandchilderen() with _grandchilderen = new List<Grandchild>() and put a breakpoint on the line I can see that the line is being hit and yet my data from the database is actually returned when I would expect an empty array to be returned.

However if I replace the line with

_grandchilderen = new List<Grandchild>(){new Grandchild()};

Then the value returned from the getter contains none of my database data, but does include my newly created grandchild.

Some kind of weird EF voodoo is going on here and I am not sure how to troubleshoot it.

Community
  • 1
  • 1
Hugoagogo
  • 1,598
  • 16
  • 34
  • Try following : parent_instance.Childeren.Select(x => x.Grandchilderen).SelectMany(c => c).ToList(); – jdweng Apr 18 '17 at 15:26
  • Have you tried using `ICollection` instead of `List`? – Marc L. Apr 18 '17 at 16:45
  • I require my collection to be accessible by index so I had to go with list – Hugoagogo Apr 18 '17 at 22:22
  • @jdweng I tried the different linq query but got the same result. – Hugoagogo Apr 18 '17 at 22:23
  • Imagine a proxy getter as follows: `get { var res = base.Property; if (res == null || !res.Any()) { LoadFromDatabase(ref res); } return res; }`. I don't know how the actual implementation looks, but this demonstrates that you can't assume anything special to happen before your getter is called and you can't expect your getter result to be final. – grek40 Apr 24 '17 at 07:26
  • @grek40 thanks for the insight, do you have any suggestions on how I can work around this and achieve my desired/expected functionality. – Hugoagogo Apr 24 '17 at 12:31
  • I can only suggest, do not place any side-effect logic into the property accessors of your "POCO". If needed, use some separate initialization logic, where you can actually react to the result of the proxy property getter. – grek40 Apr 24 '17 at 12:56
  • Where would you recommend this initialisation be placed as there are similar issues with placing stuff in the constructor. – Hugoagogo Apr 24 '17 at 13:30

1 Answers1

0

I managed to solve this myself based on the hints from grek40 I was able to get things working by ensuring that the first call to the function finishes without modification to the value. (from non EF code) to ensure things get linked through properly.

by modifying my class to the following function

private bool _grandchilderenloaded = false;
protected List<Grandchild> _grandchilderen;

public virtual List<Grandchild> Grandchilderen
{
    get
    {
        if (!_grandchilderenreloaded)
            {
            _grandchilderenreloaded = true;
            if (Grandchilderen == null) // Note this getter calling itself here is where the magic happens
                SpawnGrandchilderen(); // The Db value has now been loaded, if it was non null do some setup.
            }
        return _grandchilderen;
    }
    set { _grandchilderen= value; }
}
Hugoagogo
  • 1,598
  • 16
  • 34