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:
- I can't create an abstract extension class to force
IncludeAll()
andIncludedFind()
method. - I still have to update the extension method if my models change.
- I'll have a proliferation of extension methods/classes.
- Isn't there an
IncludeAll()
like method available for Entity Framework?- Is there something like this on NuGet?
- It just feels wrong...
-
\$\begingroup\$ Could you perhaps use AutoMapper or some other existing mapping service to do what you are wanting? I haven't used AutoMapper but sounds like what you might be looking for. \$\endgroup\$dreza– dreza2013年09月05日 20:42:16 +00:00Commented Sep 5, 2013 at 20:42
-
\$\begingroup\$ Automapper maps models to viewmodels. I don't think it helps loading entities from the database. \$\endgroup\$annemartijn– annemartijn2013年09月05日 20:45:26 +00:00Commented Sep 5, 2013 at 20:45
-
1\$\begingroup\$ It doesn't map models to view models. It maps anything to anything from what understand. Just happens that that is a common usage for VM => M and vice versa. But if you have considered and discarded it then no worries. \$\endgroup\$dreza– dreza2013年09月06日 01:38:51 +00:00Commented Sep 6, 2013 at 1:38
3 Answers 3
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.
-
\$\begingroup\$ This would solve my problem, but isn't reflection something you should avoid? As far as I know it's bad for performance and therefore it's condemned by many programmers. I would like to hear your opinion @martijnlentink! \$\endgroup\$annemartijn– annemartijn2013年09月06日 12:01:56 +00:00Commented Sep 6, 2013 at 12:01
-
3\$\begingroup\$ Reflection does affect performance, but to be honest I don't think you'll ever experience any issues. Try googling for 'Reflection bad' or 'slow', you will find that most peeps agree that reflection isn't that bad after all. It's just too powerful not to use imho. \$\endgroup\$martijnlentink– martijnlentink2013年09月06日 13:45:41 +00:00Commented Sep 6, 2013 at 13:45
-
\$\begingroup\$ What about the children of children? \$\endgroup\$X Pahadi– X Pahadi2018年05月03日 01:33:00 +00:00Commented May 3, 2018 at 1:33
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());
}
-
\$\begingroup\$ Welcome to Code Review, could you rephrase your introduction a bit to make it clearer? Right now the code itself is a better explanation to me than the text. I've also taken the liberty and fixed a few typos, I could only guess at one of the words though, please take a look if I got it wrong. Enjoy your stay! \$\endgroup\$ferada– ferada2019年05月06日 23:58:09 +00:00Commented May 6, 2019 at 23:58
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.