Entity Framework has a nice feature that allows for child navigation properties/collections to be automatically loaded. Essentially, this translates into SQL table joins.
I know this is old news, but I wanted to share a brief experience while updating my own base repositories.
For loading child navigation properties, Entity Framework can take an Expression<T,object> or a string. String literals can, obviously, be problematic. The string property reference has to match the model property name exactly. What I’ve done in the past was to get the property name from a MemberExpression:
/// <summary> /// Returns property name from expression without an instance of the object /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expression"></param> /// <returns></returns> public static string GetPropertyName<T>(Expression<Func<T, object>> expression) { if (expression.Body is MemberExpression) { return ((MemberExpression)expression.Body).Member.Name; } else { var op = ((UnaryExpression)expression.Body).Operand; return ((MemberExpression)op).Member.Name; } }
With this string in hand, we can call the extension method on any IQueryable<T> to fetch the child properties:
IQueryable<MyClass> query = _dbSet.Where(x => x.Prop1 = "Hello"); var propToInclude = GetPropertyName<MyClass>(x => x.ChildCollection) query = query.Include(propToInclude); var result = query.ToList();
That’s not too bad, but since the Include extension method also accepts expressions directly, I wanted to update my pattern to take advantage of this path as well. In my repository, it was a simple matter of adding a new method:
public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, params Expression<Func<TEntity, object>>[] includes) { return this.Get(filter, orderBy, string.Empty, includes, false); }
Note the addition of a params array of expressions. In the private get method that I use, it becomes a simple matter now to handle includes of string type or expressions. Below is the full code for that method. Note the change in the “include” handling.
private IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy, string includeProperties, Expression<Func<TEntity, object>>[] includeExpressions, bool untracked = false) { IQueryable<TEntity> query = untracked ? _dbSet.AsNoTracking() : _dbSet; if (filter != null) { query = untracked ? query.Where(filter).AsNoTracking() : query.Where(filter); } // If the includeProperties are specified, we will split then, iterate over them, and call the IQueryable.Include(str) method // If the includeExpressions are specified, we can use LINQ's Aggregate, or a foreach, to call IQuery.Include(Expression<Func<TEntity, object>> if (!string.IsNullOrWhiteSpace(includeProperties)) { foreach (var includeProperty in includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = untracked ? query.Include(includeProperty).AsNoTracking() : query.Include(includeProperty); } } else if (includeExpressions != null && includeExpressions.Length > 0) { query = untracked ? includeExpressions.Aggregate(query, (current, include) => current.Include(include).AsNoTracking()) : includeExpressions.Aggregate(query, (current, include) => current.Include(include)); } // If OrderBy is specified, attach the IQueryable to it if (orderBy != null) { return untracked ? orderBy(query).AsQueryable().AsNoTracking() : orderBy(query).AsQueryable(); } else { return untracked ? query.AsQueryable().AsNoTracking() : query.AsQueryable(); } }