與@莫霍面的回答的問題是,它取代了底層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在那裏你會找到這個代碼的最新版本。 –