The aim of this class is to set rules for including navigational properties on Configuration level and then apply where needed in the code.
I've decided to store expressions in the list for different entities and then invoke System.Data.Entity.QueryableExtensions
Include
method to get proper JOIN
s for my navigational properties. It does work, but at what price?
How optimal and efficient is the following solution:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;
using XXX.Data.Security;
using XXX.Extensions;
namespace XXX.Data.Entity.ModelConfiguration {
internal class IncludedReferencePropertiesConfiguration<TEntity> where TEntity : class {
#region Constructors
private IncludedReferencePropertiesConfiguration(LoanAdminContext dbContext) {
this.DbContext = dbContext;
}
#endregion Constructors
#region Properties
#region Instance
private static IncludedReferencePropertiesConfiguration<TEntity> instance;
public static IncludedReferencePropertiesConfiguration<TEntity> Instance(LoanAdminContext dbContext) {
//Always refresh DbContext
if (instance == null) {
instance = new IncludedReferencePropertiesConfiguration<TEntity>(dbContext);
} else {
instance.DbContext = dbContext;
}
return instance;
}
#endregion Instance
#region Includes
private static readonly IList<Expression<Func<TEntity, Object>>> Includes = new List<Expression<Func<TEntity, object>>>();
#endregion Includes
#region DbContext
private LoanAdminContext DbContext { get; set; }
#endregion DbContext
#region Include<Expression<Func<TEntity, Object>>>
/// <summary>
/// Expression list which should keep the included referenced navigation properies
/// </summary>
public static void Include(Expression<Func<TEntity, Object>> path) {
if (Includes.All(include => include.PropertyName() != path.PropertyName())) {
Includes.Add(path);
}
}
#endregion Include<Expression<Func<TEntity, Object>>>
#region ApplyIncludes<Expression<IQueryable<TEntity>>
/// <summary>
/// Applies includes to entities
/// </summary>
public IQueryable<TEntity> ApplyIncludes(IQueryable<TEntity> entities) {
foreach (var include in Includes) {
var includeType = include.Property().PropertyType;
var includeMethod = typeof(System.Data.Entity.QueryableExtensions).GetMethods()
.Single(method =>
method.Name == "Include"
&&
method.IsGenericMethod
&&
method.GetParameters().Count() == 2
&&
method.GetParameters()[0].ParameterType.IsGenericType
&&
method.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&&
method.GetParameters()[1].ParameterType.IsGenericType
&&
method.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
var includeGenericMethod = includeMethod.MakeGenericMethod(typeof(TEntity), includeType);
entities = (IQueryable<TEntity>)includeGenericMethod.Invoke(null, new object[] { entities, Cast(includeType, include) });
}
return entities;
}
#endregion ApplyIncludes<IQueryable<TEntity>>
#endregion Properties
#region Public Functions
#region LoadIncludedProperties
public void ApplyIncludes(TEntity entity) {
// Loop through all include path expressions and load corresponding properties
foreach (var navigationPropertyExpression in Includes) {
var navigationPropertyName = navigationPropertyExpression.PropertyName();
var navigationPropertyInfo = entity.GetType().GetProperty(navigationPropertyName);
if (navigationPropertyInfo != null) {
var navigationPropertyEntities = navigationPropertyInfo.IsCollectionType()
?
this.DbContext.Entry(entity).Collection(navigationPropertyName).Query()
:
this.DbContext.Entry(entity).Reference(navigationPropertyName).Query();
navigationPropertyEntities = this.DbContext.CreateAllActionableObjectsQuery(navigationPropertyInfo.UnderlyingType(), navigationPropertyEntities, ActionType.Read);
navigationPropertyEntities.Load();
}
}
}
#endregion LoadIncludedProperties
#endregion Public Functions
#region Private Functions
private static Expression Cast(Type toType, Expression<Func<TEntity, Object>> expression) {
var converted = Expression.Convert(expression.Body, toType);
return Expression.Lambda(converted, expression.Parameters);
}
#endregion Private Functions
}
}
namespace XXX.Data.Entity.ModelConfiguration {
internal static class EntityTypeConfigurationExtensions {
public static EntityTypeConfiguration<TEntity> Include<TEntity>(this EntityTypeConfiguration<TEntity> configuration, Expression<Func<TEntity, Object>> path)
where TEntity : class {
// Add eagerly loaded reference property to configureation class
IncludedReferencePropertiesConfiguration<TEntity>.Include(path);
// Return reference to configuration, for chaining of calls
return configuration;
}
}
}
Could I have made it more elegant and shorter?