diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index f4f38dbe73..d2bedc83f9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -1065,13 +1066,7 @@ public override void Cancel() } if (!_pendingCancel) - { // Do nothing if already pending. - // Before attempting actual cancel, set the _pendingCancel flag to false. - // This denotes to other thread before obtaining stateObject from the - // session pool that there is another thread wishing to cancel. - // The period in question is between entering the ExecuteAPI and obtaining - // a stateObject. _pendingCancel = true; TdsParserStateObject stateObj = _stateObj; @@ -1763,7 +1758,6 @@ private Task InternalExecuteNonQuery( asyncWrite, isRetry, methodName); - if (reader != null) { if (task != null) @@ -2587,7 +2581,6 @@ long firstAttemptStart { completion.TrySetResult(retryTask.Result); } - }, state: globalCompletion, TaskScheduler.Default ); @@ -3105,7 +3098,6 @@ private async Task ExecuteScalarUntilEndAsync(SqlDataReader reader, Canc } /// - public Task ExecuteXmlReaderAsync() => ExecuteXmlReaderAsync(CancellationToken.None); /// @@ -4871,7 +4863,6 @@ internal SqlDataReader RunExecuteReader( task: out unused, usedCache: out _, method: method); - Debug.Assert(unused == null, "returned task during synchronous execution"); return reader; } @@ -6192,7 +6183,8 @@ private void SetUpRPCParameters(_SqlRPC rpc, bool inSchema, SqlParameterCollecti // Don't assume a default value exists for parameters in the case when // the user is simply requesting schema. // TVPs use DEFAULT and do not allow NULL, even for schema only. - if (parameter.Value == null && (!inSchema || SqlDbType.Structured == parameter.SqlDbType)) + if ((parameter.Value == null || (parameter.Value is ICollection collection && collection.Count == 0)) && + (!inSchema || parameter.SqlDbType == SqlDbType.Structured)) { options |= TdsEnums.RPC_PARAM_DEFAULT; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs index fb9cdbb8e0..78cbe55f8f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1444,10 +1444,7 @@ private void GetActualFieldsAndProperties(out List fields, throw SQL.NotEnoughColumnsInStructuredType(); } } - else - { - throw SQL.IEnumerableOfSqlDataRecordHasNoRows(); - } + } finally { @@ -2044,7 +2041,7 @@ internal void Validate(int index, bool isCommandProc) GetCoercedValue(); } - if (metaType.SqlDbType == SqlDbTypeExtensions.Vector && + if (metaType.SqlDbType == SqlDbTypeExtensions.Vector && (_value == null || _value == DBNull.Value) && (Direction == ParameterDirection.Output || Direction == ParameterDirection.InputOutput)) { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs index c5e3b000dc..c4283d3fd6 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using Microsoft.Data.Sql; +using Microsoft.Data.SqlClient.Server; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -518,5 +520,110 @@ public void ParameterCollectionTest() cmd.Parameters.Remove(cmd.Parameters[0]); } } + + #region fixing "Change SqlParameter to allow empty IEnumerable? #2971" + public const string DbName = "TVPTestDb"; + public const string ServerConnection = @"Server=.;Integrated Security=true;Encrypt=false;"; + public static string DbConnection => ServerConnection + $"Initial Catalog={DbName};"; + private static class SqlDataRecordTestCases + { + public static IEnumerable TestSqlDataRecordParameters => new List + { + new object[] { null }, + new object[] { new List() } // empty list, and the issue it throw an Arrgument Error Exception + }; + } + + private static void CreateDatabaseAndSchema() + { + string createDb = $@" + IF DB_ID('{DbName}') IS NULL + CREATE DATABASE {DbName};"; + + string createSchema = @" + USE TVPTestDb; + + IF NOT EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'MySimpleTableType') + BEGIN + CREATE TYPE dbo.MySimpleTableType AS TABLE + ( + Id INT, + Name NVARCHAR(100) + ); + END; + + IF OBJECT_ID('dbo.TargetTable', 'U') IS NULL + BEGIN + CREATE TABLE dbo.TargetTable + ( + Id INT PRIMARY KEY, + Name NVARCHAR(100) + ); + END; + + IF OBJECT_ID('dbo.InsertFromTVP', 'P') IS NULL + BEGIN + EXEC(' + CREATE PROCEDURE dbo.InsertFromTVP + @MyTVP dbo.MySimpleTableType READONLY + AS + BEGIN + SET NOCOUNT ON; + INSERT INTO dbo.TargetTable (Id, Name) + SELECT Id, Name FROM @MyTVP; + END + '); + END;"; + + using var conn = new SqlConnection(ServerConnection); + conn.Open(); + new SqlCommand(createDb, conn).ExecuteNonQuery(); + + using var connDb = new SqlConnection(DbConnection); + connDb.Open(); + new SqlCommand(createSchema, connDb).ExecuteNonQuery(); + } + + private static void DeleteDatabase() + { + string dropDb = $@" + IF DB_ID('{DbName}') IS NOT NULL + BEGIN + ALTER DATABASE {DbName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE {DbName}; + END;"; + using var conn = new SqlConnection(ServerConnection); + conn.Open(); + new SqlCommand(dropDb, conn).ExecuteNonQuery(); + } + private int perpare_SqlRecord_tests(List records = null) + { + SqlConnection cn = new SqlConnection(DbConnection); + + SqlCommand cmd; + // Text, with parameters + cmd = new SqlCommand("dbo.InsertFromTVP", cn); + cmd.CommandType = CommandType.StoredProcedure; + + var paramEmpty = cmd.Parameters.AddWithValue("@MyTVP", records); + paramEmpty.SqlDbType = SqlDbType.Structured; + paramEmpty.TypeName = "dbo.MySimpleTableType"; + cn.Open(); + return cmd.ExecuteNonQuery(); + + } + [Theory] + [MemberData(nameof(SqlDataRecordTestCases.TestSqlDataRecordParameters), MemberType = typeof(SqlDataRecordTestCases))] + public void SqlDataRecord_TABLE_deafult(List parameters) + { + CreateDatabaseAndSchema(); + int rowAffects = perpare_SqlRecord_tests(parameters); + DeleteDatabase(); + Assert.Equal(-1, rowAffects); + + } + #endregion + + } }