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
{
//Used for child entities.
//Example: Order.Customer
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> parent,
Expression<Func<T, StructuralObject>> expression)
where T : StructuralObject
{
return Include(parent, (LambdaExpression) expression);
}
//Used for child collections of entities.
//Example: Order.OrderLines
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
{
//There must be only one root entity to load related entities to it.
if (expression.Parameters.Count != 1)
{
throw new NotSupportedException();
}
//We'll store entity names here in order then join them at the end.
var entityNames = new List<string>();
//We split the calls ... Entity.MemberOfTypeChild.ChildMemberOfChildMember etc..
//Example: (Order ord) => ord.Customer.Address
string[] childTypesMembers = expression.Body.ToString().Split('.');
//Get the root entity type to start searching for the types of the members inside it.
//In prev. example: Find: Order
Type parentType = expression.Parameters[0].Type;
//entityNames.Add(GetEntityNameFromType(parentType));
//The first word in the expression is just a variable name of the root entity.
// Skip it and start next.
//In example: First part is: ord
for (int i = 1; i < childTypesMembers.Length; i++)
{
string memberName = childTypesMembers[i];
//Get the member from the root entity to get its entity type.
MemberInfo member = parentType.GetMember(memberName)[0];
//We cannot get the type of the entity except by knowing
// whether it's property or field (most likely will be property).
//Bad catch in the reflection API? Maybe!
Type memberType = member.MemberType == MemberTypes.Property
? ((PropertyInfo) member).PropertyType
: ((FieldInfo) member).FieldType;
//Add the eneity name got from entity type to the list.
entityNames.Add(GetEntityNameFromType(memberType));
//The next member is belong to the child entity, so,
// the root entity to seach for members should be the child entity type.
parentType = memberType;
}
//Join the entity names by "." again.
string includes = string.Join(".", entityNames.ToArray());
//Simulate the original Include(string) call.
return parent.Include(includes);
}
private static string GetEntityNameFromType(Type type)
{
// We didn't just use the Entity type names because maybe
// the table is called Orders and the class is Order or OrderEntity.
if (type.HasElementType) //For arrays, like: OrderLines[]
{
//The type of the element of the array is what we want.
type = type.GetElementType();
}
else if (type.IsGenericType) // for collections, like: EntityCollection<OrderLines>
{
var genericClassTypeParameters = type.GetGenericArguments();
//The generic class must have one entity type only to load it.
if (genericClassTypeParameters.Length != 1)
throw new NotSupportedException();
//The type of the element of the collection is what we want.
type = genericClassTypeParameters[0];
}
//Get the attributes that have the entity name in them.
var entityTypeAttributes =
type.GetCustomAttributes(typeof (EdmEntityTypeAttribute), true) as EdmEntityTypeAttribute[];
//Make sure there IS one and ONLY one attribute to get the only entity name.
if (entityTypeAttributes == null || entityTypeAttributes.Length != 1)
throw new NotSupportedException();
//Return the entity name.
return entityTypeAttributes[0].Name;
}
}
}