System.Data.IDbCommand 和异步执行

本文关键字:异步 执行 IDbCommand Data System | 更新日期: 2023-09-27 18:22:19

System.Data.SqlClient.SqlCommand有方法

BeginExecuteNonQuery
BeginExecuteReader
BeginExecuteXmlReader

EndExecuteNonQuery
EndExecuteReader
EndExecuteXmlReader

用于异步执行。

System.Data.IDbCommand只有

ExecuteNonQuery
ExecuteReader
ExecuteXmlReader

仅用于同步操作。

是否有用于异步操作的接口 ?
另外,为什么没有 BeginExecuteScalar ?

System.Data.IDbCommand 和异步执行

我建议在使用数据库API时将DbCommand及其朋友视为接口。为了在各种数据库提供程序上推广 API,DbCommand实现了与IDbCommand一样好——或者可以说更好,因为它包含了更新的技术,例如适当的await能够Task *Async()成员。

MS 无法向IDbCommand添加任何具有新功能的新方法。如果他们要向IDbCommand添加一个方法,这是一个重大变化,因为任何人都可以在他们的代码中自由实现该接口,并且 MS 已经付出了很多努力来保持框架中的 ABI 和 API 兼容性。如果他们在 .net 版本中扩展接口,以前工作的客户代码将停止编译,并且未重新编译的现有程序集将开始遇到运行时错误。此外,他们无法通过扩展方法添加适当的*Async()Begin*()方法,而无需在幕后进行丑陋的强制DbCommand(这本身就是一种不好的做法,会破坏类型安全性并不必要地引入动态运行时强制转换(。

另一方面,MS 可以在不破坏 ABI 的情况下向DbCommand添加新的虚拟方法。向基类添加新方法可能会被视为破坏 API(编译时,不像运行时那样糟糕(,因为如果您继承了DbCommand并添加了具有相同名称的成员,您将开始收到警告 CS0108:"member1"隐藏继承的成员"member2"。如果打算隐藏,请使用 new 关键字。因此,DbCommand可以获得新功能,而对遵循良好实践的代码的使用影响最小(例如,只要它不反对类型系统并使用类似 myCommand.GetType().GetMethods()[3].Invoke(myCommand, …) 的东西调用方法,大多数东西就会继续工作(。

MS可以用来支持喜欢接口的人的一个可能策略是引入名称为IAsyncDbCommand的新接口,并DbCommand实现它们。他们没有这样做。我不知道为什么,但他们可能没有这样做,因为这会增加复杂性,而直接使用DbCommand的替代方案提供了使用接口的大部分好处,几乎没有缺点。也就是说,这将是几乎没有回报的工作。

IDbCommand没有

开始/结束异步方法,因为它们在 ADO.NET 的原始 .NET 1.1 版本中尚不存在,并且在 .NET 2.0 中添加异步方法时,将这些方法添加到IDbCommand将是一项重大更改(向接口添加成员是该接口实现者的重大更改(。

我不知道为什么BeginExecuteScalar不存在,但它可以作为环绕BeginExecuteReader的扩展方法实现。无论如何,在.NET 4.5中,我们现在有了ExecuteScalarAsync它更易于使用。

实际上,创建等同于 BeginExecuteNonQuery、EndExecuteNonQuery 等的异步行为将是相当困难的任务。这些 API 的实现远远优于简单地生成单独的线程、等待数据库响应和调用回调。它们依赖于 I/O 重叠,并提供更好的线程经济性。在网络跃点期间不消耗额外的线程,命令的数据库处理 - 这可能是调用所花费总时间的 99%。对于几次调用,这没有什么区别,但是当您设计高吞吐量服务器时,线程经济性变得非常重要。

我想知道为什么BeginExecuteScalar不见了。此外,大多数其他提供商(例如 ODP.Net(根本没有异步 API!

是的,没有用于异步操作的接口。

为了解决这个问题,我构建了一个填充程序,如果它们存在于 IDbConnection.IDbCommand/IDataReader 上,则调用异步方法,如果它们不存在,则调用常规方法。

源:https://github.com/ttrider/IDbConnection-Async

NuGet:https://www.nuget.org/packages/IDbConnection-Async/

例:

        using (IDbConnection connection = new SqlConnection(connectionString))
        {
            await connection.OpenAsync();
            IDbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT Name FROM Person;";
            using (IDataReader reader = await command.ExecuteReaderAsync())
            {
                do
                {
                    while (await reader.ReadAsync())
                    {
                        if (!await reader.IsDBNullAsync(0))
                        {
                            var name = reader.GetFieldValueAsync<string>(0);
                            Assert.IsNotNull(name);
                        }
                    }
                } while (await reader.NextResultAsync());
            }
        }

即使您检索"一个值",大部分时间也将花费在 1( 到数据库服务器的网络跃点上,2( 数据库服务器执行命令。比您花费在将 1000 条记录读取到数据集上花费的时间要多得多。所以,我同意,目前还不清楚为什么没有BeginExecuteScalar...

当我需要将数据调用迁移到异步方法时,我偶然发现了这个问题。我已经为未来的 .NET 标准创建了一个问题,以合并异步接口。同时,我还创建了一个库,其中包含一组用于 System.Data 的接口和适配器。

您可以通过自定义代码实现异步行为,因为它并不复杂,至于您的问题 - 您的目标没有任何标准的异步操作。

不,

没有接口

没有BeginExecuteScalar的原因是,您可能不需要异步调用来获取一个应该非常快的单个值