如何在C#中捕获原始(内部)异常?

如何在C#中捕获原始(内部)异常?,c#,exception,C#,Exception,我正在调用一个抛出自定义异常的函数: GetLockOwnerInfo(...) GetLockOwnerInfo(...) ExecuteReader(...) GetLockOwnerInfo(...) ExecuteReader(...) ExecuteReader(...) try { DoSomething(); } catch (SqlException e) { if (e.Number = 247) { return

我正在调用一个抛出自定义异常的函数:

GetLockOwnerInfo(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}
此函数依次调用引发异常的函数:

GetLockOwnerInfo(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}
此函数依次调用引发异常的函数:

GetLockOwnerInfo(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}
等等:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
         ExecuteReaderClient(...)
             Fill(...)
其中一个函数抛出一个
SqlException
,尽管该代码不知道什么是
SqlException

更高级别将
SqlException
包装到另一个
BusinessRuleException
中,以包含一些特殊属性和附加详细信息,同时将“原始”异常包含为
InnerException

catch (DbException ex)
{
    BusinessRuleExcpetion e = new BusinessRuleException(ex)
    ...
    throw e;
}
catch (BusinessRuleException ex)
{
    LockerException e = new LockerException(ex)
    ...
    throw e;
}
更高级别将
BusinessRuleException
包装到另一个
LockerException
中,以包含一些特殊属性和附加详细信息,同时将“原始”异常作为
InnerException

catch (DbException ex)
{
    BusinessRuleExcpetion e = new BusinessRuleException(ex)
    ...
    throw e;
}
catch (BusinessRuleException ex)
{
    LockerException e = new LockerException(ex)
    ...
    throw e;
}
现在的问题是,我想要捕获原始的
SqlException
,以检查特定的错误代码

但无法“捕获内部异常”:

我考虑在抛出时正确捕获
SqlException
,并将各种值复制到重新抛出的异常中,但该代码并不依赖于Sql。它正在经历一个
SqlException
,但它不依赖于SqlException

我考虑捕获所有异常:

try
{
   DoSomething(...);
}
catch (Exception e)
{
   SqlException ex = HuntAroundForAnSqlException(e);
   if (ex != null)
   {
      if (e.Number = 247) 
      {
          return "Someone";
      }
      else
         throw;
   }
   else
      throw;
}
但这是可怕的代码


鉴于.NET不允许您更改
异常的
消息
以包含附加信息,那么捕获原始异常的预期机制是什么?

检查已包装异常的错误代码不是一个好做法,因为它严重损害了封装。想象一下,在某个时刻重写逻辑以从非SQL源(例如web服务)读取。在相同的条件下,它会抛出除
SQLException
之外的内容,而您的外部代码将无法检测到它

您应该将代码添加到块捕获
SQLException
中,以便立即检查
e.Number=247
,并抛出
BusinessRuleException
,该属性与响应非
SQLException
而抛出的
BusinessRuleException
SQLException
e.Number!=247以某种有意义的方式。例如,如果神奇的数字
247
意味着你遇到了一个重复的数字(这纯粹是我的猜测),你可以这样做:

catch (SQLException e) {
    var toThrow = new BusinessRuleException(e);
    if (e.Number == 247) {
        toThrow.DuplicateDetected = true;
    }
    throw toThrow;
}
public BusinessRuleException(Exception inner)
:   base(inner) {
    SetDuplicateDetectedFlag(inner);
}

public BusinessRuleException(string message, Exception inner)
:   base(message, inner) {
    SetDuplicateDetectedFlag(inner);
}

private void SetDuplicateDetectedFlag(Exception inner) {
    var innerSql = inner as SqlException;
    DuplicateDetected = innerSql != null && innerSql.Number == 247;
}
稍后捕获
BusinessRuleException
时,可以检查其
DuplicateDetected
属性,并采取相应的行动

编辑1(回应DB读取代码无法检查
SQLException
的评论)

您还可以更改
BusinessRuleException
以在其构造函数中检查
SQLException
,如下所示:

catch (SQLException e) {
    var toThrow = new BusinessRuleException(e);
    if (e.Number == 247) {
        toThrow.DuplicateDetected = true;
    }
    throw toThrow;
}
public BusinessRuleException(Exception inner)
:   base(inner) {
    SetDuplicateDetectedFlag(inner);
}

public BusinessRuleException(string message, Exception inner)
:   base(message, inner) {
    SetDuplicateDetectedFlag(inner);
}

private void SetDuplicateDetectedFlag(Exception inner) {
    var innerSql = inner as SqlException;
    DuplicateDetected = innerSql != null && innerSql.Number == 247;
}

这是不可取的,因为它破坏了封装,但至少它在一个地方做到了。如果您需要检查其他类型的异常(例如,因为您添加了一个web服务源),您可以将其添加到
SetDuplicateDetectedFlag
方法中,然后一切都会再次工作。

我不想告诉您这一点,但您无法捕获内部异常

你能做的就是检查一个

我建议您捕获高级异常(我相信它是
LockerException
),并检查该异常的
InnerException
属性。检查类型,如果不是
SqlException
,请检查该异常的
InnerException
。遍历每一个,直到找到
SqlException
类型,然后获取所需的数据


那,我同意DasBrink的光,你应该考虑——如果可能的话——对你的异常框架的一个重大重构。 让外部应用程序层关心包装异常的细节是一种代码气味;包装越深,气味越大。您现在拥有的将

SqlException
包装成
dbException
的类可能是为了将SqlClient公开为通用数据库接口而设计的。因此,该类别应包括区分不同例外情况的方法。例如,它可以定义dbTimeoutWaitingForLockException,并在捕获SqlException并基于其错误代码确定存在锁定超时时决定抛出它。在vb.net中,使用dbException类型公开ErrorCause枚举可能更为简洁,因此当Ex.Cause=DbErrorCause.LockTimeout
时,可以说
将Ex捕获为dbException,但不幸的是,异常过滤器在C#中不可用


如果内部类包装器不知道如何映射异常,那么让内部类方法接受一个异常包装委托可能会有帮助,该委托将接受内部类捕获或“希望”抛出的异常,并以适合外部类的方式包装它。在直接从外部类调用内部类的情况下,这种方法可能会有些过分,但如果涉及到中间类,这种方法可能会很有用。

问得好,回答得好

我只想用一些进一步的想法补充已经给出的答案:

一方面,我同意dasblinkenlight和其他用户的观点。如果捕获一个异常以重新引发另一类型的异常,并将原始异常设置为内部异常,那么除了维护方法的约定外,您不应该出于其他原因而这样做。(访问SQL server是调用方不知道/必须不知道/无法知道的一个实现细节,因此调用方无法预料将抛出
SqlException
(或
DbException

应用这项技术