创建自定义异常时如何保留原始异常的完整堆栈追踪?
自定义仓储异常时保留原始堆栈追踪的方法
需求背景
为仓储定义统一的自定义异常,让应用层无需关心仓储的实现技术,就能接收一致的异常类型。
当前代码实现
仓储代码
public async Task<MyType?> GetDataAsync(long paramLgId) { try { MyType? myResult = await _context.MyType .FirstOrDefaultAsync(x => x.Id == paramLgId); return myResult; } catch(Exception ex) { throw EntityCoreExceptionsHelper.GetExcepcion(ex); } }
异常辅助类代码
public static class EntityCoreExceptionsHelper { public static Exception GetExcepcion(Exception paramExcepcion) { SqlException? myExcepcionSql = paramExcepcion as SqlException; if(myExcepcionSql != null) { if(myExcepcionSql.Number == -1) { return new BaseDeDatosNoAccesibleException("Server not available.", myExcepcionSql); } else if (myExcepcionSql.Number == 4060) { return new BaseDeDatosNoAccesibleException("DataBaseNotAvailable.", myExcepcionSql); } return new ExcepcionIndeterminadaException("Unexpected exception.", myExcepcionSql); } return new ExcepcionIndeterminadaException("Unexpected exception.", paramExcepcion); } }
当前问题
使用throw EntityCoreExceptionsHelper.GetExcepcion(ex);抛出异常时,堆栈追踪仅指向仓储类的catch代码行,而非触发查询的原始异常行。虽可从内部异常中获取原始行,但需多层遍历,操作繁琐。需要让上层接收的新异常直接包含完整堆栈追踪(含原始异常行),无需遍历内部异常。
解决方案
方案1:结合ExceptionDispatchInfo捕获原始异常上下文
通过ExceptionDispatchInfo保留原始异常的调用栈信息,再合并到自定义异常中:
修改异常辅助类
using System.Runtime.ExceptionServices; public static class EntityCoreExceptionsHelper { public static void ThrowCustomException(Exception paramExcepcion) { Exception customException; SqlException? myExcepcionSql = paramExcepcion as SqlException; if(myExcepcionSql != null) { customException = myExcepcionSql.Number switch { -1 => new BaseDeDatosNoAccesibleException("Server not available.", myExcepcionSql), 4060 => new BaseDeDatosNoAccesibleException("DataBaseNotAvailable.", myExcepcionSql), _ => new ExcepcionIndeterminadaException("Unexpected exception.", myExcepcionSql) }; } else { customException = new ExcepcionIndeterminadaException("Unexpected exception.", paramExcepcion); } // 捕获原始异常上下文,合并堆栈信息 var exceptionInfo = ExceptionDispatchInfo.Capture(paramExcepcion); customException.StackTrace += $"\n---原始异常堆栈---\n{paramExcepcion.StackTrace}"; // 抛出时保留原始上下文 exceptionInfo.Throw(); throw customException; // 仅为编译通过,实际不会执行到 } }
修改仓储类的catch块
public async Task<MyType?> GetDataAsync(long paramLgId) { try { MyType? myResult = await _context.MyType .FirstOrDefaultAsync(x => x.Id == paramLgId); return myResult; } catch(Exception ex) { EntityCoreExceptionsHelper.ThrowCustomException(ex); } }
方案2:自定义异常构造时合并堆栈
在自定义异常的构造函数中直接合并原始异常的堆栈信息:
自定义异常类示例
public class BaseDeDatosNoAccesibleException : Exception { public BaseDeDatosNoAccesibleException(string message, Exception innerException) : base(message, innerException) { if(innerException != null) { StackTrace += $"\n---原始异常堆栈---\n{innerException.StackTrace}"; } } } // 其他自定义异常类可参照此逻辑实现 public class ExcepcionIndeterminadaException : Exception { public ExcepcionIndeterminadaException(string message, Exception innerException) : base(message, innerException) { if(innerException != null) { StackTrace += $"\n---原始异常堆栈---\n{innerException.StackTrace}"; } } }
此方案下,仓储类和异常辅助类的代码无需额外修改,新异常的堆栈信息会自动包含原始异常的堆栈。
关键说明
ExceptionDispatchInfo.Capture会捕获原始异常的完整上下文(含堆栈、线程信息),调用Throw()时保留这些信息,手动追加堆栈可让新异常直接展示完整调用链。- 若自定义异常需要序列化,需重写
GetObjectData方法,确保合并后的堆栈信息能被正确序列化。
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

