diff --git a/src/libraries/System.Data.SqlClient/ref/System.Data.SqlClient.cs b/src/libraries/System.Data.SqlClient/ref/System.Data.SqlClient.cs index 952d59e51aa50..7198dbcb6a63e 100644 --- a/src/libraries/System.Data.SqlClient/ref/System.Data.SqlClient.cs +++ b/src/libraries/System.Data.SqlClient/ref/System.Data.SqlClient.cs @@ -425,6 +425,11 @@ public override void Cancel() { } public System.IAsyncResult BeginExecuteXmlReader() { throw null; } public System.IAsyncResult BeginExecuteXmlReader(System.AsyncCallback callback, object stateObject) { throw null; } public System.Xml.XmlReader EndExecuteXmlReader(System.IAsyncResult asyncResult) { throw null; } + public System.IAsyncResult BeginExecuteReader() { throw null; } + public System.IAsyncResult BeginExecuteReader(System.AsyncCallback callback, object stateObject) { throw null; } + public System.IAsyncResult BeginExecuteReader(System.AsyncCallback callback, object stateObject, System.Data.CommandBehavior behavior) { throw null; } + public System.IAsyncResult BeginExecuteReader(System.Data.CommandBehavior behavior) { throw null; } + public System.Data.SqlClient.SqlDataReader EndExecuteReader(System.IAsyncResult asyncResult) { throw null; } public override void Prepare() { } public System.Data.Sql.SqlNotificationRequest Notification { get { throw null; } set { } } public void ResetCommandTimeout() { } diff --git a/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs b/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs index 6416f49e45895..f39d2643c5d47 100644 --- a/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs +++ b/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlCommand.cs @@ -1459,7 +1459,7 @@ override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) } - internal SqlDataReader EndExecuteReader(IAsyncResult asyncResult) + public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) { Exception asyncException = ((Task)asyncResult).Exception; if (asyncException != null) @@ -1509,7 +1509,22 @@ private SqlDataReader EndExecuteReaderInternal(IAsyncResult asyncResult) } } - internal IAsyncResult BeginExecuteReader(CommandBehavior behavior, AsyncCallback callback, object stateObject) + public IAsyncResult BeginExecuteReader() + { + return BeginExecuteReader(null, null, CommandBehavior.Default); + } + + public IAsyncResult BeginExecuteReader(AsyncCallback callback, object stateObject) + { + return BeginExecuteReader(callback, stateObject, CommandBehavior.Default); + } + + public IAsyncResult BeginExecuteReader(CommandBehavior behavior) + { + return BeginExecuteReader(null, null, behavior); + } + + public IAsyncResult BeginExecuteReader(AsyncCallback callback, object stateObject, CommandBehavior behavior) { // Reset _pendingCancel upon entry into any Execute - used to synchronize state // between entry into Execute* API and the thread obtaining the stateObject. @@ -1710,7 +1725,7 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b { RegisterForConnectionCloseNotification(ref returnedTask); - Task.Factory.FromAsync(BeginExecuteReader, EndExecuteReader, behavior, null).ContinueWith((t) => + Task.Factory.FromAsync((commandBehavior, callback, stateObject) => BeginExecuteReader(callback, stateObject, commandBehavior), EndExecuteReader, behavior, null).ContinueWith((t) => { registration.Dispose(); if (t.IsFaulted) diff --git a/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlDependencyListener.cs b/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlDependencyListener.cs index ac908e142129d..5640f45109858 100644 --- a/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlDependencyListener.cs +++ b/src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SqlDependencyListener.cs @@ -227,7 +227,7 @@ internal bool AppDomainUnload(string appDomainKey) private void AsynchronouslyQueryServiceBrokerQueue() { AsyncCallback callback = new AsyncCallback(AsyncResultCallback); - _com.BeginExecuteReader(CommandBehavior.Default, callback, null); // NO LOCK NEEDED + _com.BeginExecuteReader(callback, null, CommandBehavior.Default); // NO LOCK NEEDED } private void AsyncResultCallback(IAsyncResult asyncResult) diff --git a/src/libraries/System.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs b/src/libraries/System.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs new file mode 100644 index 0000000000000..f75ae1c0747b6 --- /dev/null +++ b/src/libraries/System.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Data.SqlClient.ManualTesting.Tests +{ + public static class BeginExecReaderAsyncTest + { + private static string GenerateCommandText() + { + int suffix = (new Random()).Next(5000); + string companyName = "M Inc."; + string phone = "777-1111"; + + string commandText = + $"CREATE TABLE #Shippers{suffix}(" + + $"[ShipperID][int] NULL," + + $"[CompanyName] [nvarchar] (40) NOT NULL," + + $"[Phone] [nvarchar] (24) NULL )" + + $"INSERT INTO #Shippers{suffix}" + + $"([CompanyName] " + + $",[Phone])" + + $"VALUES " + + $"('{companyName}' " + + $",'{phone}'); " + + $"WAITFOR DELAY '0:0:3'; " + + $"select s.ShipperID, s.CompanyName, s.Phone " + + $"from #Shippers{suffix} s; "; + + return commandText; + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void ExecuteTest() + { + using (SqlConnection connection = new SqlConnection(DataTestUtility.TcpConnStr)) + { + SqlCommand command = new SqlCommand(GenerateCommandText(), connection); + connection.Open(); + + IAsyncResult result = command.BeginExecuteReader(); + while (!result.IsCompleted) + { + System.Threading.Thread.Sleep(100); + } + SqlDataReader reader = command.EndExecuteReader(result); + Assert.True(reader.HasRows, $"FAILED: Reader has no rows"); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void BeginExecuteReaderWithCallback() + { + object state = new object(); + bool callbackExecutedFlag = false; + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TcpConnStr)) + using (SqlCommand command = new SqlCommand(GenerateCommandText(), connection)) + { + connection.Open(); + + Tuple stateAndCommand = new Tuple(state, command); + + IAsyncResult result = command.BeginExecuteReader(ar => + { + Tuple asyncArgs = ar.AsyncState as Tuple; + Assert.NotNull(asyncArgs); + + SqlDataReader reader = asyncArgs.Item2.EndExecuteReader(ar); + callbackExecutedFlag = true; + Assert.True(reader.HasRows, $"FAILED: Reader has no rows"); + Assert.Equal(state, asyncArgs.Item1); + }, stateAndCommand); + + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10)); + + Assert.True(callbackExecutedFlag, $"FAILED: Callback did not executed"); + } + } + } +} diff --git a/src/libraries/System.Data.SqlClient/tests/ManualTests/System.Data.SqlClient.ManualTesting.Tests.csproj b/src/libraries/System.Data.SqlClient/tests/ManualTests/System.Data.SqlClient.ManualTesting.Tests.csproj index dd256fe1d7353..dfaead50f91c3 100644 --- a/src/libraries/System.Data.SqlClient/tests/ManualTests/System.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/libraries/System.Data.SqlClient/tests/ManualTests/System.Data.SqlClient.ManualTesting.Tests.csproj @@ -9,6 +9,7 @@ +