何时使用Microsoft.Data.SqlClient.SqlException而不是System.Data.SqlClient.SqlException?

19

我知道大约在2019年,Microsoft创建了Microsoft.Data.SqlClient作为System.Data.SqlClient的替代品。System.Data.SqlClient将继续受到支持,但新的开发和功能将全部在Microsoft.Data.SqlClient中进行。这两个库都有一个名为 'SqlException' 的类。

假设我在所有地方都使用Microsoft.Data.SqlClient,那么相关的异常应该是Microsoft.Data.SqlClient.SqlException类型,但是我使用了许多第三方库,我怎么确定它们是否会引发Microsoft.Data.SqlClient.SqlExceptionSystem.Data.SqlClient.SqlException?这是否意味着在我有一些为Microsoft.Data.SqlClient.SqlException编写的catch处理程序的情况下,我也应该检查System.Data.SqlClient.SqlException?还是有什么巧妙的方法意味着我只需要考虑Microsoft.Data.SqlClient.SqlException?

例如,我有一些旧代码,类似于下面所示的代码,在我们开始使用Microsoft.Data.SqlClient之前编写。我担心如果我简单地将其更改为使用Microsoft.Data.SqlClient,那么会有一些异常是System.Data.SqlClient.SqlException,而我的代码将不再注意到它们。

    private static bool HandleSqlExceptionInSomeWay(Exception ex)
    {
        var se = ex as System.Data.SqlClient.SqlException;

        if (se != null)
        {
            // ... do something with the SqlException
            return true;
        }

        return false;
    }

那么我应该将它改成像这样,即分别检查不同类型吗?

    private static bool HandleSqlExceptionInSomeWay(Exception ex)
    {
        // handle old SqlExceptions (e.g. from any old 
        // libraries not using MS package yet)
        var se1 = ex as System.Data.SqlClient.SqlException;

        if (se1 != null)
        {
            // ... do something with the SqlException ...

            return true;
        }

        // handle shiny new SqlExceptions 
        var se2 = ex as Microsoft.Data.SqlClient.SqlException;

        if (se2 != null)
        {
            // ... do something with the SqlException ... 

            return true;
        }

        return false;
    }

尽可能使用新的库。大多数NuGet包已经使用了Microsoft.Data.SqlClient.SqlException - Panagiotis Kanavos
我怎样才能确定他们使用了哪个包?如果他们使用的是旧库,请升级到使用新库的版本。 - Panagiotis Kanavos
我猜如果你只捕获 Microsoft.Data.SqlClient.SqlException,那么如果依赖项抛出 System.Data.SqlClient.SqlException,你的“全局日志处理程序”也会开始看到它? - Caius Jard
如果有帮助的话,它们都继承自System.Data.Common.DbException - DavidG
1
从理论上讲,库应该记录其方法可能抛出的异常类型(例如,使用<exception cref="..."> xmldoc)。这将是方法合同的一部分,因此更改它将是一个破坏性的变化,需要在语义版本控制中使用新的主要版本号,并在发布说明中提到。然而,在实践中,理论和实践并不总是一致的... - Heinzi
2
顺便提一下,你的 var se1 = ex as System.Data.SqlClient.SqlException; if (se1 != null) 可以更易读地写成 if(ex is System.Data.SqlClient.SqlException sex) ... - Caius Jard
3个回答

12

这两个类不同,但它们都继承自同一个基类DbException。这是所有数据库异常的通用类,但不会拥有这两个派生类中的所有属性。

您应该检查使用的库/NuGet包,并确保使用支持新Microsoft.Data.SqlClient库的版本。混合使用数据提供程序不是好的做法,尽可能避免。大多数流行的NuGet包已经使用了Microsoft.Data.SqlClient。

如果您无法这样做,则选项取决于您实际处理数据库异常的方式。您是否检查SQL Server特定的属性?

另一个选项是推迟升级,直到所有NuGet包都升级为止。这两个库都包含需要在部署期间包含的本地DLL。如果您混合使用库,则必须包含所有本地DLL。

这可能很痛苦。

处理异常

如果需要使用这两个库,每种异常类型都需要单独处理。模式匹配可以使此过程变得更加容易:

switch (ex) 
{
    case  System.Data.SqlClient.SqlException exc:
        HandleOldException(exc);
        return true;
    case Microsoft.Data.SqlClient.SqlException exc:
        HandleNewException(exc);
        return true;
    case DbException exc:
        HandleDbException(exc);
        return true;
    default:
        return false;
}

映射异常

另一种选择是将这两种异常类型映射到一个包含有趣属性的新自定义类型中。您需要映射SqlExceptionSqlError类。使用AutoMapper可以使此过程更加简便:

var configuration = new MapperConfiguration(cfg => {
    cfg.CreateMap<System.Data.SqlClient.SqlException, MySqlException>();
    cfg.CreateMap<System.Data.SqlClient.SqlError, MySqlError>();
    cfg.CreateMap<Microsoft.Data.SqlClient.SqlException, MySqlException>();
    cfg.CreateMap<Microsoft.Data.SqlClient.SqlError, MySqlError>();
});

这将允许将任何异常映射到常见的MySqlException类型:

var commonExp=mapper.Map<MySqlException>(ex);

如果你有一个两年内没有更新的库,考虑升级到最新版本(如果可用),或者考虑摆脱它。 - Camilo Terevinto
谢谢,但是有没有办法知道一个包是否使用了新库,而不需要它是开源的并且我检查代码? - Rory
2
@Rory NuGet包可以提供依赖项列表。如果您没有使用NuGet包,请通过ILSpy打开dll文件,这将为您提供依赖关系。 - Camilo Terevinto
@CamiloTerevinto - 对于许多内部应用程序来说,两年前的库并不罕见,升级或更改库的成本也很高昂。 - Rory
肯定catch (System.Data.SqlClient.SqlException ex) {...} catch (Microsoft.Data.SqlClient.SqlException ex) {...}比模式匹配更有意义,不是吗? - Charlieface
显示剩余3条评论

4

进一步解释Richard的回答,它意味着你的try/catch看起来像这样:

try{
  //boom
} catch(Microsoft.Data.SqlClient.SqlException ex){
  Handler(ex);
} catch (System.Data.SqlClient.SqlException ex) {
  Handler(ex);
}

如果您想同时处理多种类型的异常...

处理程序是一种可访问的重载(或父类型参数)方法,用于处理每种异常。虽然不太美观,但除非所有类型都具有相同可用的父类型可供捕获(然后必要时检查异常类型),否则一个catch无法捕获多个不同类型的异常。

编辑:完全忘记了catch when,对于这种情况很有用,谢谢@Heinzi

try{
  //boom
} catch (DbException ex) 
  when (ex is Microsoft.Data.SqlClient.SqlException || ex is System.Data.SqlClient.SqlException)
{
  //handle
}

1
或者,如果您只想要一个catch块,并且不需要特定的异常属性:catch(Exception ex)when(ex is Microsoft.Data.SqlClient.SqlException || ex is System.Data.SqlClient.SqlException) - Heinzi
2
@Heinzi 在那个时候,我会将它们重命名为 using OldSqlException = System.Data.SqlClient.SqlException; using NewSqlException = Microsoft.Data.SqlClient.SqlException; :D - Camilo Terevinto
我认为最后一个不行,因为你会收到一个编译器错误,它可能没有被初始化。说实话,我最喜欢第一个版本,一个具体类型的异常处理程序。Handler 方法可以使用一个继承自 System.Data.Common.DbException(已经有 ErrorCode)的包装类来映射两种 SqlException 类型中共同拥有的属性。 - Tim Schmelter
@TimSchmelter 我没有测试过,但我过去使用相同的语法来区分不同包中的DTO。我也更喜欢Handler在内部处理这个问题。 - Camilo Terevinto
你知道使用K&R分支风格格式化C#代码在.NET世界中完全不标准,这使得整个社区读取你的代码示例和与其一起工作更加困难吗? - julealgon

1

如果您不能确定第三方库是使用Microsoft还是System库,您需要同时处理两个库。没有“魔法酱汁”可以将一个库的SqlException转换为另一个库的SqlException


2
谢谢。我希望那个神奇的酱料被更广泛地使用,它很美味。 - Rory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接