2013-12-17 54 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'这样的事情(不填充导航属性等)。 –