Search code examples
c#linqentity-frameworklinq-to-entitiesnavigation-properties

Using navigation to load entity 2 level deep


I have a class

public class Level1
{
   public int Id {get; set;}
   public virtual List<Level2> Level2List {get; set;}
}

public class Level2
{
   public int Id {get; set;}
   public int Level3Id {get; set;}
   public virtual Level3 Level3 {get; set;}
}

public class Level3
{
   public int Id {get; set;}
   public string Name {get; set;}
}

Using navigation properties i could load List<Level2> like this

var myList = _myLevel1Repository.GetSingle(x=>x.Id == Level1Id, x=>x.Level2List);

but how do I load Level3 and its properties that is linked with Level2?

PS: Lazy loading is not possible. This is the Getsingle function

    public T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navProps)
    {
        T item = null;
        using (var context = new MyDbContext())
            item = GetFilteredSet(context, navProps).AsNoTracking().FirstOrDefault(where);
        return item;
    }

Solution

  • Your GetSingle method should be this way:

    public T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navProps)
    {
        T item = null;
        using (var context = new MyDbContext())
        {
            IQueryable<T> query = context.Set<T>();
    
            //Include the navigations properties as part of the query
            if (navProps!= null)
            {
                query = navProps.Aggregate(query, (current, include) => current.Include(include));
            }
            item = query.Where(where).FirstOrDefault();
        }
        return item;
    }
    

    I don't know what are you doing in the GetFilteredSet method, but I guess you can reorganize the code I show above at your convenience. The key to include more than one level of nav. properties in EF is use the Include method. When you use this method you are going to load the nav. properties as part of your query (check eager loading section in this link). Now, there are two Include methods :

    • DbQuery.Include Method

      Whit this method you need to pass the path of the nav. properties you want to load as an string, for example, in your case, it would be:

      context.Set<Level1>.Include("Level2List.Level3");
      
    • DbExtensions.Include extension method

      This is the method that I use in my above code where you can specify the related objects to include using a lambda expression. IMHO this is the best variant because is strongly typed, and if you change some of the nav. properties name in your entities, you are also going to receive a compilation error(s). In the link I share above, you can see all the patterns you can use to include different levels of nav. properties.

      context.Set<Level1>.Include(l1=>l1.Level2List.Select(l2=>l2.Level3));
      

    Returning to the initial problem, now you can use your GetSingle method to include more than one level this way:

    var entity= _myLevel1Repository.GetSingle(x=>x.Id == Level1Id, x=>x.Level2List.Select(l2=>l2.Level3));