The problem with @Moho's answer is that it replaces the underlying IQueryable
. When you simply wrap the IQueryable
, it effects the final Expression
that is generated. If you immediately wrap the ISet<T>
, it will break calls to Include
. Furthermore, it can effect how/when other operations occur. So the solution is actually a bit more involved.
In searching for the solution, I came across this blog: http://blogs.msdn.com/b/alexj/archive/2010/03/01/tip-55-how-to-extend-an-iqueryable-by-wrapping-it.aspx. Unfortunately, this example is a little bit broken, but it was easy to fix (and improve). Below, I post the code I ended up writing.
The first class is an abstract base class that makes it possible to create different types of IQueryable
wrappers. LINQ uses IQueryProvider
s to convert LINQ expressions into executable code. I created an IQueryProvider
that simply passes calls to the underlying provider, making it essentially invisible.
public abstract class InterceptingProvider : IQueryProvider
{
private readonly IQueryProvider provider;
protected InterceptingProvider(IQueryProvider provider)
{
this.provider = provider;
}
public virtual IEnumerator<TElement> ExecuteQuery<TElement>(Expression expression)
{
IQueryable<TElement> query = provider.CreateQuery<TElement>(expression);
IEnumerator<TElement> enumerator = query.GetEnumerator();
return enumerator;
}
public virtual IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
IQueryable<TElement> queryable = provider.CreateQuery<TElement>(expression);
return new InterceptingQuery<TElement>(queryable, this);
}
public virtual IQueryable CreateQuery(Expression expression)
{
IQueryable queryable = provider.CreateQuery(expression);
Type elementType = queryable.ElementType;
Type queryType = typeof(InterceptingQuery<>).MakeGenericType(elementType);
return (IQueryable)Activator.CreateInstance(queryType, queryable, this);
}
public virtual TResult Execute<TResult>(Expression expression)
{
return provider.Execute<TResult>(expression);
}
public virtual object Execute(Expression expression)
{
return provider.Execute(expression);
}
}
Then I created a class to wrap the actual IQuerable
. This class sends any calls to the provider. This way calls to Where
, Select
, etc. get passed to the underlying provider.
internal class InterceptingQuery<TElement> : IQueryable<TElement>
{
private readonly IQueryable queryable;
private readonly InterceptingProvider provider;
public InterceptingQuery(IQueryable queryable, InterceptingProvider provider)
{
this.queryable = queryable;
this.provider = provider;
}
public IQueryable<TElement> Include(string path)
{
return new InterceptingQuery<TElement>(queryable.Include(path), provider);
}
public IEnumerator<TElement> GetEnumerator()
{
Expression expression = queryable.Expression;
return provider.ExecuteQuery<TElement>(expression);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Type ElementType
{
get { return typeof(TElement); }
}
public Expression Expression
{
get { return queryable.Expression; }
}
public IQueryProvider Provider
{
get { return provider; }
}
}
Notice that this class implements a method called Include
. This allows the System.Data.Entity.QueryableExtensions.Include
methods to work against the wrapper.
At this point, we just need a subclass of InterceptingProvider
that can actually wrap the exceptions that get thrown.
internal class WrappedProvider<TException> : InterceptingProvider
where TException : Exception
{
private readonly Func<TException, Exception> wrapper;
internal WrappedProvider(IQueryProvider provider, Func<TException, Exception> wrapper)
: base(provider)
{
this.wrapper = wrapper;
}
public override IEnumerator<TElement> ExecuteQuery<TElement>(Expression expression)
{
return Check(() => wrapEnumerator<TElement>(expression), wrapper);
}
private IEnumerator<TElement> wrapEnumerator<TElement>(Expression expression)
{
IEnumerator<TElement> enumerator = base.ExecuteQuery<TElement>(expression);
return new WrappedEnumerator<TElement>(enumerator, wrapper);
}
public override TResult Execute<TResult>(Expression expression)
{
return Check(() => base.Execute<TResult>(expression), wrapper);
}
public override object Execute(Expression expression)
{
return Check(() => base.Execute(expression), wrapper);
}
internal static TResult Check<TResult>(Func<TResult> action, Func<TException, Exception> wrapper)
{
try
{
return action();
}
catch (TException exception)
{
throw wrapper(exception);
}
}
private class WrappedEnumerator<TElement> : IEnumerator<TElement>
{
private readonly IEnumerator<TElement> enumerator;
private readonly Func<TException, Exception> wrapper;
public WrappedEnumerator(IEnumerator<TElement> enumerator, Func<TException, Exception> wrapper)
{
this.enumerator = enumerator;
this.wrapper = wrapper;
}
public TElement Current
{
get { return enumerator.Current; }
}
public void Dispose()
{
enumerator.Dispose();
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
return WrappedProvider<TException>.Check(enumerator.MoveNext, wrapper);
}
public void Reset()
{
enumerator.Reset();
}
}
}
Here I am just overriding the ExecuteQuery
and Execute
methods. In the case of Execute
, the underlying provider is executed immediately and I catch and wrap any exceptions. As for ExecuteQuery
, I create an implementation of IEnumerator
that wraps exceptions as @Moho suggested.
The only thing missing is the code to actually create the WrappedProvider
. I created a simple extension method.
public static class QueryWrappers
{
public static IQueryable<TElement> Handle<TElement, TException>(this IQueryable<TElement> source, Func<TException, Exception> wrapper)
where TException : Exception
{
return WrappedProvider<TException>.Check(() => handle(source, wrapper), wrapper);
}
private static IQueryable<TElement> handle<TElement, TException>(IQueryable<TElement> source, Func<TException, Exception> wrapper)
where TException : Exception
{
var provider = new WrappedProvider<TException>(source.Provider, wrapper);
return provider.CreateQuery<TElement>(source.Expression);
}
}
I tested this code in a handful of scenarios to see if I could break something: SQL Server turned off; Single
on a table with multiple records; Include
-ing a non-existent table; etc. It appeared to work in every case with no unwanted side-effects.
Since the InterceptingProvider
class is abstract, it can be used to create other types of invisible IQueryProvider
s. You can recreate the code in AlexJ's blog with very little work.
The nice thing is that I don't feel weary about exposing IQuerable
from my data layer anymore. Now the business layer can mess with the IQueryable
all it wants and there's no risk of violating encapsulation because of an Entity Framework exception escaping.
The only thing I like to do is make sure the exception gets wrapped with a message indicating what operation failed; e.g., "An error occurred. Could not retrieve the requested user." I like to wrap the IQueryable
in the data layer, but I don't know what the business logic will do to it until later. So I make the business logic responsible for telling the data layer what its intentions are. Passing an error message string to the data layer, just in case, is a bit of a pain, but this is still better than defining a distinct repository method for every possible query and rewriting the same error handling logic 100 times over.