You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

创建自定义异常时如何保留原始异常的完整堆栈追踪?

自定义仓储异常时保留原始堆栈追踪的方法

需求背景

为仓储定义统一的自定义异常,让应用层无需关心仓储的实现技术,就能接收一致的异常类型。

当前代码实现

仓储代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:22:52