2013-12-17 56 views
2

我在業務邏輯中公開IQueryable的最大問題是它可能會在我的業務邏輯中拋出一個實體框架異常。我認爲這是一個問題,因爲我的業務層需要知道我正在使用實體框架 - 或者 - 我必須捕獲一個非常通用的異常。IQueryable包裝異常

相反,我想創建一個IQueryable捕獲實體框架異常並將它們轉換爲我的數據層異常類型。

最後,我想我的代碼看起來像這樣:然後

public IQueryable<Customer> GetCustomers() 
{ 
    var customers = from customer in dbContext.Customers 
        where customer.IsActive 
        select customer; 
    return customers.WrapErrors(ex => new DataLayerException("oops", ex); 
} 

客戶將能夠增加額外的LINQ條款。如果發生錯誤(數據庫關閉),則原始異常將被DataLayerException包裝。

回答

2

與@莫霍面的回答的問題是,它取代了底層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。這個類將任何調用發送給提供者。這種方式調用WhereSelect等傳遞給底層提供者。

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(); 
     } 
    } 
} 

在這裏,我只是重寫ExecuteQueryExecute方法。在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次相同的錯誤處理邏輯要好得多。

+0

這太好了。我能夠在沒有太多麻煩的情況下使用舊的orm Subsonic的IQueryable來添加重試。看來我必須使InterceptingQuery實現IOrderedQueryable而不是IQueryable。 –

+0

我也有一個開源項目:https://github.com/jehugaleahsa/QueryableInterceptors在那裏你會找到這個代碼的最新版本。 –

1

創建一個包裝的IQueryable<T>,並實現了自己IEnumerator<T>包裝類,允許你拋出的異常轉換上可能會拋出異常調用(如在我的例子MoveNext(),這將覆蓋大多數,如果不是所有的問題)。例如:

class Program 
{ 
    static void Main(string[] args) 
    { 
     using(var context = new TestContext()) 
     { 
      for(int i = 0; i < 2; ++i) 
      { 
       IQueryable<EntityA> query = context.EntityAs.Include("NoSuchProperty"); 

       if(i == 1) 
       { 
        query = query.WrapErrors(ex => new ExceptionWrapper("Test 123", ex)); 
       } 

       try 
       { 
        var list = query.ToList(); 
       } 
       catch(Exception ex) 
       { 
        Console.WriteLine(ex.GetType()); 
        //Console.WriteLine(ex); 
       } 
      } 
     } 

     Console.ReadKey(); 
    } 
} 

public static class ExtensionMethods 
{ 
    public static IQueryable<T> WrapErrors<T>(this IQueryable<T> query, Func<Exception, Exception> exceptionConversion) 
    { 
     return new QueryWrapper<T>(query, exceptionConversion); 
    } 
} 

public class QueryWrapper<T> : IQueryable<T> 
{ 
    private IQueryable<T> _query; 
    private Func<Exception, Exception> _exceptionConversion; 

    public QueryWrapper(IQueryable<T> query, Func<Exception, Exception> exceptionConversion) 
    { 
     if(null == query) 
     { 
      throw new ArgumentNullException("query"); 
     } 

     _query = query; 
     _exceptionConversion = exceptionConversion; 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return new QueryWrapperEnumerator(_query, _exceptionConversion); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    public Type ElementType 
    { 
     get 
     { 
      return _query.ElementType; 
     } 
    } 

    public System.Linq.Expressions.Expression Expression 
    { 
     get 
     { 
      return _query.Expression; 
     } 
    } 

    public IQueryProvider Provider 
    { 
     get 
     { 
      return _query.Provider; 
     } 
    } 

    public class QueryWrapperEnumerator : IEnumerator<T> 
    { 
     IEnumerator<T> _enumerator; 
     public Func<Exception, Exception> _exceptionConversion; 

     public QueryWrapperEnumerator(IQueryable<T> query, Func<Exception, Exception> exceptionConversion) 
     { 
      if(null == query) 
      { 
       throw new ArgumentNullException("query"); 
      } 

      _enumerator = query.GetEnumerator(); 
      _exceptionConversion = exceptionConversion; 
     } 

     public T Current 
     { 
      get 
      { 
       return _enumerator.Current; 
      } 
     } 

     public void Dispose() 
     { 
      _enumerator.Dispose(); 
     } 

     object System.Collections.IEnumerator.Current 
     { 
      get 
      { 
       return _enumerator.Current; 
      } 
     } 

     public bool MoveNext() 
     { 
      try 
      { 
       return _enumerator.MoveNext(); 
      } 
      catch(Exception ex) 
      { 
       if(null == _exceptionConversion) 
       { 
        throw; 
       } 

       throw _exceptionConversion.Invoke(ex); 
      } 
     } 

     public void Reset() 
     { 
      _enumerator.Reset(); 
     } 
    } 
} 

public class EntityA 
{ 
    [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)] 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

public class TestContext : DbContext 
{ 
    public DbSet<EntityA> EntityAs { get; set; } 

    public TestContext() 
    { 
     Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>()); 
    } 
} 

public class DropCreateDatabaseAlwaysInitializer<T> : DropCreateDatabaseAlways<T> where T : DbContext 
{ 
    protected override void Seed(T context) 
    { 
    } 
} 
+0

你遇到了我做過的同樣的事情。 'GetEnumerator'是否會拋出異常或'MoveNext'?這個電話可以提早來到,還是必須最後來電? –

+0

「MoveNext」引發異常。創建一個新的控制檯應用程序並複製/粘貼整個示例以查看結果。 – Moho

+0

當我關閉SQL Server時,實際上有一個異常拋出'IQueryable.Provider'。如果我移動了這段代碼,它改變了像'Include'這樣的事情(不填充導航屬性等)。 –