如何在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
)
应用这项技术