System.Data.IDbCommand и асинхронное выполнение?
System.Data.SqlClient.SqlCommand имеет методы
BeginExecuteNonQuery
BeginExecuteReader
BeginExecuteXmlReader
а также
EndExecuteNonQuery
EndExecuteReader
EndExecuteXmlReader
для асинхронного выполнения.
System.Data.IDbCommand имеет только
ExecuteNonQuery
ExecuteReader
ExecuteXmlReader
которые предназначены только для синхронных операций.
Есть ли интерфейс для асинхронных операций?
Кроме того, почему нет BeginExecuteScalar?
8 ответов
IDbCommand
не имеет асинхронных методов начала / конца, потому что они еще не существовали в исходном выпуске ADO.NET.NET 1.1, и когда асинхронные методы были добавлены в.NET 2.0, было бы серьезным изменением добавить их в IDbCommand
(добавление членов к интерфейсу является серьезным изменением для разработчиков этого интерфейса).
Я не знаю почему BeginExecuteScalar
не существует, но он может быть реализован как метод расширения, который охватывает BeginExecuteReader
, Во всяком случае в.NET 4.5 у нас теперь есть ExecuteScalarAsync
который проще в использовании.
Я рекомендую лечить DbCommand
и его друзья, как если бы они были интерфейсами при использовании API баз данных. Ради обобщения API над различными поставщиками баз данных, DbCommand
достигает так же хорошо, как IDbCommand
- или, возможно, лучше, потому что он включает в себя новые технологии, такие как собственно await
в состоянии Task
*Async()
члены.
MS не может добавить какие-либо новые методы с новой функциональностью IDbCommand
, Если бы они должны были добавить метод IDbCommand
Это серьезное изменение, потому что любой может свободно реализовать этот интерфейс в своем коде, а MS приложила немало усилий для сохранения совместимости ABI и API в рамках. Если бы они расширили интерфейсы в выпуске.net, код клиента, который ранее работал, прекратил бы компиляцию, и существующие сборки, которые не были перекомпилированы, начали бы сталкиваться с ошибками во время выполнения. Кроме того, они не могут добавить правильные *Async()
или же Begin*()
методы через методы расширения, не делая уродливое приведение к DbCommand
за кулисами (что является самой плохой практикой, нарушающей безопасность типов и излишне вводящей динамическое приведение во время выполнения).
С другой стороны, MS может добавлять новые виртуальные методы в DbCommand
не нарушая ABI. Добавление новых методов в базовый класс может рассматриваться как нарушение API (время компиляции, не так плохо, как во время выполнения), потому что если вы унаследовали DbCommand
и добавив члена с тем же именем, вы начнете получать предупреждение CS0108: "member1" скрывает унаследованный член "member2". Используйте новое ключевое слово, если скрытие было предназначено.). Таким образом, DbCommand
может получить новые функции с минимальным влиянием на потребление кода, что следует за передовой практикой (например, большинство вещей будет работать до тех пор, пока они не будут работать против системы типов и вызывать методы, используя что-то вроде myCommand.GetType().GetMethods()[3].Invoke(myCommand, …)
).
Возможной стратегией, которую MS могла бы использовать для поддержки людей, которым нравятся интерфейсы, было бы введение новых интерфейсов с такими именами, как IAsyncDbCommand
и имеют DbCommand
реализовать их. Они этого не сделали. Я не знаю почему, но они, вероятно, не сделали этого, потому что это увеличило бы сложность и альтернативу непосредственного потребления DbCommand
обеспечивает большую часть преимуществ использования интерфейсов с небольшими недостатками. Т.е. это будет работа с небольшим возвратом.
На самом деле, создание асинхронного поведения, эквивалентного BeginExecuteNonQuery, EndExecuteNonQuery и т. Д., Было бы довольно сложной задачей. Реализация этих API намного превосходит простое создание отдельного потока, ожидание ответа базы данных и вызов обратного вызова. Они полагаются на перекрытие ввода / вывода и обеспечивают гораздо лучшую экономию потоков. Никакие дополнительные потоки не расходуются на время сетевого перехода, обработки базы данных команды - что, вероятно, составляет 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 Standard для включения асинхронного интерфейса. Тем временем я также создал библиотеку с набором интерфейсов и адаптеров для System.Data.
Вы можете реализовать асинхронное поведение с помощью своего пользовательского кода, так как он не так сложен, как по вашему вопросу - для ваших целей нет никаких стандартных асинхронных операций.
Нет, нет интерфейсов для них
Причина, почему нет BeginExecuteScalar
потому что вам, вероятно, не понадобится асинхронный вызов для возврата одного значения, которое должно быть очень быстрым