If you don’t know what eager loading is, jump to “What’s eager loading?”.
Eager Loading Syntax
If you are eager loading Products
for example in a typical (Categories 1<->* Products
) relation, the standard syntax would look like:
DbDataContext.Categories.Include("Products")
What is the Problem With That?
The “Products
” part. The word “Products
” is a string
. If I rename the Products
table to ShopProducts
or whatever or even remove it from this data diagram and have it elsewhere, or even something wrong happens and the relation is removed from DB/diagram by mistake, my code will still compile, but will fail when it runs. This is BAD BAD BAD.
How to Solve This?
Since I always believe that if something exists somewhere, you shouldn’t do it yourself unless it's totally broken (and I mean REALLY REALLY BROKEN), I started searching inside the Entity Framework itself for something to get the entity name from.
At first, it seemed super easy. Every entity class has a static
property “EntityKeyPropertyName
”, so, I thought I can write something like:
DbDataContext.Categories.Include(Product.EntityKeyPropertyName);
Where Product
is the entity class generated for table “Products
”. Note that singularizing the name (Products
becomes Product
) does not happen automatically like in Linq-To Sql, you’ll have to change it manually, which is not required for the code here of course.
As you can see in the comment, this didn’t work. The value of property was always “-EntityKey-
”, the default value of the abstract
class “StructuralObject
” which all entity classes inherit.
I kept searching all over until I found that the only place I can get the name from was an Attribute
generated on the class somewhat like this:
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(
NamespaceName="DatabaseNameFlowModel", Name="Products")]
My requirement was simple. If the diagram has something wrong that the relation between ParentTable
and ChildTable
tables, but not about the entity classes themselves, my code should not still compile and fail on run. I need to use some code that depends on the relation so that if something is wrong with the relation, this code fails early and I know about the problem the next time I build the VS project.
The Final Solution
I tried badly to get the entity name from the API, after getting frustrated, I ended up writing this code:
using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq.Expressions;
using System.Reflection;
namespace Meligy.Samples.EntityFramework
{
public static class LinqExenstions
{
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> parent,
Expression<Func<T, StructuralObject>> expression)
where T : StructuralObject {
return Include(parent, (LambdaExpression) expression);
}
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> parent,
Expression<Func<T, RelatedEnd>> expression)
where T : StructuralObject {
return Include(parent, (LambdaExpression) expression);
}
private static ObjectQuery<T> Include<T>(ObjectQuery<T> parent,
LambdaExpression expression)
where T : StructuralObject
{
if (expression.Parameters.Count != 1)
{
throw new NotSupportedException();
}
var entityNames = new List<string>();
string[] childTypesMembers = expression.Body.ToString().Split('.');
Type parentType = expression.Parameters[0].Type;
for (int i = 1; i < childTypesMembers.Length; i++)
{
string memberName = childTypesMembers[i];
MemberInfo member = parentType.GetMember(memberName)[0];
Type memberType = member.MemberType == MemberTypes.Property
? ((PropertyInfo) member).PropertyType
: ((FieldInfo) member).FieldType;
entityNames.Add(GetEntityNameFromType(memberType));
parentType = memberType;
}
string includes = string.Join(".", entityNames.ToArray());
return parent.Include(includes);
}
private static string GetEntityNameFromType(Type type)
{
if (type.HasElementType)
{
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var genericClassTypeParameters = type.GetGenericArguments();
if (genericClassTypeParameters.Length != 1)
throw new NotSupportedException();
type = genericClassTypeParameters[0];
}
var entityTypeAttributes =
type.GetCustomAttributes(typeof (EdmEntityTypeAttribute),
true) as EdmEntityTypeAttribute[];
if (entityTypeAttributes == null || entityTypeAttributes.Length != 1)
throw new NotSupportedException();
return entityTypeAttributes[0].Name;
}
}
}
This enables you to write:
DbDataContext.Categories.Include( (cat)=> cat.Prodycts);
or:
DbDataContext.Prodycts.Include( (prod)=> prod.Category);
According to your need.
For things like: Order.Customer.Address
(multiple levels), you’ll have to write code like:
DbDataContext.Orders.Include( order => order.Customer ).Include(
customer => Customer.Address );
What’s Eager Loading? (in case you don’t know)
Let’s say you have tables Products
and Categories
with relation 1<->*
between them (any category has many products
; one product
has one category
). Let’s say you want to display a page of all products
grouped by categories. Something like the following list but with much more information of course:
- Category 1
- Product A
- Product B
- Product C
- Category 2
- Product X
- Product Y
- Product Z
If you are using some ORM / Code generator that creates for you classes like “Product
”, “Category
” and gives you properties like “myCategory.Products
” , “myProduct.Category
”, how would you create such page?
Normally, you’ll put a repeater or such for products inside a repeater for categories.
The products
repeater will have its data source set to the current category
item of the Categories
repeater, something like “( (CategoryEntity)Container.DataItem ).Products
”. Fine with that? Familiar?
OK. Now, if the code generator that generated the “Products
” property has something like that:
public List<PRoduct> _Products;
public List<PRoduct> Products
{
get
{
if (_Products == null)
{
_Products = (from products in DB.Products
where products.CategoryID == this.ID
select products)
.ToList();
}
return _Products;
}
}
* Nevermind the LINQ syntax. It’s just like writing “SELECT * FROM [Products] WHERE …
” with all the dataset
/datareader
stuff.
Lazy Loading (AKA. Deferred Loading)
If the generated code (or your code) looks like this, this means that that for every category in the database, you’ll have a separate DB call to get the products of this category.
It also means that the products of each category will not be loaded until someone writes code that calls the getter of the Products
property. That’s why this style of coding (not loading the related objects until they’re asked to be loaded) is called Lazy Loading.
This is good for a single category where you may be seeking just the basic information of the category and will not try to load products, since then they will not be requested when you don’t ask for it.
However, this is very bad for our example page. Because it means a new DB call for each category. Imagine that you have 20 or 50 or 100 Category
there, this will give you how many DB calls? (Hint: N Categories + 1 call for the category list itself).
Eager Loading
What if the code in the getter above was in the constructor? Imagine something like:
public Category(int categoryID)
{
_CategoryID = categoryID;
_Products = (from products in DB.Products
where products.CategoryID == this.ID
select products)
.ToList();
}
This is good in case you know that in every situation when you use the category
, the Products
will be needed. This is probably not useful in a Product
/category
scenario, but think of a Person
table and Address
table where most likely whenever you load a Person
you’re going to load his Addresses
.
This is also useful especially when using ORM/code generator as in the first example. Let's get back to the Repeater example. If you use Entity framework or similar ORM, and you set the Categories
query to load the Products
eager loading (meaning each Category
is created with its Products
loaded already), Entity Framework can have a single connection and only TWO database hits, one for the Categories
, and one for the Products
. This is very useful in many listing scenarios. It also helps especially when you have many parent
objects (say Categories
) or if the parent
object needs to load entities of many different classes (say User
needs to load Roles and Permissions and Personal Information and History and …. (if such case is applicable for you, of course.
Now that you know what eager loading is, you can go up and check how the Entity Framework does that.
Technorati Tags:
Entity Framework,
LINQ,
LINQ To Entities,
LinqToEntities,
EagerLoading,
Eager Loading,
Meligy,
Mohamed Meligy,
Lambda Expressions,
Code Expressions,
C# 3.0,
C#3,
Csharp,
C Sharp,
C#,
Data Access,
ORM
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.