与@莫霍面的回答的问题是,它取代了底层IQueryable
。当您简单地包装IQueryable
时,它会影响生成的最终Expression
。如果立即包装ISet<T>
,它将打破电话Include
。此外,它可以影响其他操作发生的方式/时间。所以解决方案实际上有一点涉及。
在寻找解决方案时,我遇到了这个博客:http://blogs.msdn.com/b/alexj/archive/2010/03/01/tip-55-how-to-extend-an-iqueryable-by-wrapping-it.aspx。不幸的是,这个例子有点破,但很容易修复(和改进)。下面,我张贴我写的代码。
第一类是抽象基类,它可以创建不同类型的包装。 LINQ使用IQueryProvider
将LINQ表达式转换为可执行代码。我创建了一个IQueryProvider
,它只是将调用传递给底层提供者,使其基本上不可见。
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);
}
}
然后我创建了一个类来包装实际的IQuerable
。这个类将任何调用发送给提供者。这种方式调用Where
,Select
等传递给底层提供者。
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; }
}
}
请注意,该类实现了一种名为Include
的方法。这允许System.Data.Entity.QueryableExtensions.Include
方法针对包装。
在这一点上,我们只需要一个InterceptingProvider
的子类,它可以实际包装抛出的异常。
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();
}
}
}
在这里,我只是重写ExecuteQuery
和Execute
方法。在Execute
的情况下,底层提供程序立即执行,并捕获并包装所有异常。至于ExecuteQuery
,我创建了一个实现IEnumerator
,它将@Moho建议的异常封装起来。
唯一缺少的是实际创建WrappedProvider
的代码。我创建了一个简单的扩展方法。
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);
}
}
我在少数情况下测试了这段代码,看看我是否可以破坏某些东西:SQL Server关闭;在具有多个记录的表格上的Single
; Include
-ing一个不存在的表;它似乎在每种情况下都起作用,没有不需要的副作用。
由于InterceptingProvider
类是抽象类,它可以用来创建其他类型的不可见IQueryProvider
s。你可以用很少的工作在AlexJ的博客中重新创建代码。
好的是,我不再为从我的数据层中暴露IQuerable
而感到厌倦。现在,业务层可能会混淆IQueryable
的所有要求,并且由于实体框架异常转义,不存在违反封装的风险。
我唯一想做的事情就是确保异常被一个消息指示什么操作失败;例如,“发生错误,无法检索请求的用户”。我喜欢将IQueryable
包装在数据层中,但我不知道业务逻辑会在后面做些什么。所以我让业务逻辑负责告诉数据层它的意图是什么。为了以防万一,将错误消息字符串传递给数据层有点痛苦,但这比为每个可能的查询定义独特的存储库方法并重写100次相同的错误处理逻辑要好得多。
这太好了。我能够在没有太多麻烦的情况下使用旧的orm Subsonic的IQueryable来添加重试。看来我必须使InterceptingQuery实现IOrderedQueryable而不是IQueryable。 –
我也有一个开源项目:https://github.com/jehugaleahsa/QueryableInterceptors在那里你会找到这个代码的最新版本。 –