DbSet IncludeAll method

Posted on

Problem

The Problem

As an ASP.NET MVC4 developper, I’m using Entity Framework a lot. Considering performance I often use lazy loading for my models.

public class Result {
    public int Id { get; set; }
    public decimal Grade { get; set; }
    public virtual Skill Skill { get; set; }
    public int SkillId { get; set; }
    public virtual Player Player { get; set; }
    public int PlayerId { get; set; }
    public virtual Category { get; set; }
    public int CategoryId { get; set; }
}

If I want to include all navigation models I’ll have to include all those models.

public ActionResult Details(int id = 0)
{
    Result result = db.Results
                      .Include(r => r.Skill)
                      .Include(r => r.Player)
                      .Include(r => r.Category)
                      .SingleOrDefault(r => r.Id == id);
    //some viewmodel mapping
    return View(viewmodel);
}

My solution

I built an extension method to remove this from my controller code.

public static class IncludeExtensions
{
    public static IQueryable<Result> IncludeAll(this IQueryable<Result> results)
    {
        return results.Include(r => r.Skill)
                      .Include(r => r.Player)
                      .Include(r => r.Category);
    }

    public static Result IncludedFind(this IQueryable<Result> results, int id)
    {
        return results.IncludeAll().SingleOrDefault(r => r.Id == id);
    }
}

public ActionResult Details(int id = 0)
{
    Result result = db.Results.IncludedFind(id);
    //some viewmodel mapping
    return View(viewmodel);
}

There are a few problems with this:

  1. I can’t create an abstract extension class to force IncludeAll() and IncludedFind() method.
  2. I still have to update the extension method if my models change.
  3. I’ll have a proliferation of extension methods/classes.
  4. Isn’t there an IncludeAll() like method available for Entity Framework?
    • Is there something like this on NuGet?
  5. It just feels wrong…

Solution

A simple option would be to use reflection to check for properties that are virtual and has the Id-suffix. This is where I came up with, working for me;

public static IQueryable<T> IncludeAll<T>(this IQueryable<T> queryable) where T : class
{
   var type = typeof (T);
   var properties = type.GetProperties();
   foreach (var property in properties)
   {
       var isVirtual = property.GetGetMethod().IsVirtual;
       if (isVirtual && properties.FirstOrDefault(c => c.Name == property.Name + "Id") != null)
       {
           queryable = queryable.Include(property.Name);
       }
   }
   return queryable;
}

I hope this answers your question.

Include uses when you want to include ICollection properties or not int, string, bool, etc. types. I use this in my base repository that precedes all my entities.

public IQueryable<T> GetIncludes(IQueryable<T> Queryable)
{
    var normal_types = new List<Type>() {
        typeof(int),
        typeof(string),
        typeof(bool)
    };

    var ty = typeof(T);

    foreach (var item in ty.GetProperties())
    {
        if (!normal_types.Contains(item.GetType()))
        {
            Queryable.Include(item.Name);
        }
    }

    return Queryable;
}

public IQueryable<T> GetIncludes(DbSet<T> Queryable)
{
    return GetIncludes(Queryable.AsQueryable());
}

EF knows about all navigation properties. So, instead of using reflection and try to figure it out by myself, I can just ask the DbContext about navigation properties.

Here‘s a wonderful SO answer that does exactly that.

Leave a Reply

Your email address will not be published. Required fields are marked *