I want to rewrite certain parts of the LINQ expression just before execution. And I'm having problems injecting my rewriter in the correct place (at all actually).
Looking at the Entity Framework source (in reflector) it in the end comes down to the IQueryProvider.Execute
which in EF is coupled to the expression by the ObjectContext
offering the internal IQueryProvider Provider { get; }
property.
So I created a a wrapper class (implementing IQueryProvider
) to do the Expression rewriting when the Execute gets called and then pass it to the original Provider.
Problem is, the field behind Provider
is private ObjectQueryProvider _queryProvider;
. This ObjectQueryProvider
is an internal sealed class, meaning it's not possible to create a subclass offering the added rewriting.
So this approach got me to a dead end due to the very tightly coupled ObjectContext.
How to solve this problem? Am I looking in the wrong direction? Is there perhaps a way to inject myself around this ObjectQueryProvider
?
Update: While the provided solutions all work when you're "wrapping" the ObjectContext using the Repository pattern, a solution which would allow for direct usage of the generated subclass from ObjectContext would be preferable. Hereby remaining compatible with the Dynamic Data scaffolding.
Based on the answer by Arthur I've create a working wrapper.
The snippets provided provide a way to wrap each LINQ query with your own QueryProvider and IQueryable root. This would mean that you've got to have control over the initial query starting (as you'll have most of the time using any sort of pattern).
The problem with this method is that it's not transparent, a more ideal situation would be to inject something in the entities container at the constructor level.
I've created a compilable the implementation, got it to work with entity framework, and added support for the ObjectQuery.Include method. The expression visitor class can be copied from MSDN.
public class QueryTranslator<T> : IOrderedQueryable<T>
{
private Expression expression = null;
private QueryTranslatorProvider<T> provider = null;
public QueryTranslator(IQueryable source)
{
expression = Expression.Constant(this);
provider = new QueryTranslatorProvider<T>(source);
}
public QueryTranslator(IQueryable source, Expression e)
{
if (e == null) throw new ArgumentNullException("e");
expression = e;
provider = new QueryTranslatorProvider<T>(source);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)provider.ExecuteEnumerable(this.expression)).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return provider.ExecuteEnumerable(this.expression).GetEnumerator();
}
public QueryTranslator<T> Include(String path)
{
ObjectQuery<T> possibleObjectQuery = provider.source as ObjectQuery<T>;
if (possibleObjectQuery != null)
{
return new QueryTranslator<T>(possibleObjectQuery.Include(path));
}
else
{
throw new InvalidOperationException("The Include should only happen at the beginning of a LINQ expression");
}
}
public Type ElementType
{
get { return typeof(T); }
}
public Expression Expression
{
get { return expression; }
}
public IQueryProvider Provider
{
get { return provider; }
}
}
public class QueryTranslatorProvider<T> : ExpressionVisitor, IQueryProvider
{
internal IQueryable source;
public QueryTranslatorProvider(IQueryable source)
{
if (source == null) throw new ArgumentNullException("source");
this.source = source;
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
return new QueryTranslator<TElement>(source, expression) as IQueryable<TElement>;
}
public IQueryable CreateQuery(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
Type elementType = expression.Type.GetGenericArguments().First();
IQueryable result = (IQueryable)Activator.CreateInstance(typeof(QueryTranslator<>).MakeGenericType(elementType),
new object[] { source, expression });
return result;
}
public TResult Execute<TResult>(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
object result = (this as IQueryProvider).Execute(expression);
return (TResult)result;
}
public object Execute(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
Expression translated = this.Visit(expression);
return source.Provider.Execute(translated);
}
internal IEnumerable ExecuteEnumerable(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
Expression translated = this.Visit(expression);
return source.Provider.CreateQuery(translated);
}
#region Visitors
protected override Expression VisitConstant(ConstantExpression c)
{
// fix up the Expression tree to work with EF again
if (c.Type == typeof(QueryTranslator<T>))
{
return source.Expression;
}
else
{
return base.VisitConstant(c);
}
}
#endregion
}
Example usage in your repository:
public IQueryable<User> List()
{
return new QueryTranslator<User>(entities.Users).Include("Department");
}