.NET 6中的数据库命令批处理
从一开始,.NET的一个核心功能就是被称为ADO.NET或System.Data的数据库访问框架。这个框架的目的是为了确保所有的数据库都能以一致的方式被访问。然而,偶尔也会有一些需要解决的差距。
其中一个差距是在一个批次中发送多个SQL语句的能力。虽然有些数据库(如SQL Server)允许用分号分隔语句,但这并不是所有数据库都能做到的。Shay Rojansky写道:
这种方法的问题是,大多数数据库要求每条语句有单独的协议信息(如PostgreSQL、MySQL),迫使数据库的ADO.NET提供者在客户端解析SQL,并在分号上分割。这既不可靠(解析SQL很困难),也不利于性能--理想情况下,ADO.NET提供者应该简单地将用户提供的SQL转发给数据库,而不用解析或重写它。
甚至SQL Server也有局限性,比如不支持在一个批次中调用多个存储过程。
.NET 6对此的解决方案是新的DbBatch类。批处理API建议中的目标是:
- 提供一种结构化的方式来在一次往返中执行多个SQL语句,而不需要在客户端解析SQL。
- 使API与其他ADO.NET API保持一致,特别是接近
DbCommand(两者都是 "可执行"),以减少采用的概念复杂性。 - 允许在同一批次中混合不同类型的语句(插入、更新、选择)。目前的连接方法支持这一点,我们的阅读器API也支持(多个结果集)。
- 为批处理中的每个单独的命令提供对受影响的行数的非聚合访问。
直接使用DbBatch
在这种模式下,你首先要创建一个DbBatch类的特定数据库。例如,如果以PostgreSQL为目标,那么你将创建一个NpgsqlBatch 的实例。对于MySQL,该类被称为MySqlBatch 。(对于通用编程,你也可以使用DbProviderFactory来为你创建正确的DbBatch子类)。
在DbBatch类上,你添加你的连接和可选的事务对象。然后你添加你的DbBatchCommand对象的列表。
DbBatchCommand是DbCommand 的精简版。基本上,它只是SQL字符串和参数集合,其他的都在DbBatch层面处理。
在这一点上,你可以在DbBatch 上调用一个执行方法。三个基本选项是:
ExecuteNonQuery:返回受影响的总行数。ExecuteScalar:返回第一个结果集的第一行的第一列。ExecuteReader:按顺序返回所有的结果集。
当使用ExecuteReader ,只有第一个结果集是可见的,直到你调用DbDataReader.NextResult 。这时,第一个结果集会丢失,而下一个结果集会变得可见。重复这个过程,直到所有的结果集都被消耗,并且NextResult返回错误。
在执行了一个DbBatch ,每个DbBatchCommand将有它的RecordsAffected属性被设置。具体什么时候被填充是具体实施的,可能会延迟到所有结果集被消耗。
DbBatch 和 ORMs
为了提高效率,大多数ORM需要进行批处理操作。所以理论上他们可以通过切换到DbBatch API看到性能提升。这将在内部完成,对ORM没有明显的改变。从本质上讲,这对应用开发者来说是一个免费的性能提升。
PostgreSQL的实现
.NET的官方PostgreSQL驱动已经被更新,以支持批处理API。伴随着这一变化的是原始SQL模式选项。
如上所述,PostgreSQL驱动需要扫描SQL字符串中的分号,以便将批处理转换为单个语句。这种解析可能是相当昂贵的,因为它必须正确地处理嵌入字符串字面的分号等东西。
解析还涉及到命名参数的问题。PostgreSQL本身不支持命名参数,所以客户端解析器也需要把命名参数转换成位置参数。
通过切换到原始SQL模式,解析功能被禁用。这提供了一些潜在的性能改进,但是你失去了通过NpgsqlCommand使用批处理的能力,这是一个更大的性能问题。
一旦显式批处理通过NpgsqlBatch成为可能,原始SQL模式也成为一个可行的选择。要使用这种模式,你必须不给任何参数分配名称。
如果你没有参数,那么你必须使用这些命令中的任何一个:
AppContext.SetSwitch("Npgsql.EnableSqlRewriting", false);
cmd.Parameters.Add(new() { Value = 0 });
AppContext版本在全局范围内改变设置。
MySQL的实现
MySQL的实现正在作为MySqlConnector的一部分提供。这个库被普遍认为比官方的Oracle版本更好,并且也支持MariaDB。
MySqlBatch功能实际上早在2019年就完成了,但没有得到太多的关注,因为匹配的框架API从.NET Core 3中被削减。当它最终随.NET 6发布时,唯一的重大变化是将其标记为继承自匹配基类。
SQL服务器的实现
这个功能的SQL Server实现滞后于其他功能。虽然在2018年开始规划,但还没有做出关键的决定。12月的最后一次更新表明这是一个管理问题。
api在net 6中,所以我们需要的是一个net6的构建目标,以及MS方面关于api将在其他基类不可用的构建中提供的政策决定。
就实现而言,已经有很多基础功能到位了。一个被称为SqlCommandSet的类似的内部类已经使用了很长时间,但是只能通过SqlDataAdapter间接访问,甚至很少有开发者知道它的存在。