From 1be606c7ebc46b73ef6cce41f75ede7e0fd278ac Mon Sep 17 00:00:00 2001
From: Johnny Pham <23270162+johnnypham@users.noreply.github.com>
Date: Tue, 9 Jun 2020 18:48:20 -0700
Subject: [PATCH 1/5] Optional ORDER hints for SqlBulkCopy (#540)
---
doc/samples/SqlBulkCopy_ColumnOrderHint.cs | 84 +++
.../SqlBulkCopy_ColumnOrderHintCollection.cs | 83 +++
...qlBulkCopy_ColumnOrderHintCollectionAdd.cs | 86 +++
...lBulkCopy_ColumnOrderHintCollectionAdd2.cs | 83 +++
...BulkCopy_ColumnOrderHintCollectionClear.cs | 177 ++++++
...ulkCopy_ColumnOrderHintCollectionRemove.cs | 176 ++++++
...kCopy_ColumnOrderHintCollectionRemoveAt.cs | 176 ++++++
.../SqlBulkCopy_ColumnOrderHintColumn.cs | 85 +++
.../SqlBulkCopy_ColumnOrderHintSortOrder.cs | 85 +++
.../Microsoft.Data.SqlClient/SqlBulkCopy.xml | 79 ++-
.../SqlBulkCopyColumnOrderHint.xml | 131 ++++
.../SqlBulkCopyColumnOrderHintCollection.xml | 226 +++++++
.../netcore/ref/Microsoft.Data.SqlClient.cs | 36 ++
.../src/Microsoft.Data.SqlClient.csproj | 6 +
.../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 47 +-
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 20 +
.../netcore/src/Resources/SR.Designer.cs | 36 ++
.../netcore/src/Resources/SR.resx | 14 +-
.../netfx/ref/Microsoft.Data.SqlClient.cs | 36 ++
.../netfx/src/Microsoft.Data.SqlClient.csproj | 6 +
.../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 65 +-
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 20 +
.../netfx/src/Resources/Strings.Designer.cs | 36 ++
.../netfx/src/Resources/Strings.resx | 14 +-
.../SqlClient/SqlBulkCopyColumnOrderHint.cs | 66 ++
.../SqlBulkCopyColumnOrderHintCollection.cs | 133 ++++
.../Microsoft.Data.SqlClient.Tests.csproj | 1 +
...qlBulkCopyColumnOrderHintCollectionTest.cs | 583 ++++++++++++++++++
....Data.SqlClient.ManualTesting.Tests.csproj | 6 +
.../SQL/SqlBulkCopyTest/OrderHint.cs | 150 +++++
.../SQL/SqlBulkCopyTest/OrderHintAsync.cs | 132 ++++
.../OrderHintDuplicateColumn.cs | 70 +++
.../OrderHintIdentityColumn.cs | 67 ++
.../OrderHintMissingTargetColumn.cs | 75 +++
.../SqlBulkCopyTest/OrderHintTransaction.cs | 58 ++
.../SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs | 37 ++
36 files changed, 3167 insertions(+), 18 deletions(-)
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHint.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs
create mode 100644 doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs
create mode 100644 doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml
create mode 100644 doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml
create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHint.cs
create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHintCollection.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBulkCopyColumnOrderHintCollectionTest.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHint.cs b/doc/samples/SqlBulkCopy_ColumnOrderHint.cs
new file mode 100644
index 0000000000..9960debf20
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHint.cs
@@ -0,0 +1,84 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Setup an order hint for the ProductNumber column.
+ SqlBulkCopyColumnOrderHint hintNumber =
+ new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending);
+ bulkCopy.ColumnOrderHints.Add(hintNumber);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs
new file mode 100644
index 0000000000..4a92099ddb
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs
@@ -0,0 +1,83 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Specify the sort order for the ProductNumber column in
+ // the destination table.
+ bulkCopy.ColumnOrderHints.Add("ProductNumber", SortOrder.Ascending);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs
new file mode 100644
index 0000000000..2d6599d525
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs
@@ -0,0 +1,86 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Specify the sort order for the ProductNumber column in
+ // the destination table.
+ // Setup an order hint for the ProductNumber column.
+ SqlBulkCopyColumnOrderHint hintNumber =
+ new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending);
+ bulkCopy.ColumnOrderHints.Add(hintNumber);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs
new file mode 100644
index 0000000000..4a92099ddb
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs
@@ -0,0 +1,83 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Specify the sort order for the ProductNumber column in
+ // the destination table.
+ bulkCopy.ColumnOrderHints.Add("ProductNumber", SortOrder.Ascending);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs
new file mode 100644
index 0000000000..0df1281b52
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs
@@ -0,0 +1,177 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a connection to the AdventureWorks database.
+ using (SqlConnection connection =
+ new SqlConnection(connectionString))
+ {
+ connection.Open();
+
+ // Empty the destination tables.
+ SqlCommand deleteHeader = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ deleteHeader.ExecuteNonQuery();
+ SqlCommand deleteDetail = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ deleteDetail.ExecuteNonQuery();
+
+ // Perform an initial count on the destination
+ // table with matching columns.
+ SqlCommand countRowHeader = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ long countStartHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Header table = {0}",
+ countStartHeader);
+
+ // Perform an initial count on the destination
+ // table with different column positions.
+ SqlCommand countRowDetail = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ long countStartDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Detail table = {0}",
+ countStartDetail);
+
+ // Get data from the source table as a SqlDataReader.
+ // The Sales.SalesOrderHeader and Sales.SalesOrderDetail
+ // tables are quite large and could easily cause a timeout
+ // if all data from the tables is added to the destination.
+ // To keep the example simple and quick, a parameter is
+ // used to select only orders for a particular account
+ // as the source for the bulk insert.
+ SqlCommand headerData = new SqlCommand(
+ "SELECT [SalesOrderID], [OrderDate], " +
+ "[AccountNumber] FROM [Sales].[SalesOrderHeader] " +
+ "WHERE [AccountNumber] = @accountNumber;",
+ connection);
+ SqlParameter parameterAccount = new SqlParameter();
+ parameterAccount.ParameterName = "@accountNumber";
+ parameterAccount.SqlDbType = SqlDbType.NVarChar;
+ parameterAccount.Direction = ParameterDirection.Input;
+ parameterAccount.Value = "10-4020-000034";
+ headerData.Parameters.Add(parameterAccount);
+ SqlDataReader readerHeader = headerData.ExecuteReader();
+
+ // Get the Detail data in a separate connection.
+ using (SqlConnection connection2 = new SqlConnection(connectionString))
+ {
+ connection2.Open();
+ SqlCommand sourceDetailData = new SqlCommand(
+ "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " +
+ "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " +
+ "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." +
+ "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " +
+ "WHERE [AccountNumber] = @accountNumber;", connection2);
+
+ SqlParameter accountDetail = new SqlParameter();
+ accountDetail.ParameterName = "@accountNumber";
+ accountDetail.SqlDbType = SqlDbType.NVarChar;
+ accountDetail.Direction = ParameterDirection.Input;
+ accountDetail.Value = "10-4020-000034";
+ sourceDetailData.Parameters.Add(accountDetail);
+ SqlDataReader readerDetail = sourceDetailData.ExecuteReader();
+
+ // Create the SqlBulkCopy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoOrderHeader";
+
+ // Guarantee that columns are mapped correctly by
+ // defining the column mappings for the order.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate");
+ bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber");
+
+ // Add order hint for OrderDate column.
+ bulkCopy.ColumnOrderHints.Add("OrderDate", SortOrder.Ascending);
+
+ // Write readerHeader to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerHeader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerHeader.Close();
+ }
+
+ // Set up the order details destination.
+ bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail";
+
+ // Clear the ColumnMappingCollection.
+ bulkCopy.ColumnMappings.Clear();
+
+ // Add order detail column mappings.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID");
+ bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty");
+ bulkCopy.ColumnMappings.Add("ProductID", "ProductID");
+ bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice");
+
+ // Clear the ColumnOrderHintCollection.
+ bulkCopy.ColumnOrderHints.Clear();
+
+ // Add order hint for SalesOrderID column.
+ bulkCopy.ColumnOrderHints.Add("SalesOrderID", SortOrder.Ascending);
+
+ // Write readerDetail to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerDetail);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerDetail.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // tables to see how many rows were added.
+ long countEndHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Header table.",
+ countEndHeader - countStartHeader);
+ long countEndDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Detail table.",
+ countEndDetail - countStartDetail);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the connection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs
new file mode 100644
index 0000000000..ea9a8f4a46
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs
@@ -0,0 +1,176 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a connection to the AdventureWorks database.
+ using (SqlConnection connection =
+ new SqlConnection(connectionString))
+ {
+ connection.Open();
+
+ // Empty the destination tables.
+ SqlCommand deleteHeader = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ deleteHeader.ExecuteNonQuery();
+ SqlCommand deleteDetail = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ deleteDetail.ExecuteNonQuery();
+
+ // Perform an initial count on the destination
+ // table with matching columns.
+ SqlCommand countRowHeader = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ long countStartHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Header table = {0}",
+ countStartHeader);
+
+ // Perform an initial count on the destination
+ // table with different column positions.
+ SqlCommand countRowDetail = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ long countStartDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Detail table = {0}",
+ countStartDetail);
+
+ // Get data from the source table as a SqlDataReader.
+ // The Sales.SalesOrderHeader and Sales.SalesOrderDetail
+ // tables are quite large and could easily cause a timeout
+ // if all data from the tables is added to the destination.
+ // To keep the example simple and quick, a parameter is
+ // used to select only orders for a particular account
+ // as the source for the bulk insert.
+ SqlCommand headerData = new SqlCommand(
+ "SELECT [SalesOrderID], [OrderDate], " +
+ "[AccountNumber] FROM [Sales].[SalesOrderHeader] " +
+ "WHERE [AccountNumber] = @accountNumber;",
+ connection);
+ SqlParameter parameterAccount = new SqlParameter();
+ parameterAccount.ParameterName = "@accountNumber";
+ parameterAccount.SqlDbType = SqlDbType.NVarChar;
+ parameterAccount.Direction = ParameterDirection.Input;
+ parameterAccount.Value = "10-4020-000034";
+ headerData.Parameters.Add(parameterAccount);
+ SqlDataReader readerHeader = headerData.ExecuteReader();
+
+ // Get the Detail data in a separate connection.
+ using (SqlConnection connection2 = new SqlConnection(connectionString))
+ {
+ connection2.Open();
+ SqlCommand sourceDetailData = new SqlCommand(
+ "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " +
+ "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " +
+ "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." +
+ "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " +
+ "WHERE [AccountNumber] = @accountNumber;", connection2);
+
+ SqlParameter accountDetail = new SqlParameter();
+ accountDetail.ParameterName = "@accountNumber";
+ accountDetail.SqlDbType = SqlDbType.NVarChar;
+ accountDetail.Direction = ParameterDirection.Input;
+ accountDetail.Value = "10-4020-000034";
+ sourceDetailData.Parameters.Add(accountDetail);
+ SqlDataReader readerDetail = sourceDetailData.ExecuteReader();
+
+ // Create the SqlBulkCopy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoOrderHeader";
+
+ // Guarantee that columns are mapped correctly by
+ // defining the column mappings for the order.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate");
+ bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber");
+
+ // Add the order hint for the OrderDate column.
+ SqlBulkCopyColumnOrderHint hintDate =
+ new SqlBulkCopyColumnOrderHint("OrderDate", SortOrder.Ascending);
+ bulkCopy.ColumnOrderHints.Add(hintDate);
+
+ // Write readerHeader to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerHeader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerHeader.Close();
+ }
+
+ // Set up the order details destination.
+ bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail";
+
+ // Clear the ColumnMappingCollection.
+ bulkCopy.ColumnMappings.Clear();
+
+ // Add order detail column mappings.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID");
+ bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty");
+ bulkCopy.ColumnMappings.Add("ProductID", "ProductID");
+ bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice");
+
+ // Remove the order hint for the OrderDate column.
+ bulkCopy.ColumnOrderHints.Remove(hintDate);
+
+ // Write readerDetail to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerDetail);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerDetail.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // tables to see how many rows were added.
+ long countEndHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Header table.",
+ countEndHeader - countStartHeader);
+ long countEndDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Detail table.",
+ countEndDetail - countStartDetail);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the connection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs
new file mode 100644
index 0000000000..b965599175
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs
@@ -0,0 +1,176 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a connection to the AdventureWorks database.
+ using (SqlConnection connection =
+ new SqlConnection(connectionString))
+ {
+ connection.Open();
+
+ // Empty the destination tables.
+ SqlCommand deleteHeader = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ deleteHeader.ExecuteNonQuery();
+ SqlCommand deleteDetail = new SqlCommand(
+ "DELETE FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ deleteDetail.ExecuteNonQuery();
+
+ // Perform an initial count on the destination
+ // table with matching columns.
+ SqlCommand countRowHeader = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;",
+ connection);
+ long countStartHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Header table = {0}",
+ countStartHeader);
+
+ // Perform an initial count on the destination
+ // table with different column positions.
+ SqlCommand countRowDetail = new SqlCommand(
+ "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;",
+ connection);
+ long countStartDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine(
+ "Starting row count for Detail table = {0}",
+ countStartDetail);
+
+ // Get data from the source table as a SqlDataReader.
+ // The Sales.SalesOrderHeader and Sales.SalesOrderDetail
+ // tables are quite large and could easily cause a timeout
+ // if all data from the tables is added to the destination.
+ // To keep the example simple and quick, a parameter is
+ // used to select only orders for a particular account
+ // as the source for the bulk insert.
+ SqlCommand headerData = new SqlCommand(
+ "SELECT [SalesOrderID], [OrderDate], " +
+ "[AccountNumber] FROM [Sales].[SalesOrderHeader] " +
+ "WHERE [AccountNumber] = @accountNumber;",
+ connection);
+ SqlParameter parameterAccount = new SqlParameter();
+ parameterAccount.ParameterName = "@accountNumber";
+ parameterAccount.SqlDbType = SqlDbType.NVarChar;
+ parameterAccount.Direction = ParameterDirection.Input;
+ parameterAccount.Value = "10-4020-000034";
+ headerData.Parameters.Add(parameterAccount);
+ SqlDataReader readerHeader = headerData.ExecuteReader();
+
+ // Get the Detail data in a separate connection.
+ using (SqlConnection connection2 = new SqlConnection(connectionString))
+ {
+ connection2.Open();
+ SqlCommand sourceDetailData = new SqlCommand(
+ "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " +
+ "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " +
+ "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." +
+ "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " +
+ "WHERE [AccountNumber] = @accountNumber;", connection2);
+
+ SqlParameter accountDetail = new SqlParameter();
+ accountDetail.ParameterName = "@accountNumber";
+ accountDetail.SqlDbType = SqlDbType.NVarChar;
+ accountDetail.Direction = ParameterDirection.Input;
+ accountDetail.Value = "10-4020-000034";
+ sourceDetailData.Parameters.Add(accountDetail);
+ SqlDataReader readerDetail = sourceDetailData.ExecuteReader();
+
+ // Create the SqlBulkCopy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoOrderHeader";
+
+ // Guarantee that columns are mapped correctly by
+ // defining the column mappings for the order.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate");
+ bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber");
+
+ // Add the order hint for the OrderDate column.
+ SqlBulkCopyColumnOrderHint hintDate =
+ new SqlBulkCopyColumnOrderHint("OrderDate", SortOrder.Ascending);
+ bulkCopy.ColumnOrderHints.Add(hintDate);
+
+ // Write readerHeader to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerHeader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerHeader.Close();
+ }
+
+ // Set up the order details destination.
+ bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail";
+
+ // Clear the ColumnMappingCollection.
+ bulkCopy.ColumnMappings.Clear();
+
+ // Add order detail column mappings.
+ bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID");
+ bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID");
+ bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty");
+ bulkCopy.ColumnMappings.Add("ProductID", "ProductID");
+ bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice");
+
+ // Remove the order hint for the OrderDate column.
+ bulkCopy.ColumnOrderHints.RemoveAt(0);
+
+ // Write readerDetail to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(readerDetail);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ readerDetail.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // tables to see how many rows were added.
+ long countEndHeader = System.Convert.ToInt32(
+ countRowHeader.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Header table.",
+ countEndHeader - countStartHeader);
+ long countEndDetail = System.Convert.ToInt32(
+ countRowDetail.ExecuteScalar());
+ Console.WriteLine("{0} rows were added to the Detail table.",
+ countEndDetail - countStartDetail);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the connection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs
new file mode 100644
index 0000000000..6a75e0289c
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs
@@ -0,0 +1,85 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Setup an order hint for the ProductNumber column.
+ SqlBulkCopyColumnOrderHint hintNumber =
+ new SqlBulkCopyColumnOrderHint("number", SortOrder.Ascending);
+ hintNumber.Column = "ProductNumber";
+ bulkCopy.ColumnOrderHints.Add(hintNumber);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs b/doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs
new file mode 100644
index 0000000000..54cef5da53
--- /dev/null
+++ b/doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs
@@ -0,0 +1,85 @@
+//
+using System;
+using System.Data;
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string connectionString = GetConnectionString();
+ // Open a sourceConnection to the AdventureWorks database.
+ using (SqlConnection sourceConnection =
+ new SqlConnection(connectionString))
+ {
+ sourceConnection.Open();
+
+ // Perform an initial count on the destination table.
+ SqlCommand commandRowCount = new SqlCommand(
+ "SELECT COUNT(*) FROM " +
+ "dbo.BulkCopyDemoMatchingColumns;",
+ sourceConnection);
+ long countStart = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Starting row count = {0}", countStart);
+
+ // Get data from the source table as a SqlDataReader.
+ SqlCommand commandSourceData = new SqlCommand(
+ "SELECT ProductID, Name, " +
+ "ProductNumber " +
+ "FROM Production.Product;", sourceConnection);
+ SqlDataReader reader =
+ commandSourceData.ExecuteReader();
+
+ // Set up the bulk copy object.
+ using (SqlBulkCopy bulkCopy =
+ new SqlBulkCopy(connectionString))
+ {
+ bulkCopy.DestinationTableName =
+ "dbo.BulkCopyDemoMatchingColumns";
+
+ // Setup an order hint for the ProductNumber column.
+ SqlBulkCopyColumnOrderHint hintNumber =
+ new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending);
+ hintNumber.SortOrder = SortOrder.Descending;
+ bulkCopy.ColumnOrderHints.Add(hintNumber);
+
+ // Write from the source to the destination.
+ try
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ // Close the SqlDataReader. The SqlBulkCopy
+ // object is automatically closed at the end
+ // of the using block.
+ reader.Close();
+ }
+ }
+
+ // Perform a final count on the destination
+ // table to see how many rows were added.
+ long countEnd = System.Convert.ToInt32(
+ commandRowCount.ExecuteScalar());
+ Console.WriteLine("Ending row count = {0}", countEnd);
+ Console.WriteLine("{0} rows were added.", countEnd - countStart);
+ Console.WriteLine("Press Enter to finish.");
+ Console.ReadLine();
+ }
+ }
+
+ private static string GetConnectionString()
+ // To avoid storing the sourceConnection string in your code,
+ // you can retrieve it from a configuration file.
+ {
+ return "Data Source=(local); " +
+ " Integrated Security=true;" +
+ "Initial Catalog=AdventureWorks;";
+ }
+}
+//
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml
index 70a502c825..08e804ad7a 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml
@@ -360,6 +360,24 @@ During the execution of a bulk copy operation, this collection can be accessed,
]]>
+
+
+ Returns a collection of
+
+ items. Column order hints describe the sort order of columns in the clustered index of the destination table.
+
+
+ A collection of column order hints. By default, it is an empty collection.
+
+
+
+
+
Name of the destination table on the server.
@@ -530,6 +548,11 @@ the object's `Finalize` method.
To be added.
+
+ A
+
+ did not specify a valid destination column name.
+
@@ -572,6 +595,11 @@ Transact-SQL `INSERT … SELECT` statement to copy the data.
[!code-csharp[SqlBulkCopy.ConnectionString#1](~/../sqlclient/doc/samples/SqlBulkCopy_ConnectionString.cs#1)]
]]>
+
+ A
+
+ did not specify a valid destination column name.
+
@@ -610,6 +638,11 @@ This code is provided to demonstrate the syntax for using **SqlBulkCopy** only.
[!code-csharp[SqlBulkCopy.DataTable#1](~/../sqlclient/doc/samples/SqlBulkCopy_DataTable.cs#1)]
]]>
+
+ A
+
+ did not specify a valid destination column name.
+
Performing Bulk Copy Operations
@@ -664,6 +697,11 @@ The method is calle
[!code-csharp[SqlBulkCopy.DataRowState#1](~/../sqlclient/doc/samples/SqlBulkCopy_DataRowState.cs#1)]
]]>
+
+ A
+
+ did not specify a valid destination column name.
+
An array of objects that will be copied to the destination table.
@@ -688,6 +726,11 @@ In this example, a is created at run time. A single
]]>
+
+ A
+
+ did not specify a valid destination column name.
+
@@ -740,6 +783,10 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -797,6 +844,9 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -857,6 +907,9 @@ For more information about asynchronous programming in the .NET Framework Data P
returned.
is specified in the connection string.
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -960,6 +1013,10 @@ For more information about asynchronous programming in the .NET Framework Data P
returned.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -1037,6 +1094,10 @@ For more information about asynchronous programming in the .NET Framework Data P
returned.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -1094,8 +1155,12 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
-
+
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -1160,6 +1225,10 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -1219,6 +1288,10 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
@@ -1291,6 +1364,10 @@ For more information about asynchronous programming in the .NET Framework Data P
object is closed before method execution.
is specified in the connection string.
+
+ A
+
+ did not specify a valid destination column name.
Returned in the task object, any error returned by SQL Server that occurred while opening the connection.
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml
new file mode 100644
index 0000000000..8a7b04cf68
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ Defines the sort order for a column in a
+
+ instance's destination table, according to the clustered index on the table.
+
+
+ collection is not empty, order hints can only be provided for valid
+destination columns which have been mapped.
+
+If a of Unspecified is given, an will be thrown.
+
+## Examples
+The following example bulk copies data from a source table in the **AdventureWorks** sample database to a destination table in the same database.
+A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHint#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHint.cs#1)]
+]]>
+
+
+
+
+
+ The name of the destination column within the destination table.
+
+
+ The sort order of the corresponding destination column.
+
+
+ Creates a new column order hint for the specified destination column.
+
+
+ [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHint#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHint.cs#1)]
+]]>
+
+
+
+
+
+ Name of the destination column in the destination table for which the hint is being provided.
+
+
+ The string value of the
+
+ property.
+
+
+ will be thrown if a null or empty string is given.
+
+## Examples
+The following example bulk copies data from a source table in the **AdventureWorks** sample database to a destination table in the same database.
+A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintColumn#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs#1)]
+]]>
+
+ The value is null or empty.
+
+
+
+
+ The sort order of the destination column in the destination table.
+
+
+ The SortOrder value of the
+
+ property.
+
+
+ will be thrown if a of Unspecified is given.
+
+## Examples
+The following example bulk copies data from a source table in the **AdventureWorks** sample database to a destination table in the same database.
+A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintSortOrder#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs#1)]
+]]>
+
+ The sort order cannot be unspecified for a column order hint.
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml
new file mode 100644
index 0000000000..af8ba0984f
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml
@@ -0,0 +1,226 @@
+
+
+
+ Collection of objects that inherits from .
+
+ collection is not empty, order hints can only be provided for valid
+destination columns which have been mapped.
+
+If a of Unspecified is given, an will be thrown.
+
+## Examples
+
+The following example bulk copies data from a source table in the **AdventureWorks** sample database to a destination table in the same database.
+ are added to the for the
+ object to specify order hints for the bulk copy.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to
+use a Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollection#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs#1)]
+
+]]>
+
+
+
+
+ The object that describes the order hint to be added to the collection.
+ Adds the specified order hint to the .
+ A object.
+
+ [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance,
+it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionAdd#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs)]
+
+]]>
+
+
+ The value is null.
+
+
+
+ The name of the destination column within the destination table.
+ The sort order of the corresponding destination column.
+ Creates a new and adds it to the collection.
+ A column column order hint.
+
+ by providing the destination column name and its sort order.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionAdd2#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs#1)]
+
+]]>
+
+
+
+
+ Clears the contents of the collection.
+
+ method is most commonly used when you use a single
+instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the
+ after the method and before processing the next bulk copy.
+
+Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate
+ for each operation.
+
+## Examples
+The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details.
+The example defines a column order hint for each bulk copy operation.
+The method must be used after the first bulk copy is performed and before the next bulk copy's order hint is defined.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionClear#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs#1)]
+
+]]>
+
+
+
+ A valid object.
+ Gets a value indicating whether a specified object exists in the collection.
+
+ if the specified column order hint exists in the collection; otherwise .
+
+
+ The one-dimensional array that is the destination of the elements copied from
+ . The array must have zero-based indexing.
+ The zero-based index in at which copying begins.
+ Copies the elements of the to an array of
+ items, starting at a particular index.
+
+
+
+ The object for which to search.
+ Gets the index of the specified object.
+ The zero-based index of the column order hint, or -1 if the column order hint is not found in the collection.
+ To be added.
+
+
+ Integer value of the location within the at which to insert the new
+ .
+
+ object to be inserted in the collection.
+ Insert a new at the index specified.
+ The order in which column order hints can be added is arbitrary.
+ The index is less than zero or greater than .
+ A null column order hint cannot be added to the collection.
+
+
+ The zero-based index of the to find.
+ Gets the object at the specified index.
+ A object.
+ To be added.
+ The index must be non-negative and less than the size of the collection.
+
+
+
+ object to be removed from the collection.
+ Removes the specified element from the .
+
+ method is most commonly used when you use a single
+instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the
+ after the method and before processing the next bulk copy.
+You can clear the entire collection by using the
+ method, or remove hints individually using the
+method or the method.
+
+Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate
+ for each operation.
+
+## Examples
+The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details.
+The example defines a column order hint for the **OrderDate** column in the first bulk copy operation. The hint is removed before the second bulk copy operation.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionRemove#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs#1)]
+
+]]>
+
+ The value is null.
+
+
+ The zero-based index of the object to be removed from the collection.
+ Removes the column order hint at the specified index from the collection.
+
+ method is most commonly used when you use a single
+instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the
+ after the method and before processing the next bulk copy.
+You can clear the entire collection by using the
+ method, or remove hints individually using the
+method or the method.
+
+Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate
+ for each operation.
+
+## Examples
+The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details.
+The example defines a column order hint for the **OrderDate** column in the first bulk copy operation. The hint is removed before the second bulk copy operation.
+
+> [!IMPORTANT]
+> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](~/docs/framework/data/adonet/sql/bulk-copy-example-setup.md).
+This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a
+Transact-SQL `INSERT … SELECT` statement to copy the data.
+
+[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionRemoveAt#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs#1)]
+
+]]>
+
+ The index must be non-negative and less than the size of the collection.
+
+
+ Gets a value indicating whether access to the is synchronized (thread safe).
+ `true` if access to the is synchronized (thread safe); otherwise, `false`.
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index b3b5097ed6..52e6e35dd3 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -143,6 +143,8 @@ public sealed partial class SqlBulkCopy : System.IDisposable
public bool EnableStreaming { get { throw null; } set { } }
///
public Microsoft.Data.SqlClient.SqlBulkCopyColumnMappingCollection ColumnMappings { get { throw null; } }
+ ///
+ public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints { get { throw null; } }
///
public string DestinationTableName { get { throw null; } set { } }
///
@@ -239,6 +241,40 @@ public sealed partial class SqlBulkCopyColumnMappingCollection : System.Collecti
///
public new void RemoveAt(int index) { }
}
+ ///
+ public sealed class SqlBulkCopyColumnOrderHint
+ {
+ ///
+ public SqlBulkCopyColumnOrderHint(string column, SortOrder sortOrder) { }
+ ///
+ public string Column { get { throw null; } set { } }
+ ///
+ public SortOrder SortOrder { get { throw null; } set { } }
+ }
+ ///
+ public sealed class SqlBulkCopyColumnOrderHintCollection : System.Collections.CollectionBase
+ {
+ ///
+ public SqlBulkCopyColumnOrderHint this[int index] { get { throw null; } }
+ ///
+ public SqlBulkCopyColumnOrderHint Add(SqlBulkCopyColumnOrderHint columnOrderHint) { throw null; }
+ ///
+ public SqlBulkCopyColumnOrderHint Add(string column, SortOrder sortOrder) { throw null; }
+ ///
+ public new void Clear() { }
+ ///
+ public bool Contains(SqlBulkCopyColumnOrderHint value) { throw null; }
+ ///
+ public void CopyTo(SqlBulkCopyColumnOrderHint[] array, int index) { }
+ ///
+ public int IndexOf(SqlBulkCopyColumnOrderHint value) { throw null; }
+ ///
+ public void Insert(int index, SqlBulkCopyColumnOrderHint columnOrderHint) { }
+ ///
+ public void Remove(SqlBulkCopyColumnOrderHint columnOrderHint) { }
+ ///
+ public new void RemoveAt(int index) { }
+ }
///
[System.FlagsAttribute]
public enum SqlBulkCopyOptions
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 41c3596704..c31298fb12 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -96,6 +96,12 @@
Microsoft\Data\SqlClient\SqlBulkCopyOptions.cs
+
+ Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHint.cs
+
+
+ Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHintCollection.cs
+
Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactory.cs
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
index 686e194397..adfc7ec37c 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
@@ -218,13 +218,13 @@ private int RowNumber
rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DataTable:
- rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
+ rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DbDataReader:
case ValueSourceType.IDataReader:
case ValueSourceType.Unspecified:
default:
- return -1;
+ return -1;
}
return ++rowNo;
}
@@ -272,6 +272,7 @@ public SqlBulkCopy(SqlConnection connection)
}
_connection = connection;
_columnMappings = new SqlBulkCopyColumnMappingCollection();
+ ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection();
}
///
@@ -299,6 +300,7 @@ public SqlBulkCopy(string connectionString)
}
_connection = new SqlConnection(connectionString);
_columnMappings = new SqlBulkCopyColumnMappingCollection();
+ ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection();
_ownConnection = true;
}
@@ -368,6 +370,12 @@ public SqlBulkCopyColumnMappingCollection ColumnMappings
}
}
+ ///
+ public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints
+ {
+ get;
+ }
+
///
public string DestinationTableName
{
@@ -604,6 +612,8 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
throw SQL.BulkLoadExistingTransaction();
}
+ HashSet destColumnNames = new HashSet();
+
// Loop over the metadata for each column
_SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData;
_sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length);
@@ -636,6 +646,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
}
_sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata));
+ destColumnNames.Add(metadata.column);
nmatched++;
if (nmatched > 1)
@@ -770,12 +781,13 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
updateBulkCommandText.Append(")");
- if ((_copyOptions & (
+ if (((_copyOptions & (
SqlBulkCopyOptions.KeepNulls
| SqlBulkCopyOptions.TableLock
| SqlBulkCopyOptions.CheckConstraints
| SqlBulkCopyOptions.FireTriggers
| SqlBulkCopyOptions.AllowEncryptedValueModifications)) != SqlBulkCopyOptions.Default)
+ || ColumnOrderHints.Count > 0)
{
bool addSeparator = false; // Insert a comma character if multiple options in list
updateBulkCommandText.Append(" with (");
@@ -804,11 +816,40 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
updateBulkCommandText.Append((addSeparator ? ", " : "") + "ALLOW_ENCRYPTED_VALUE_MODIFICATIONS");
addSeparator = true;
}
+ if (ColumnOrderHints.Count > 0)
+ {
+ updateBulkCommandText.Append((addSeparator ? ", " : "") + TryGetOrderHintText(destColumnNames));
+ }
updateBulkCommandText.Append(")");
}
return (updateBulkCommandText.ToString());
}
+ private string TryGetOrderHintText(HashSet destColumnNames)
+ {
+ StringBuilder orderHintText = new StringBuilder("ORDER(");
+
+ foreach (SqlBulkCopyColumnOrderHint orderHint in ColumnOrderHints)
+ {
+ string columnNameArg = orderHint.Column;
+ if (!destColumnNames.Contains(columnNameArg))
+ {
+ // column is not valid in the destination table
+ throw SQL.BulkLoadOrderHintInvalidColumn(columnNameArg);
+ }
+ if (!string.IsNullOrEmpty(columnNameArg))
+ {
+ string columnNameEscaped = SqlServerEscapeHelper.EscapeIdentifier(SqlServerEscapeHelper.EscapeStringAsLiteral(columnNameArg));
+ string sortOrderText = orderHint.SortOrder == SortOrder.Descending ? "DESC" : "ASC";
+ orderHintText.Append($"{columnNameEscaped} {sortOrderText}, ");
+ }
+ }
+
+ orderHintText.Length = orderHintText.Length - 2;
+ orderHintText.Append(")");
+ return orderHintText.ToString();
+ }
+
private Task SubmitUpdateBulkCommand(string TDSCommand)
{
SqlClientEventSource.Log.CorrelationTraceEvent(" ObjectID{0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 0905a3ba0f..5c8ec93400 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -871,6 +871,26 @@ internal static Exception BulkLoadNonMatchingColumnName(string columnName, Excep
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadNonMatchingColumnName, columnName), e);
}
+ internal static Exception BulkLoadNullEmptyColumnName(string paramName)
+ {
+ return ADP.Argument(string.Format(System.SRHelper.GetString(SR.SQL_ParameterCannotBeEmpty), paramName));
+ }
+ internal static Exception BulkLoadUnspecifiedSortOrder()
+ {
+ return ADP.Argument(System.SRHelper.GetString(SR.SQL_BulkLoadUnspecifiedSortOrder));
+ }
+ internal static Exception BulkLoadInvalidOrderHint()
+ {
+ return ADP.Argument(System.SRHelper.GetString(SR.SQL_BulkLoadInvalidOrderHint));
+ }
+ internal static Exception BulkLoadOrderHintInvalidColumn(string columnName)
+ {
+ return ADP.InvalidOperation(string.Format(System.SRHelper.GetString(SR.SQL_BulkLoadOrderHintInvalidColumn), columnName));
+ }
+ internal static Exception BulkLoadOrderHintDuplicateColumn(string columnName)
+ {
+ return ADP.InvalidOperation(string.Format(System.SRHelper.GetString(SR.SQL_BulkLoadOrderHintDuplicateColumn), columnName));
+ }
internal static Exception BulkLoadStringTooLong(string tableName, string columnName, string truncatedValue)
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadStringTooLong, tableName, columnName, truncatedValue));
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
index 10848064a8..fd5175504c 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
@@ -2193,6 +2193,15 @@ internal class SR {
}
}
+ ///
+ /// Looks up a localized string similar to The given column order hint is not valid..
+ ///
+ internal static string SQL_BulkLoadInvalidOrderHint {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadInvalidOrderHint", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Timeout Value '{0}' is less than 0..
///
@@ -2283,6 +2292,24 @@ internal class SR {
}
}
+ ///
+ /// Looks up a localized string similar to The column '{0}' was specified more than once..
+ ///
+ internal static string SQL_BulkLoadOrderHintDuplicateColumn {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadOrderHintDuplicateColumn", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The sorted column '{0}' is not valid in the destination table..
+ ///
+ internal static string SQL_BulkLoadOrderHintInvalidColumn {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadOrderHintInvalidColumn", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Attempt to invoke bulk copy on an object that has a pending operation..
///
@@ -2301,6 +2328,15 @@ internal class SR {
}
}
+ ///
+ /// Looks up a localized string similar to A column order hint cannot have an unspecified sort order..
+ ///
+ internal static string SQL_BulkLoadUnspecifiedSortOrder {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadUnspecifiedSortOrder", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Failed to instantiate a SqlAuthenticationInitializer with type '{0}'..
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
index 18a556eb63..bab71b9600 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
@@ -1860,6 +1860,18 @@
The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
Cannot set the Credential property if 'Authentication=Active Directory Integrated' has been specified in the connection string.
@@ -1878,4 +1890,4 @@
Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
-
\ No newline at end of file
+
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 9238acc23f..345ebcea9e 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -157,6 +157,8 @@ public sealed partial class SqlBulkCopy : System.IDisposable
public int BulkCopyTimeout { get { throw null; } set { } }
///
public Microsoft.Data.SqlClient.SqlBulkCopyColumnMappingCollection ColumnMappings { get { throw null; } }
+ ///
+ public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints { get { throw null; } }
///
public string DestinationTableName { get { throw null; } set { } }
///
@@ -255,6 +257,40 @@ public sealed partial class SqlBulkCopyColumnMappingCollection : System.Collecti
///
public new void RemoveAt(int index) { }
}
+ ///
+ public sealed class SqlBulkCopyColumnOrderHint
+ {
+ ///
+ public SqlBulkCopyColumnOrderHint(string column, SortOrder sortOrder) { }
+ ///
+ public string Column { get { throw null; } set { } }
+ ///
+ public SortOrder SortOrder { get { throw null; } set { } }
+ }
+ ///
+ public sealed class SqlBulkCopyColumnOrderHintCollection : System.Collections.CollectionBase
+ {
+ ///
+ public SqlBulkCopyColumnOrderHint this[int index] { get { throw null; } }
+ ///
+ public SqlBulkCopyColumnOrderHint Add(SqlBulkCopyColumnOrderHint columnOrderHint) { throw null; }
+ ///
+ public SqlBulkCopyColumnOrderHint Add(string column, SortOrder sortOrder) { throw null; }
+ ///
+ public new void Clear() { }
+ ///
+ public bool Contains(SqlBulkCopyColumnOrderHint value) { throw null; }
+ ///
+ public void CopyTo(SqlBulkCopyColumnOrderHint[] array, int index) { }
+ ///
+ public int IndexOf(SqlBulkCopyColumnOrderHint value) { throw null; }
+ ///
+ public void Insert(int index, SqlBulkCopyColumnOrderHint columnOrderHint) { }
+ ///
+ public void Remove(SqlBulkCopyColumnOrderHint columnOrderHint) { }
+ ///
+ public new void RemoveAt(int index) { }
+ }
///
[System.FlagsAttribute]
public enum SqlBulkCopyOptions
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index b60bf1ae7d..858fadfdb4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -150,6 +150,12 @@
Microsoft\Data\SqlClient\SqlBulkCopyOptions.cs
+
+ Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHint.cs
+
+
+ Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHintCollection.cs
+
Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactory.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
index c4c92977d4..37a4ff7c30 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
@@ -333,6 +333,7 @@ public SqlBulkCopy(SqlConnection connection)
}
_connection = connection;
_columnMappings = new SqlBulkCopyColumnMappingCollection();
+ ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection();
}
///
@@ -361,6 +362,7 @@ public SqlBulkCopy(string connectionString) : this(new SqlConnection(connectionS
}
_connection = new SqlConnection(connectionString);
_columnMappings = new SqlBulkCopyColumnMappingCollection();
+ ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection();
_ownConnection = true;
}
@@ -430,6 +432,12 @@ public SqlBulkCopyColumnMappingCollection ColumnMappings
}
}
+ ///
+ public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints
+ {
+ get;
+ }
+
///
public string DestinationTableName
{
@@ -692,6 +700,8 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
throw SQL.BulkLoadExistingTransaction();
}
+ HashSet destColumnNames = new HashSet();
+
// loop over the metadata for each column
//
_SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData;
@@ -726,6 +736,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
}
_sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata));
+ destColumnNames.Add(metadata.column);
nmatched++;
if (nmatched > 1)
@@ -872,12 +883,13 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
updateBulkCommandText.Append(")");
- if ((_copyOptions & (
+ if (((_copyOptions & (
SqlBulkCopyOptions.KeepNulls
| SqlBulkCopyOptions.TableLock
| SqlBulkCopyOptions.CheckConstraints
| SqlBulkCopyOptions.FireTriggers
| SqlBulkCopyOptions.AllowEncryptedValueModifications)) != SqlBulkCopyOptions.Default)
+ || ColumnOrderHints.Count > 0)
{
bool addSeparator = false; // insert a comma character if multiple options in list ...
updateBulkCommandText.Append(" with (");
@@ -906,11 +918,40 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
updateBulkCommandText.Append((addSeparator ? ", " : "") + "ALLOW_ENCRYPTED_VALUE_MODIFICATIONS");
addSeparator = true;
}
+ if (ColumnOrderHints.Count > 0)
+ {
+ updateBulkCommandText.Append((addSeparator ? ", " : "") + TryGetOrderHintText(destColumnNames));
+ }
updateBulkCommandText.Append(")");
}
return (updateBulkCommandText.ToString());
}
+ private string TryGetOrderHintText(HashSet destColumnNames)
+ {
+ StringBuilder orderHintText = new StringBuilder("ORDER(");
+
+ foreach (SqlBulkCopyColumnOrderHint orderHint in ColumnOrderHints)
+ {
+ string columnNameArg = orderHint.Column;
+ if (!destColumnNames.Contains(columnNameArg))
+ {
+ // column is not valid in the destination table
+ throw SQL.BulkLoadOrderHintInvalidColumn(columnNameArg);
+ }
+ if (!string.IsNullOrEmpty(columnNameArg))
+ {
+ string columnNameEscaped = SqlServerEscapeHelper.EscapeIdentifier(SqlServerEscapeHelper.EscapeStringAsLiteral(columnNameArg));
+ string sortOrderText = orderHint.SortOrder == SortOrder.Descending ? "DESC" : "ASC";
+ orderHintText.Append($"{columnNameEscaped} {sortOrderText}, ");
+ }
+ }
+
+ orderHintText.Length = orderHintText.Length - 2;
+ orderHintText.Append(")");
+ return orderHintText.ToString();
+ }
+
// submitts the updatebulk command
//
private Task SubmitUpdateBulkCommand(string TDSCommand)
@@ -2934,18 +2975,18 @@ private void CopyBatchesAsyncContinuedOnError(bool cleanupParser)
{
tdsReliabilitySection.Start();
#endif //DEBUG
- if ((cleanupParser) && (_parser != null) && (_stateObj != null))
- {
- _parser._asyncWrite = false;
- Task task = _parser.WriteBulkCopyDone(_stateObj);
- Debug.Assert(task == null, "Write should not pend when error occurs");
- RunParser();
- }
+ if ((cleanupParser) && (_parser != null) && (_stateObj != null))
+ {
+ _parser._asyncWrite = false;
+ Task task = _parser.WriteBulkCopyDone(_stateObj);
+ Debug.Assert(task == null, "Write should not pend when error occurs");
+ RunParser();
+ }
- if (_stateObj != null)
- {
- CleanUpStateObject();
- }
+ if (_stateObj != null)
+ {
+ CleanUpStateObject();
+ }
#if DEBUG
}
finally
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 49bec90d25..6cb5b7ed8c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -1015,6 +1015,26 @@ static internal Exception BulkLoadNonMatchingColumnName(string columnName, Excep
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadNonMatchingColumnName, columnName), e);
}
+ internal static Exception BulkLoadNullEmptyColumnName(string paramName)
+ {
+ return ADP.Argument(string.Format(StringsHelper.GetString(Strings.SQL_ParameterCannotBeEmpty), paramName));
+ }
+ internal static Exception BulkLoadUnspecifiedSortOrder()
+ {
+ return ADP.Argument(StringsHelper.GetString(Strings.SQL_BulkLoadUnspecifiedSortOrder));
+ }
+ internal static Exception BulkLoadInvalidOrderHint()
+ {
+ return ADP.Argument(StringsHelper.GetString(Strings.SQL_BulkLoadInvalidOrderHint));
+ }
+ internal static Exception BulkLoadOrderHintInvalidColumn(string columnName)
+ {
+ return ADP.InvalidOperation(string.Format(StringsHelper.GetString(Strings.SQL_BulkLoadOrderHintInvalidColumn), columnName));
+ }
+ internal static Exception BulkLoadOrderHintDuplicateColumn(string columnName)
+ {
+ return ADP.InvalidOperation(string.Format(StringsHelper.GetString(Strings.SQL_BulkLoadOrderHintDuplicateColumn), columnName));
+ }
static internal Exception BulkLoadStringTooLong(string tableName, string columnName, string truncatedValue)
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadStringTooLong, tableName, columnName, truncatedValue));
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index 2d0d237f75..9f40b57bdd 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -8910,6 +8910,15 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to The given column order hint is not valid..
+ ///
+ internal static string SQL_BulkLoadInvalidOrderHint {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadInvalidOrderHint", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Timeout Value '{0}' is less than 0..
///
@@ -9000,6 +9009,24 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to The column '{0}' was specified more than once..
+ ///
+ internal static string SQL_BulkLoadOrderHintDuplicateColumn {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadOrderHintDuplicateColumn", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The sorted column '{0}' is not valid in the destination table..
+ ///
+ internal static string SQL_BulkLoadOrderHintInvalidColumn {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadOrderHintInvalidColumn", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Attempt to invoke bulk copy on an object that has a pending operation..
///
@@ -9018,6 +9045,15 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to A column order hint cannot have an unspecified sort order..
+ ///
+ internal static string SQL_BulkLoadUnspecifiedSortOrder {
+ get {
+ return ResourceManager.GetString("SQL_BulkLoadUnspecifiedSortOrder", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Failed to instantiate a SqlAuthenticationInitializer with type '{0}'..
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 628471f836..0bca401c21 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4530,6 +4530,18 @@
UDT size must be less than {1}, size: {0}
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
Unsupported authentication specified in this context: {0}
@@ -4542,4 +4554,4 @@
Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
-
\ No newline at end of file
+
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHint.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHint.cs
new file mode 100644
index 0000000000..2329df4140
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHint.cs
@@ -0,0 +1,66 @@
+// 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 System;
+
+namespace Microsoft.Data.SqlClient
+{
+ ///
+ public sealed class SqlBulkCopyColumnOrderHint
+ {
+ ///
+ public SqlBulkCopyColumnOrderHint(string column, SortOrder sortOrder)
+ {
+ Column = column;
+ SortOrder = sortOrder;
+ }
+
+ private string _columnName;
+ private SortOrder _sortOrder;
+
+ internal event EventHandler NameChanging;
+
+ ///
+ public string Column
+ {
+ get => _columnName ?? string.Empty;
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw SQL.BulkLoadNullEmptyColumnName(nameof(Column));
+ }
+ // Do nothing if column name is the same
+ if (_columnName != value)
+ {
+ OnNameChanging(value);
+ _columnName = value;
+ }
+ }
+ }
+
+ ///
+ public SortOrder SortOrder
+ {
+ get => _sortOrder;
+ set
+ {
+ if (value != SortOrder.Unspecified)
+ {
+ _sortOrder = value;
+ }
+ else
+ {
+ throw SQL.BulkLoadUnspecifiedSortOrder();
+ }
+ }
+ }
+
+ private void OnNameChanging(string newName)
+ {
+ var handler = NameChanging;
+ handler?.Invoke(this, newName);
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHintCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHintCollection.cs
new file mode 100644
index 0000000000..82fda1ad7d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnOrderHintCollection.cs
@@ -0,0 +1,133 @@
+// 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 System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Data.SqlClient
+{
+ ///
+ public sealed class SqlBulkCopyColumnOrderHintCollection : CollectionBase
+ {
+ private readonly HashSet _columnNames = new HashSet();
+
+ ///
+ public SqlBulkCopyColumnOrderHint this[int index] => (SqlBulkCopyColumnOrderHint)List[index];
+
+ ///
+ public SqlBulkCopyColumnOrderHint Add(SqlBulkCopyColumnOrderHint columnOrderHint)
+ {
+ if (columnOrderHint == null)
+ {
+ throw new ArgumentNullException(nameof(columnOrderHint));
+ }
+ if (string.IsNullOrEmpty(columnOrderHint.Column) ||
+ columnOrderHint.SortOrder == SortOrder.Unspecified)
+ {
+ throw SQL.BulkLoadInvalidOrderHint();
+ }
+ RegisterColumnName(columnOrderHint, columnOrderHint.Column);
+ InnerList.Add(columnOrderHint);
+ return columnOrderHint;
+ }
+
+ ///
+ public SqlBulkCopyColumnOrderHint Add(string column, SortOrder sortOrder) => Add(new SqlBulkCopyColumnOrderHint(column, sortOrder));
+
+ ///
+ /// Invoked before the collection is cleared using Clear(). Unregisters each order hint.
+ ///
+ protected override void OnClear()
+ {
+ foreach (SqlBulkCopyColumnOrderHint orderHint in InnerList)
+ {
+ UnregisterColumnName(orderHint, orderHint.Column);
+ }
+ }
+
+ ///
+ public bool Contains(SqlBulkCopyColumnOrderHint value) => InnerList.Contains(value);
+
+ ///
+ public void CopyTo(SqlBulkCopyColumnOrderHint[] array, int index) => InnerList.CopyTo(array, index);
+
+ ///
+ public int IndexOf(SqlBulkCopyColumnOrderHint value) => InnerList.IndexOf(value);
+
+ ///
+ public void Insert(int index, SqlBulkCopyColumnOrderHint columnOrderHint)
+ {
+ // Try inserting into an invalid index to throw an exception
+ if (index < 0 || index > InnerList.Count)
+ {
+ InnerList.Insert(index, columnOrderHint);
+ }
+ if (columnOrderHint == null)
+ {
+ throw new ArgumentNullException(nameof(columnOrderHint));
+ }
+ RegisterColumnName(columnOrderHint, columnOrderHint.Column);
+ InnerList.Insert(index, columnOrderHint);
+ }
+
+ ///
+ public void Remove(SqlBulkCopyColumnOrderHint columnOrderHint)
+ {
+ if (columnOrderHint == null)
+ {
+ throw new ArgumentNullException(nameof(columnOrderHint));
+ }
+ // OnRemove only works with the List instance and not the InnerList instance
+ List.Remove(columnOrderHint);
+ }
+
+ ///
+ /// Invoked before the order hint is removed using Remove() or RemoveAt(). Unregisters the order hint.
+ ///
+ protected override void OnRemove(int index, object value)
+ {
+ if (value is SqlBulkCopyColumnOrderHint orderHint)
+ {
+ UnregisterColumnName(orderHint, orderHint.Column);
+ }
+ else
+ {
+ throw new ArgumentNullException(nameof(orderHint));
+ }
+ }
+
+ private void ColumnNameChanging(object sender, string newName)
+ {
+ if (sender is SqlBulkCopyColumnOrderHint orderHint)
+ {
+ if (_columnNames.Contains(newName))
+ {
+ throw SQL.BulkLoadOrderHintDuplicateColumn(newName);
+ }
+ UnregisterColumnName(orderHint, orderHint.Column);
+ RegisterColumnName(orderHint, newName);
+ }
+ }
+
+ private void RegisterColumnName(SqlBulkCopyColumnOrderHint orderHint, string columnName)
+ {
+ if (_columnNames.Contains(columnName))
+ {
+ throw SQL.BulkLoadOrderHintDuplicateColumn(orderHint.Column);
+ }
+ _columnNames.Add(columnName);
+ orderHint.NameChanging += ColumnNameChanging;
+ }
+
+ private void UnregisterColumnName(SqlBulkCopyColumnOrderHint orderHint, string columnName)
+ {
+ if (Contains(orderHint))
+ {
+ _columnNames.Remove(columnName);
+ orderHint.NameChanging -= ColumnNameChanging;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
index 63371aaa78..c0cdf6fd85 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
@@ -44,6 +44,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBulkCopyColumnOrderHintCollectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBulkCopyColumnOrderHintCollectionTest.cs
new file mode 100644
index 0000000000..b6104335dd
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBulkCopyColumnOrderHintCollectionTest.cs
@@ -0,0 +1,583 @@
+// 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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.Tests
+{
+ public class SqlBulkCopyColumnOrderHintCollectionTest
+ {
+ private static SqlBulkCopyColumnOrderHintCollection CreateCollection() => new SqlBulkCopy(new SqlConnection()).ColumnOrderHints;
+
+ private static SqlBulkCopyColumnOrderHintCollection CreateCollection(params SqlBulkCopyColumnOrderHint[] orderHints)
+ {
+ Assert.NotNull(orderHints);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+
+ foreach (SqlBulkCopyColumnOrderHint orderHint in orderHints)
+ {
+ collection.Add(orderHint);
+ }
+
+ return collection;
+ }
+
+ [Fact]
+ public void Properties_ReturnFalse()
+ {
+ IList list = CreateCollection();
+ Assert.False(list.IsSynchronized);
+ Assert.False(list.IsFixedSize);
+ Assert.False(list.IsReadOnly);
+ }
+
+ [Fact]
+ public void Methods_NullParameterPassed_ThrowsArgumentNullException()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+ collection.Add(new SqlBulkCopyColumnOrderHint("column", SortOrder.Ascending));
+
+ Assert.Throws(() => collection.CopyTo(null, 0));
+ Assert.Throws(() => collection.Add(null));
+
+ Assert.Throws(() => collection.Insert(0, null));
+ Assert.Throws(() => collection.Remove(null));
+
+ IList list = collection;
+ Assert.Throws(() => list[0] = null);
+ Assert.Throws(() => list.Add(null));
+ Assert.Throws(() => list.CopyTo(null, 0));
+ Assert.Throws(() => list.Insert(0, null));
+ Assert.Throws(() => list.Remove(null));
+ }
+
+ [Fact]
+ public void Members_InvalidRange_ThrowsArgumentOutOfRangeException()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+
+ var item = new SqlBulkCopyColumnOrderHint("column", SortOrder.Ascending);
+
+ Assert.Throws(() => collection[-1]);
+ Assert.Throws(() => collection[collection.Count]);
+ Assert.Throws(() => collection.Insert(-1, item));
+ Assert.Throws(() => collection.Insert(collection.Count + 1, item));
+ Assert.Throws(() => collection.RemoveAt(-1));
+ Assert.Throws(() => collection.RemoveAt(collection.Count));
+
+ IList list = collection;
+ Assert.Throws(() => list[-1]);
+ Assert.Throws(() => list[collection.Count]);
+ Assert.Throws(() => list[-1] = item);
+ Assert.Throws(() => list[collection.Count] = item);
+ Assert.Throws(() => list.Insert(-1, item));
+ Assert.Throws(() => list.Insert(collection.Count + 1, item));
+ Assert.Throws(() => list.RemoveAt(-1));
+ Assert.Throws(() => list.RemoveAt(collection.Count));
+ }
+
+ [Fact]
+ public void Add_AddItems_ItemsAddedAsEpected()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ Assert.Same(item1, collection.Add(item1));
+ Assert.Same(item1, collection[0]);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ Assert.Same(item2, collection.Add(item2));
+ Assert.Same(item2, collection[1]);
+
+ IList list = CreateCollection();
+ int index = list.Add(item1);
+ Assert.Equal(0, index);
+ Assert.Same(item1, list[0]);
+ index = list.Add(item2);
+ Assert.Equal(1, index);
+ Assert.Same(item2, list[1]);
+ }
+
+ [Fact]
+ public void Add_HelperOverloads_ItemsAddedAsExpected()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+ SqlBulkCopyColumnOrderHint item;
+
+ item = collection.Add("column1", SortOrder.Ascending);
+ Assert.NotNull(item);
+ Assert.Equal("column1", item.Column);
+ Assert.Equal(SortOrder.Ascending, item.SortOrder);
+ }
+
+ [Fact]
+ public void Add_InvalidItems_ThrowsArgumentException()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+ Assert.Throws(() => collection.Add(new SqlBulkCopyColumnOrderHint(null, SortOrder.Ascending)));
+ Assert.Throws(() => collection.Add(new SqlBulkCopyColumnOrderHint(null, SortOrder.Unspecified)));
+ Assert.Throws(() => collection.Add(new SqlBulkCopyColumnOrderHint("", SortOrder.Descending)));
+ Assert.Throws(() => collection.Add(new SqlBulkCopyColumnOrderHint("", SortOrder.Unspecified)));
+ Assert.Throws(() => collection.Add(new SqlBulkCopyColumnOrderHint("column", SortOrder.Unspecified)));
+
+ IList list = CreateCollection();
+ Assert.Throws(() => list.Add(new SqlBulkCopyColumnOrderHint(null, SortOrder.Ascending)));
+ Assert.Throws(() => list.Add(new SqlBulkCopyColumnOrderHint(null, SortOrder.Unspecified)));
+ Assert.Throws(() => list.Add(new SqlBulkCopyColumnOrderHint("", SortOrder.Descending)));
+ Assert.Throws(() => list.Add(new SqlBulkCopyColumnOrderHint("", SortOrder.Unspecified)));
+ Assert.Throws(() => list.Add(new SqlBulkCopyColumnOrderHint("column", SortOrder.Unspecified)));
+ }
+
+ [Fact]
+ public void IListAddInsert_InsertNonSqlBulkCopyColumnOrderHintItems_DoNotThrow()
+ {
+ IList list = CreateCollection();
+ list.Add(new SqlBulkCopyColumnOrderHint("column", SortOrder.Descending));
+
+ // The following operations should really throw ArgumentException due to the
+ // mismatched types, but do not throw in the full framework.
+ string foo = "foo";
+ list[0] = foo;
+ list.Add(foo);
+ list.Insert(0, foo);
+ }
+
+ [Fact]
+ public void GetEnumerator_NoItems_EmptyEnumerator()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+ IEnumerator e = collection.GetEnumerator();
+ Assert.Throws(() => e.Current);
+ Assert.False(e.MoveNext());
+ Assert.Throws(() => e.Current);
+ }
+
+ [Fact]
+ public void GetEnumerator_ItemsAdded_AllItemsReturnedAndEnumeratorBehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Descending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+
+ IEnumerator e = collection.GetEnumerator();
+
+ const int Iterations = 2;
+ for (int i = 0; i < Iterations; i++)
+ {
+ // Not started
+ Assert.Throws(() => e.Current);
+
+ Assert.True(e.MoveNext());
+ Assert.Same(item1, e.Current);
+
+ Assert.True(e.MoveNext());
+ Assert.Same(item2, e.Current);
+
+ Assert.True(e.MoveNext());
+ Assert.Same(item3, e.Current);
+
+ Assert.False(e.MoveNext());
+ Assert.False(e.MoveNext());
+ Assert.False(e.MoveNext());
+ Assert.False(e.MoveNext());
+ Assert.False(e.MoveNext());
+
+ // Ended
+ Assert.Throws(() => e.Current);
+
+ e.Reset();
+ }
+ }
+
+ [Fact]
+ public void GetEnumerator_ItemsAdded_ItemsFromEnumeratorMatchesItemsFromIndexer()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Descending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+ int index = 0;
+ foreach (SqlBulkCopyColumnOrderHint enumeratorItem in collection)
+ {
+ SqlBulkCopyColumnOrderHint indexerItem = collection[index];
+
+ Assert.NotNull(enumeratorItem);
+ Assert.NotNull(indexerItem);
+
+ Assert.Same(indexerItem, enumeratorItem);
+ index++;
+ }
+ }
+
+ [Fact]
+ public void GetEnumerator_ModifiedCollectionDuringEnumeration_ThrowsInvalidOperationException()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+
+ IEnumerator e = collection.GetEnumerator();
+
+ collection.Add("column", SortOrder.Ascending);
+
+ // Collection changed.
+ Assert.Throws(() => e.MoveNext());
+ Assert.Throws(() => e.Reset());
+ }
+
+ [Fact]
+ public void Contains_ItemsAdded_MatchesExpectation()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+
+ Assert.True(collection.Contains(item1));
+ Assert.True(collection.Contains(item2));
+ Assert.True(collection.Contains(item3));
+ Assert.False(collection.Contains(null));
+
+ IList list = collection;
+ Assert.True(list.Contains(item1));
+ Assert.True(list.Contains(item2));
+ Assert.True(list.Contains(item3));
+ Assert.False(list.Contains(null));
+ Assert.False(list.Contains("Bogus"));
+ }
+
+ [Fact]
+ public void CopyTo_ItemsAdded_ItemsCopiedToArray()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+
+ var array1 = new SqlBulkCopyColumnOrderHint[collection.Count];
+ collection.CopyTo(array1, 0);
+
+ Assert.Same(item1, array1[0]);
+ Assert.Same(item2, array1[1]);
+ Assert.Same(item3, array1[2]);
+
+ var array2 = new SqlBulkCopyColumnOrderHint[collection.Count];
+ ((ICollection)collection).CopyTo(array2, 0);
+
+ Assert.Same(item1, array2[0]);
+ Assert.Same(item2, array2[1]);
+ Assert.Same(item3, array2[2]);
+ }
+
+ [Fact]
+ public void CopyTo_InvalidArrayType_Throws()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ ICollection collection = CreateCollection(item1, item2, item3);
+
+ Assert.Throws(() => collection.CopyTo(new int[collection.Count], 0));
+ Assert.Throws(() => collection.CopyTo(new string[collection.Count], 0));
+ }
+
+ [Fact]
+ public void Indexer_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+
+ Assert.Same(item1, collection[0]);
+ Assert.Same(item2, collection[1]);
+ Assert.Same(item3, collection[2]);
+
+ IList list = collection;
+ list[0] = item2;
+ list[1] = item3;
+ list[2] = item1;
+ Assert.Same(item2, list[0]);
+ Assert.Same(item3, list[1]);
+ Assert.Same(item1, list[2]);
+ }
+
+ [Fact]
+ public void IndexOf_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2);
+
+ Assert.Equal(0, collection.IndexOf(item1));
+ Assert.Equal(1, collection.IndexOf(item2));
+ Assert.Equal(-1, collection.IndexOf(item3));
+
+ IList list = collection;
+ Assert.Equal(0, list.IndexOf(item1));
+ Assert.Equal(1, list.IndexOf(item2));
+ Assert.Equal(-1, list.IndexOf(item3));
+ Assert.Equal(-1, list.IndexOf("bogus"));
+ }
+
+ [Fact]
+ public void Insert_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+
+ collection.Insert(0, item3);
+ collection.Insert(0, item2);
+ collection.Insert(0, item1);
+
+ Assert.Equal(3, collection.Count);
+ Assert.Same(item1, collection[0]);
+ Assert.Same(item2, collection[1]);
+ Assert.Same(item3, collection[2]);
+ }
+
+ [Fact]
+ public void InsertAndClear_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection();
+
+ collection.Insert(0, item1);
+ collection.Insert(1, item2);
+ collection.Insert(2, item3);
+ Assert.Equal(3, collection.Count);
+ Assert.Same(item1, collection[0]);
+ Assert.Same(item2, collection[1]);
+ Assert.Same(item3, collection[2]);
+
+ collection.Clear();
+ Assert.Empty(collection);
+
+ collection.Add(item1);
+ collection.Add(item3);
+ Assert.Equal(2, collection.Count);
+ Assert.Same(item1, collection[0]);
+ Assert.Same(item3, collection[1]);
+
+ collection.Insert(1, item2);
+ Assert.Equal(3, collection.Count);
+ Assert.Same(item1, collection[0]);
+ Assert.Same(item2, collection[1]);
+ Assert.Same(item3, collection[2]);
+
+ collection.Clear();
+ Assert.Empty(collection);
+
+ IList list = collection;
+ list.Insert(0, item1);
+ list.Insert(1, item2);
+ list.Insert(2, item3);
+ Assert.Equal(3, list.Count);
+ Assert.Same(item1, list[0]);
+ Assert.Same(item2, list[1]);
+ Assert.Same(item3, list[2]);
+
+ list.Clear();
+ Assert.Equal(0, list.Count);
+
+ list.Add(item1);
+ list.Add(item3);
+ Assert.Equal(2, list.Count);
+ Assert.Same(item1, list[0]);
+ Assert.Same(item3, list[1]);
+
+ list.Insert(1, item2);
+ Assert.Equal(3, list.Count);
+ Assert.Same(item1, list[0]);
+ Assert.Same(item2, list[1]);
+ Assert.Same(item3, list[2]);
+
+ list.Clear();
+ Assert.Equal(0, list.Count);
+ }
+
+ [Fact]
+ public void Remove_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2);
+
+ collection.Remove(item1);
+ Assert.Single(collection);
+ Assert.Same(item2, collection[0]);
+
+ collection.Remove(item2);
+ Assert.Empty(collection);
+
+ AssertExtensions.Throws(() => collection.Remove(item2));
+ AssertExtensions.Throws(() => collection.Remove(new SqlBulkCopyColumnOrderHint("column3", SortOrder.Descending)));
+
+ IList list = CreateCollection(item1, item2);
+
+ list.Remove(item1);
+ Assert.Equal(1, list.Count);
+ Assert.Same(item2, list[0]);
+
+ list.Remove(item2);
+ Assert.Equal(0, list.Count);
+
+ AssertExtensions.Throws(null, () => list.Remove(item2));
+ AssertExtensions.Throws(null, () => list.Remove(new SqlBulkCopyColumnOrderHint("column4", SortOrder.Ascending)));
+ AssertExtensions.Throws(null, () => list.Remove("bogus"));
+ }
+
+ [Fact]
+ public void RemoveAt_BehavesAsExpected()
+ {
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ var item2 = new SqlBulkCopyColumnOrderHint("column2", SortOrder.Descending);
+ var item3 = new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending);
+
+ SqlBulkCopyColumnOrderHintCollection collection = CreateCollection(item1, item2, item3);
+
+ collection.RemoveAt(0);
+ Assert.Equal(2, collection.Count);
+ Assert.Same(item2, collection[0]);
+ Assert.Same(item3, collection[1]);
+
+ collection.RemoveAt(1);
+ Assert.Single(collection);
+ Assert.Same(item2, collection[0]);
+
+ collection.RemoveAt(0);
+ Assert.Empty(collection);
+
+ IList list = CreateCollection(item1, item2, item3);
+
+ list.RemoveAt(0);
+ Assert.Equal(2, list.Count);
+ Assert.Same(item2, list[0]);
+ Assert.Same(item3, list[1]);
+ list.RemoveAt(1);
+ Assert.Equal(1, list.Count);
+ Assert.Same(item2, list[0]);
+
+ list.RemoveAt(0);
+ Assert.Equal(0, list.Count);
+ }
+
+ [Fact]
+ public void SyncRoot_NotNullAndSameObject()
+ {
+ ICollection collection = CreateCollection();
+ Assert.NotNull(collection.SyncRoot);
+ Assert.Same(collection.SyncRoot, collection.SyncRoot);
+ }
+
+ [Fact]
+ public void Add_DuplicateColumnNames_NotAllowed()
+ {
+ SqlBulkCopyColumnOrderHintCollection collection1 = CreateCollection();
+ SqlBulkCopyColumnOrderHintCollection collection2 = CreateCollection();
+ var item1 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+
+ collection1.Add(item1);
+ item1.Column += "2";
+ collection1[0].Column += "3";
+ Assert.Equal("column123", item1.Column);
+ Assert.Throws(() => collection1.Add(item1));
+
+ collection2.Add(item1);
+ item1.Column += "4";
+ Assert.Same(collection1[0], collection2[0]);
+ Assert.Equal("column1234", collection1[0].Column);
+
+ item1.Column = "column1";
+ collection1.Add("column2", SortOrder.Ascending);
+ item1.Column = "column1";
+ collection1[0].Column = "column1";
+ collection1[1].Column = "column2";
+ Assert.Throws(() => item1.Column = "column2");
+ Assert.Throws(() => collection1[0].Column = "column2");
+ Assert.Throws(() => collection1[1].Column = "column1");
+ Assert.Throws(() => collection2[0].Column = "column2");
+ Assert.Equal("column1", collection1[0].Column);
+ Assert.Equal("column2", collection1[1].Column);
+ ValidateCollection(collection1, expectedCount: 2);
+
+ Assert.Throws(() => collection2.Add(collection1[0]));
+ collection2.Add(collection1[1]);
+ var item3 = new SqlBulkCopyColumnOrderHint("column1", SortOrder.Ascending);
+ Assert.Throws(() => collection1.Add(item3));
+ item3.Column = "column3";
+ collection1.Add(item3);
+ ValidateCollection(collection1, expectedCount: 3);
+ ValidateCollection(collection2, expectedCount: 2);
+
+ Assert.Throws(() => collection1.Insert(0, new SqlBulkCopyColumnOrderHint("column3", SortOrder.Ascending)));
+ collection1.Insert(0, new SqlBulkCopyColumnOrderHint("column4", SortOrder.Ascending));
+ collection1.Insert(collection1.Count, new SqlBulkCopyColumnOrderHint("column5", SortOrder.Ascending));
+ Assert.Throws(() => collection1[collection1.IndexOf(item1)].Column = "column4");
+ ValidateCollection(collection1, expectedCount: 5);
+
+ collection2.Remove(item1);
+ Assert.Throws(() => collection2[0].Column = item1.Column);
+ collection2[0].Column = "column6";
+ ValidateCollection(collection2, expectedCount: 1);
+
+ collection1.Clear();
+ Assert.Empty(collection1);
+ collection1.Add("column1", SortOrder.Descending);
+ collection1.Add("column2", SortOrder.Descending);
+ collection1.Add("column3", SortOrder.Descending);
+ collection2[0].Column = collection1[0].Column;
+ Assert.Throws(() => collection1.Add(collection2[0]));
+ ValidateCollection(collection1, expectedCount: 3);
+ collection1.RemoveAt(0);
+ collection1.Add(collection2[0]);
+ collection1.Remove(collection1[collection1.Count - 1]);
+ collection1.RemoveAt(1);
+ collection1.Remove(collection1[collection1.Count - 1]);
+ collection1.Add("column1", SortOrder.Descending);
+ collection1.Add("column2", SortOrder.Descending);
+ collection1.Add("column3", SortOrder.Descending);
+ collection1.Add("column4", SortOrder.Descending);
+ collection1.Add("column5", SortOrder.Descending);
+ ValidateCollection(collection1, expectedCount: 5);
+ }
+
+ // verifies that the collection contains no duplicate column names
+ private void ValidateCollection(SqlBulkCopyColumnOrderHintCollection collection, int expectedCount)
+ {
+ Assert.True(expectedCount == collection.Count, "Collection was not the expected size.");
+ bool valid = true;
+ HashSet columnNames = new HashSet();
+ foreach (SqlBulkCopyColumnOrderHint orderHint in collection)
+ {
+ if (!columnNames.Contains(orderHint.Column))
+ {
+ columnNames.Add(orderHint.Column);
+ }
+ else
+ {
+ valid = false;
+ }
+ }
+ Assert.True(valid, "Collection contained a duplicate column name.");
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index 52c66b3d1b..d0152fca05 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -61,6 +61,12 @@
Common\System\Collections\DictionaryExtensions.cs
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs
new file mode 100644
index 0000000000..7145679431
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs
@@ -0,0 +1,150 @@
+// 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 System;
+using System.Data;
+using System.Data.Common;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class OrderHint
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string sourceTable2 = "Employees";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50)); CREATE CLUSTERED INDEX IX_Test_Table_Customer_ID ON {0} (CustomerID DESC)";
+ private static readonly string initialQueryTemplate2 = "create table {0} (LastName nvarchar(50), FirstName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}";
+ private static readonly string sourceQueryTemplate2 = "SELECT LastName, FirstName FROM {0}";
+ private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable, string dstTable2)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+ string sourceQuery2 = string.Format(sourceQueryTemplate2, sourceTable2);
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string initialQuery2 = string.Format(initialQueryTemplate2, dstTable2);
+ string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable);
+ string getRowCountQuery2 = string.Format(getRowCountQueryTemplate, sourceTable2);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ dstConn.Open();
+ Helpers.TryExecute(dstCmd, initialQuery);
+ Helpers.TryExecute(dstCmd, initialQuery2);
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(getRowCountQuery, srcConn))
+ {
+ srcConn.Open();
+ try
+ {
+ int nRowsInSource = Convert.ToInt32(srcCmd.ExecuteScalar());
+ srcCmd.CommandText = getRowCountQuery2;
+ int nRowsInSource2 = Convert.ToInt32(srcCmd.ExecuteScalar());
+ srcCmd.CommandText = sourceQuery;
+ using (SqlBulkCopy bulkCopy = new SqlBulkCopy(dstConn))
+ {
+ bulkCopy.DestinationTableName = dstTable;
+
+ // no hints
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkCopy.WriteToServer(reader);
+ }
+
+ // hint for 1 of 3 columns
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkCopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ bulkCopy.WriteToServer(reader);
+ }
+
+ // hints for all 3 columns
+ // order of hints is not the same as column order in table
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkCopy.ColumnOrderHints.Add("ContactName", SortOrder.Descending);
+ bulkCopy.ColumnOrderHints.Add("CompanyName", SortOrder.Ascending);
+ bulkCopy.WriteToServer(reader);
+ }
+
+ // add column mappings
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkCopy.ColumnMappings.Add(0, 1);
+ bulkCopy.ColumnMappings.Add(1, 2);
+ bulkCopy.ColumnMappings.Add(2, 0);
+ bulkCopy.WriteToServer(reader);
+ }
+
+ // WriteToServer DataTable overload
+ using (SqlDataAdapter dataAdapter = new SqlDataAdapter(srcCmd))
+ {
+ DataTable dataTable = new DataTable();
+ dataAdapter.Fill(dataTable);
+ bulkCopy.WriteToServer(dataTable);
+ }
+
+ // WriteToServer DataRow[] overload
+ using (SqlDataAdapter dataAdapter = new SqlDataAdapter(srcCmd))
+ {
+ DataTable dataTable = new DataTable();
+ dataAdapter.Fill(dataTable);
+ bulkCopy.WriteToServer(dataTable.Select());
+ }
+
+ // WriteToServer DataTable, DataRowState overload
+ using (SqlDataAdapter dataAdapter = new SqlDataAdapter(srcCmd))
+ {
+ DataTable dataTable = new DataTable();
+ dataAdapter.Fill(dataTable);
+ DataRow[] x = dataTable.Select();
+ bulkCopy.WriteToServer(dataTable, DataRowState.Unchanged);
+ }
+ }
+
+ // add copy options
+ SqlBulkCopyOptions copyOptions =
+ SqlBulkCopyOptions.AllowEncryptedValueModifications |
+ SqlBulkCopyOptions.FireTriggers |
+ SqlBulkCopyOptions.KeepNulls;
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, copyOptions, null))
+ {
+ bulkcopy.DestinationTableName = dstTable;
+ bulkcopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ bulkcopy.ColumnOrderHints.Add("CompanyName", SortOrder.Ascending);
+ bulkcopy.ColumnOrderHints.Add("ContactName", SortOrder.Descending);
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkcopy.WriteToServer(reader);
+ }
+
+ const int nWriteToServerCalls = 8;
+ Helpers.VerifyResults(dstConn, dstTable, 3, nRowsInSource * nWriteToServerCalls);
+
+ // different tables
+ srcCmd.CommandText = sourceQuery2;
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkcopy.DestinationTableName = dstTable2;
+ bulkcopy.ColumnOrderHints.Clear();
+ bulkcopy.ColumnOrderHints.Add("LastName", SortOrder.Descending);
+ bulkcopy.WriteToServer(reader);
+ Helpers.VerifyResults(dstConn, dstTable2, 2, nRowsInSource2);
+ }
+ }
+ }
+ finally
+ {
+ DataTestUtility.DropTable(dstConn, dstTable);
+ DataTestUtility.DropTable(dstConn, dstTable2);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs
new file mode 100644
index 0000000000..d9c4515428
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs
@@ -0,0 +1,132 @@
+// 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 System;
+using System.Data;
+using System.Data.Common;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class OrderHintAsync
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string sourceTable2 = "Employees";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50)); CREATE CLUSTERED INDEX IX_Test_Table_Customer_ID ON {0} (CustomerID DESC)";
+ private static readonly string initialQueryTemplate2 = "create table {0} (LastName nvarchar(50), FirstName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}";
+ private static readonly string sourceQueryTemplate2 = "SELECT LastName, FirstName FROM {0}";
+ private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable, string dstTable2)
+ {
+ Task t = TestAsync(srcConstr, dstTable, dstTable2);
+ t.Wait();
+ Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status);
+ }
+
+ public static async Task TestAsync(string srcConstr, string dstTable, string dstTable2)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+ string sourceQuery2 = string.Format(sourceQueryTemplate2, sourceTable2);
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string initialQuery2 = string.Format(initialQueryTemplate2, dstTable2);
+ string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable);
+ string getRowCountQuery2 = string.Format(getRowCountQueryTemplate, sourceTable2);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ await dstConn.OpenAsync();
+ Helpers.TryExecute(dstCmd, initialQuery);
+ Helpers.TryExecute(dstCmd, initialQuery2);
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(getRowCountQuery, srcConn))
+ {
+ await srcConn.OpenAsync();
+ try
+ {
+ int nRowsInSource = Convert.ToInt32(await srcCmd.ExecuteScalarAsync());
+ srcCmd.CommandText = getRowCountQuery2;
+ int nRowsInSource2 = Convert.ToInt32(await srcCmd.ExecuteScalarAsync());
+ srcCmd.CommandText = sourceQuery;
+ using (SqlBulkCopy bulkCopy = new SqlBulkCopy(dstConn))
+ {
+ bulkCopy.DestinationTableName = dstTable;
+
+ // no hints
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ await bulkCopy.WriteToServerAsync(reader);
+ }
+
+ // hint for 1 of 3 columns
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ bulkCopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ await bulkCopy.WriteToServerAsync(reader);
+ }
+
+ // hints for all 3 columns
+ // order of hints is not the same as column order in table
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ bulkCopy.ColumnOrderHints.Add("ContactName", SortOrder.Descending);
+ bulkCopy.ColumnOrderHints.Add("CompanyName", SortOrder.Ascending);
+ await bulkCopy.WriteToServerAsync(reader);
+ }
+
+ // add column mappings
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ bulkCopy.ColumnMappings.Add(0, 1);
+ bulkCopy.ColumnMappings.Add(1, 2);
+ bulkCopy.ColumnMappings.Add(2, 0);
+ await bulkCopy.WriteToServerAsync(reader);
+ }
+ }
+
+ // add copy options
+ SqlBulkCopyOptions copyOptions = SqlBulkCopyOptions.AllowEncryptedValueModifications
+ | SqlBulkCopyOptions.FireTriggers
+ | SqlBulkCopyOptions.KeepNulls;
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, copyOptions, null))
+ {
+ bulkcopy.DestinationTableName = dstTable;
+ bulkcopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ bulkcopy.ColumnOrderHints.Add("CompanyName", SortOrder.Ascending);
+ bulkcopy.ColumnOrderHints.Add("ContactName", SortOrder.Descending);
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ await bulkcopy.WriteToServerAsync(reader);
+ }
+
+ const int nWriteToServerCalls = 5;
+ Helpers.VerifyResults(dstConn, dstTable, 3, nRowsInSource * nWriteToServerCalls);
+
+ // different tables
+ srcCmd.CommandText = sourceQuery2;
+ using (DbDataReader reader = await srcCmd.ExecuteReaderAsync())
+ {
+ bulkcopy.DestinationTableName = dstTable2;
+ bulkcopy.ColumnOrderHints.Clear();
+ bulkcopy.ColumnOrderHints.Add("LastName", SortOrder.Descending);
+ await bulkcopy.WriteToServerAsync(reader);
+ Helpers.VerifyResults(dstConn, dstTable2, 2, nRowsInSource2);
+ }
+ }
+ }
+ finally
+ {
+ DataTestUtility.DropTable(dstConn, dstTable);
+ DataTestUtility.DropTable(dstConn, dstTable2);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs
new file mode 100644
index 0000000000..59e82a8a97
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs
@@ -0,0 +1,70 @@
+// 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 System;
+using System.Data.Common;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class OrderHintDuplicateColumn
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}";
+ private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+ string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ dstConn.Open();
+ Helpers.TryExecute(dstCmd, initialQuery);
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(getRowCountQuery, srcConn))
+ {
+ srcConn.Open();
+ try
+ {
+ int nRowsInSource = Convert.ToInt32(srcCmd.ExecuteScalar());
+ srcCmd.CommandText = sourceQuery;
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn))
+ {
+ bulkcopy.DestinationTableName = dstTable;
+ const string destColumn = "CompanyName";
+ const string destColumn2 = "ContactName";
+ bulkcopy.ColumnOrderHints.Add(destColumn, SortOrder.Ascending);
+
+ string expectedErrorMsg = string.Format(
+ SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintDuplicateColumn, destColumn);
+ DataTestUtility.AssertThrowsWrapper(
+ () => bulkcopy.ColumnOrderHints.Add(destColumn, SortOrder.Ascending),
+ exceptionMessage: expectedErrorMsg);
+
+ bulkcopy.ColumnOrderHints.Add(destColumn2, SortOrder.Ascending);
+ Assert.Equal(2, bulkcopy.ColumnOrderHints.Count);
+
+ bulkcopy.WriteToServer(reader);
+ Helpers.VerifyResults(dstConn, dstTable, 3, nRowsInSource);
+ }
+ }
+ }
+ finally
+ {
+ DataTestUtility.DropTable(dstConn, dstTable);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs
new file mode 100644
index 0000000000..a95c13ad36
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs
@@ -0,0 +1,67 @@
+// 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 System;
+using System.Data.Common;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class OrderHintIdentityColumn
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID int IDENTITY NOT NULL, CompanyName nvarchar(50), ContactName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerId, CompanyName, ContactName FROM {0}";
+ private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ dstConn.Open();
+ Helpers.TryExecute(dstCmd, initialQuery);
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(getRowCountQuery, srcConn))
+ {
+ srcConn.Open();
+ try
+ {
+ int nRowsInSource = Convert.ToInt32(srcCmd.ExecuteScalar());
+ srcCmd.CommandText = sourceQuery;
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn))
+ {
+ bulkcopy.DestinationTableName = dstTable;
+
+ // no mapping
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkcopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ bulkcopy.WriteToServer(reader);
+ Helpers.VerifyResults(dstConn, dstTable, 3, nRowsInSource);
+ }
+
+ // with mapping
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ bulkcopy.ColumnMappings.Add(0, 1);
+ bulkcopy.WriteToServer(reader);
+ Helpers.VerifyResults(dstConn, dstTable, 3, nRowsInSource);
+ }
+ }
+ }
+ finally
+ {
+ DataTestUtility.DropTable(dstConn, dstTable);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs
new file mode 100644
index 0000000000..dcfeb6e311
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs
@@ -0,0 +1,75 @@
+// 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 System;
+using System.Data.Common;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class OrderHintMissingTargetColumn
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ dstConn.Open();
+ Helpers.TryExecute(dstCmd, initialQuery);
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn))
+ {
+ srcConn.Open();
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn))
+ {
+ try
+ {
+ bulkcopy.DestinationTableName = dstTable;
+ const string nonexistentColumn = "nonexistent column";
+ const string sourceColumn = "CustomerID";
+ const string destColumn = "ContactName";
+
+ // column does not exist in destination table
+ bulkcopy.ColumnOrderHints.Add(nonexistentColumn, SortOrder.Ascending);
+
+ string expectedErrorMsg = string.Format(
+ SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintInvalidColumn, nonexistentColumn);
+ DataTestUtility.AssertThrowsWrapper(
+ () => bulkcopy.WriteToServer(reader),
+ exceptionMessage: expectedErrorMsg);
+
+ // column does not exist in destination table because of user-defined mapping
+ bulkcopy.ColumnMappings.Add(sourceColumn, destColumn);
+ bulkcopy.ColumnOrderHints.RemoveAt(0);
+ bulkcopy.ColumnOrderHints.Add(sourceColumn, SortOrder.Ascending);
+ Assert.True(bulkcopy.ColumnOrderHints.Count == 1, "Error adding a column order hint");
+
+ expectedErrorMsg = string.Format(
+ SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintInvalidColumn, sourceColumn);
+ DataTestUtility.AssertThrowsWrapper(
+ () => bulkcopy.WriteToServer(reader),
+ exceptionMessage: expectedErrorMsg);
+ }
+ finally
+ {
+ DataTestUtility.DropTable(dstConn, dstTable);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs
new file mode 100644
index 0000000000..73dae137aa
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs
@@ -0,0 +1,58 @@
+// 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 System.Data.Common;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ class OrderHintTransaction
+ {
+ private static readonly string sourceTable = "Customers";
+ private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))";
+ private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}";
+
+ public static void Test(string srcConstr, string dstTable)
+ {
+ srcConstr = (new SqlConnectionStringBuilder(srcConstr) { InitialCatalog = "Northwind" }).ConnectionString;
+ string dstConstr = (new SqlConnectionStringBuilder(srcConstr)).ConnectionString;
+ string initialQuery = string.Format(initialQueryTemplate, dstTable);
+ string sourceQuery = string.Format(sourceQueryTemplate, sourceTable);
+
+ using (SqlConnection dstConn = new SqlConnection(dstConstr))
+ using (SqlCommand dstCmd = dstConn.CreateCommand())
+ {
+ dstConn.Open();
+ SqlTransaction txn = dstConn.BeginTransaction();
+ dstCmd.Transaction = txn;
+ Helpers.TryExecute(dstCmd, initialQuery);
+
+ using (SqlConnection srcConn = new SqlConnection(srcConstr))
+ using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn))
+ {
+ srcConn.Open();
+ using (DbDataReader reader = srcCmd.ExecuteReader())
+ {
+ using (SqlBulkCopy bulkcopy = new SqlBulkCopy(
+ dstConn, SqlBulkCopyOptions.CheckConstraints, txn))
+ {
+ try
+ {
+ bulkcopy.DestinationTableName = dstTable;
+ bulkcopy.ColumnMappings.Add(0, 2);
+ bulkcopy.ColumnMappings.Add(2, 0);
+ bulkcopy.ColumnOrderHints.Add("CustomerID", SortOrder.Ascending);
+ bulkcopy.ColumnOrderHints.Add("ContactName", SortOrder.Descending);
+ bulkcopy.WriteToServer(reader);
+ }
+ finally
+ {
+ txn.Rollback();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs
index fe9009af12..965dfde9f1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs
@@ -247,5 +247,42 @@ public void DestinationTableNameWithSpecialCharTest()
{
DestinationTableNameWithSpecialChar.Test(srcConstr, AddGuid("SqlBulkCopyTest_DestinationTableNameWithSpecialChar"));
}
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void OrderHintTest()
+ {
+ OrderHint.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHint"), AddGuid("SqlBulkCopyTest_OrderHint2"));
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void OrderHintAsyncTest()
+ {
+ OrderHintAsync.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHintAsync"), AddGuid("SqlBulkCopyTest_OrderHintAsync2"));
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void OrderHintMissingTargetColumnTest()
+ {
+ OrderHintMissingTargetColumn.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHintMissingTargetColumn"));
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void OrderHintDuplicateColumnTest()
+ {
+ OrderHintDuplicateColumn.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHintDuplicateColumn"));
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public void OrderHintTransactionTest()
+ {
+ OrderHintTransaction.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHintTransaction"));
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ [ActiveIssue(12219)]
+ public void OrderHintIdentityColumnTest()
+ {
+ OrderHintIdentityColumn.Test(srcConstr, AddGuid("SqlBulkCopyTest_OrderHintIdentityColumn"));
+ }
}
}
From e1b6cd494100171d088d278c9d21810b02a9aa90 Mon Sep 17 00:00:00 2001
From: Karina Zhou
Date: Tue, 9 Jun 2020 18:50:09 -0700
Subject: [PATCH 2/5] Add correct code page for Kazakh collation (#584)
---
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 3 +
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 3 +
.../SQL/DataReaderTest/DataReaderTest.cs | 55 +++++++++++++++++++
3 files changed, 61 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index eedcf86f8f..87d4999727 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -4268,6 +4268,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj)
{
}
break;
+ case 0x43f:
+ codePage = 1251; // Kazakh code page based on SQL Server
+ break;
default:
break;
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 692d3ef85c..930deee878 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -4730,6 +4730,9 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj)
ADP.TraceExceptionWithoutRethrow(e);
}
break;
+ case 0x43f:
+ codePage = 1251; // Kazakh code page based on SQL Server
+ break;
default:
break;
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index dca2c07362..b7787b5855 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Data;
using System.Text;
+using System.Threading;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -145,6 +146,60 @@ public static void CheckSparseColumnBit()
}
}
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))]
+ public static void CollatedDataReaderTest()
+ {
+ var databaseName = DataTestUtility.GetUniqueName("DB");
+ // Remove square brackets
+ var dbName = databaseName.Substring(1, databaseName.Length - 2);
+
+ SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
+ {
+ InitialCatalog = dbName,
+ Pooling = false
+ };
+
+ using (SqlConnection con = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlCommand cmd = con.CreateCommand())
+ {
+ try
+ {
+ con.Open();
+
+ // Create collated database
+ cmd.CommandText = $"CREATE DATABASE {databaseName} COLLATE KAZAKH_90_CI_AI";
+ cmd.ExecuteNonQuery();
+
+ //Create connection without pooling in order to delete database later.
+ using (SqlConnection dbCon = new SqlConnection(builder.ConnectionString))
+ using (SqlCommand dbCmd = dbCon.CreateCommand())
+ {
+ var data = "TestData";
+
+ dbCon.Open();
+ dbCmd.CommandText = $"SELECT '{data}'";
+ using (SqlDataReader reader = dbCmd.ExecuteReader())
+ {
+ reader.Read();
+ Assert.Equal(data, reader.GetString(0));
+ }
+ }
+
+ // Let connection close safely before dropping database for slow servers.
+ Thread.Sleep(500);
+ }
+ catch (SqlException e)
+ {
+ Assert.True(false, $"Unexpected Exception occurred: {e.Message}");
+ }
+ finally
+ {
+ cmd.CommandText = $"DROP DATABASE {databaseName}";
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
private static bool IsColumnBitSet(SqlConnection con, string selectQuery, int indexOfColumnSet)
{
bool columnSetPresent = false;
From 4258e4b44220c5f96997717a9b3441968108abd4 Mon Sep 17 00:00:00 2001
From: Karina Zhou
Date: Tue, 9 Jun 2020 18:50:25 -0700
Subject: [PATCH 3/5] Fix | Fix SqlClientMetaDataCollectionNames.Parameters
mismatching issue (#580)
* Fix Parameters undefined issue and add test case
* Update valid names and add tests
---
.../SqlClientMetaDataCollectionNames.xml | 18 +++--
.../netcore/ref/Microsoft.Data.SqlClient.cs | 10 ++-
.../SqlClientMetaDataCollectionNames.cs | 13 +--
.../netfx/ref/Microsoft.Data.SqlClient.cs | 10 ++-
.../SqlClientMetaDataCollectionNames.cs | 13 +--
.../SqlClientMetaDataCollectionNamesTest.cs | 5 +-
.../ConnectionSchemaTest.cs | 79 +++++++++++++++++--
7 files changed, 116 insertions(+), 32 deletions(-)
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml
index 8aa207b418..e013016ee9 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml
@@ -24,14 +24,10 @@
A constant for use with the **GetSchema** method that represents the **Indexes** collection.To be added.
-
- A constant for use with the **GetSchema** method that represents the **Parameters** collection.
+
+ A constant for use with the **GetSchema** method that represents the **ProcedureParameters** collection.To be added.
-
-
- A constant for use with the **GetSchema** method that represents the **ProcedureColumns** collection.
- To be added.
-
+
A constant for use with the **GetSchema** method that represents the **Procedures** collection.To be added.
@@ -56,5 +52,13 @@
A constant for use with the **GetSchema** method that represents the **Views** collection.To be added.
+
+ A constant for use with the **GetSchema** method that represents the **AllColumns** collection.
+ To be added.
+
+
+ A constant for use with the **GetSchema** method that represents the **ColumnSetColumns** collection.
+ To be added.
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 52e6e35dd3..461bee2eaf 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -328,10 +328,8 @@ public static partial class SqlClientMetaDataCollectionNames
public static readonly string IndexColumns;
///
public static readonly string Indexes;
- ///
- public static readonly string Parameters;
- ///
- public static readonly string ProcedureColumns;
+ ///
+ public static readonly string ProcedureParameters;
///
public static readonly string Procedures;
///
@@ -344,6 +342,10 @@ public static partial class SqlClientMetaDataCollectionNames
public static readonly string ViewColumns;
///
public static readonly string Views;
+ ///
+ public static readonly string AllColumns;
+ ///
+ public static readonly string ColumnSetColumns;
}
///
[System.ComponentModel.DefaultEventAttribute("RecordsAffected")]
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
index 7caf4421fa..1c805b53df 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
@@ -23,11 +23,8 @@ public static class SqlClientMetaDataCollectionNames
///
public static readonly string Indexes = "Indexes";
- ///
- public static readonly string Parameters = "Parameters";
-
- ///
- public static readonly string ProcedureColumns = "ProcedureColumns";
+ ///
+ public static readonly string ProcedureParameters = "ProcedureParameters";
///
public static readonly string Procedures = "Procedures";
@@ -46,5 +43,11 @@ public static class SqlClientMetaDataCollectionNames
///
public static readonly string Views = "Views";
+
+ ///
+ public static readonly string AllColumns = "AllColumns"; // supported starting from SQL Server 2008
+
+ ///
+ public static readonly string ColumnSetColumns = "ColumnSetColumns"; // supported starting from SQL Server 2008
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 345ebcea9e..197e886da2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -365,10 +365,8 @@ public static partial class SqlClientMetaDataCollectionNames
public static readonly string IndexColumns;
///
public static readonly string Indexes;
- ///
- public static readonly string Parameters;
- ///
- public static readonly string ProcedureColumns;
+ ///
+ public static readonly string ProcedureParameters;
///
public static readonly string Procedures;
///
@@ -381,6 +379,10 @@ public static partial class SqlClientMetaDataCollectionNames
public static readonly string ViewColumns;
///
public static readonly string Views;
+ ///
+ public static readonly string AllColumns;
+ ///
+ public static readonly string ColumnSetColumns;
}
///
public sealed partial class SqlClientPermission : System.Data.Common.DBDataPermission
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
index eb2db23548..5400ac2fb0 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientMetaDataCollectionNames.cs
@@ -22,11 +22,8 @@ public static class SqlClientMetaDataCollectionNames
///
public static readonly string Indexes = "Indexes";
- ///
- public static readonly string Parameters = "Parameters";
-
- ///
- public static readonly string ProcedureColumns = "ProcedureColumns";
+ ///
+ public static readonly string ProcedureParameters = "ProcedureParameters";
///
public static readonly string Procedures = "Procedures";
@@ -46,5 +43,11 @@ public static class SqlClientMetaDataCollectionNames
///
public static readonly string Views = "Views";
+ ///
+ public static readonly string AllColumns = "AllColumns"; // supported starting from SQL Server 2008
+
+ ///
+ public static readonly string ColumnSetColumns = "ColumnSetColumns"; // supported starting from SQL Server 2008
+
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs
index 69ed6d6304..789949ee68 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientMetaDataCollectionNamesTest.cs
@@ -16,14 +16,15 @@ public void ValuesTest()
Assert.Equal("ForeignKeys", SqlClientMetaDataCollectionNames.ForeignKeys);
Assert.Equal("IndexColumns", SqlClientMetaDataCollectionNames.IndexColumns);
Assert.Equal("Indexes", SqlClientMetaDataCollectionNames.Indexes);
- Assert.Equal("Parameters", SqlClientMetaDataCollectionNames.Parameters);
- Assert.Equal("ProcedureColumns", SqlClientMetaDataCollectionNames.ProcedureColumns);
+ Assert.Equal("ProcedureParameters", SqlClientMetaDataCollectionNames.ProcedureParameters);
Assert.Equal("Procedures", SqlClientMetaDataCollectionNames.Procedures);
Assert.Equal("Tables", SqlClientMetaDataCollectionNames.Tables);
Assert.Equal("UserDefinedTypes", SqlClientMetaDataCollectionNames.UserDefinedTypes);
Assert.Equal("Users", SqlClientMetaDataCollectionNames.Users);
Assert.Equal("ViewColumns", SqlClientMetaDataCollectionNames.ViewColumns);
Assert.Equal("Views", SqlClientMetaDataCollectionNames.Views);
+ Assert.Equal("AllColumns", SqlClientMetaDataCollectionNames.AllColumns);
+ Assert.Equal("ColumnSetColumns", SqlClientMetaDataCollectionNames.ColumnSetColumns);
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
index 6e7e1623a2..7b2742e8d6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
@@ -12,17 +12,89 @@ public static class ConnectionSchemaTest
{
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void GetAllTablesFromSchema()
+ public static void GetTablesFromSchema()
{
VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" });
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void GetAllProceduresFromSchema()
+ public static void GetProceduresFromSchema()
{
VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" });
}
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetProcedureParametersFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetDatabasesFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetForeignKeysFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetIndexesFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetIndexColumnsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetColumnsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetAllColumnsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetColumnSetColumnsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetUsersFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetViewsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetViewColumnsFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" });
+ }
+
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void GetUserDefinedTypesFromSchema()
+ {
+ VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" });
+ }
+
private static void VerifySchemaTable(string schemaItemName, string[] testColumnNames)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
@@ -36,9 +108,6 @@ private static void VerifySchemaTable(string schemaItemName, string[] testColumn
connection.Open();
DataTable table = connection.GetSchema(schemaItemName);
- // Display the contents of the table.
- Assert.InRange(table.Rows.Count, 1, int.MaxValue);
-
// Get all table columns
HashSet columnNames = new HashSet();
From e601e20ca155b47c971854351bf594756ab95d57 Mon Sep 17 00:00:00 2001
From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com>
Date: Tue, 9 Jun 2020 18:52:35 -0700
Subject: [PATCH 4/5] Raise warning message for insecure protocols (#591)
---
.../SqlClientLogger.xml | 6 ++
.../Interop/SNINativeMethodWrapper.Windows.cs | 2 +-
.../Microsoft/Data/SqlClient/SNI/SNIHandle.cs | 4 +
.../Data/SqlClient/SNI/SNIMarsConnection.cs | 2 +
.../Data/SqlClient/SNI/SNIMarsHandle.cs | 2 +
.../Data/SqlClient/SNI/SNINpHandle.cs | 15 ++++
.../Data/SqlClient/SNI/SNITcpHandle.cs | 15 ++++
.../Data/SqlClient/TdsParser.Unix.cs | 3 +-
.../Data/SqlClient/TdsParser.Windows.cs | 6 +-
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 17 +++-
.../Data/SqlClient/TdsParserHelperClasses.cs | 61 ++++++++++++++
.../Data/SqlClient/TdsParserStateObject.cs | 2 +-
.../SqlClient/TdsParserStateObjectManaged.cs | 6 +-
.../SqlClient/TdsParserStateObjectNative.cs | 65 ++++++++++++++-
.../netcore/src/Resources/SR.Designer.cs | 9 ++
.../netcore/src/Resources/SR.resx | 3 +
.../Interop/SNINativeManagedWrapperX64.cs | 2 +-
.../Interop/SNINativeManagedWrapperX86.cs | 2 +-
.../Data/Interop/SNINativeMethodWrapper.cs | 6 +-
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 15 +++-
.../Data/SqlClient/TdsParserHelperClasses.cs | 83 +++++++++++++++++++
.../netfx/src/Resources/Strings.Designer.cs | 18 ++++
.../netfx/src/Resources/Strings.resx | 6 ++
.../Data/SqlClient/SqlClientLogger.cs | 15 +++-
24 files changed, 344 insertions(+), 21 deletions(-)
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
index 229f5eee58..c093bbd505 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientLogger.xml
@@ -37,5 +37,11 @@
Logs information through a specified method of the current instance type.To be added.
+
+ The type to be logged.
+ The logging method.
+ The message to be logged.
+ Logs warning through a specified method of the current instance type.
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
index 1296e4afa3..a4eed7751b 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
@@ -225,7 +225,7 @@ internal struct SNI_Error
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
index 6c5bda96b2..bd3facd5fc 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs
@@ -91,6 +91,10 @@ internal abstract class SNIHandle
public abstract void ReturnPacket(SNIPacket packet);
+ ///
+ /// Gets a value that indicates the security protocol used to authenticate this connection.
+ ///
+ public virtual int ProtocolVersion { get; } = 0;
#if DEBUG
///
/// Test handle for killing underlying connection
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
index f3ed0c1be0..5877508942 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs
@@ -35,6 +35,8 @@ public Guid ConnectionId
}
}
+ public int ProtocolVersion => _lowerHandle.ProtocolVersion;
+
///
/// Constructor
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
index 54572634cd..8c35907a8f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs
@@ -47,6 +47,8 @@ internal sealed class SNIMarsHandle : SNIHandle
public override int ReserveHeaderSize => SNISMUXHeader.HEADER_LENGTH;
+ public override int ProtocolVersion => _connection.ProtocolVersion;
+
///
/// Dispose object
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
index ffda6c6248..362d4397d2 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs
@@ -120,6 +120,21 @@ public override uint Status
}
}
+ public override int ProtocolVersion
+ {
+ get
+ {
+ try
+ {
+ return (int)_sslStream.SslProtocol;
+ }
+ catch
+ {
+ return base.ProtocolVersion;
+ }
+ }
+ }
+
public override uint CheckConnection()
{
long scopeID = SqlClientEventSource.Log.SNIScopeEnterEvent("");
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
index 158c13949a..e83e63882a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
@@ -93,6 +93,21 @@ public override uint Status
}
}
+ public override int ProtocolVersion
+ {
+ get
+ {
+ try
+ {
+ return (int)_sslStream.SslProtocol;
+ }
+ catch
+ {
+ return base.ProtocolVersion;
+ }
+ }
+ }
+
///
/// Constructor
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
index 73eb6fd93f..1c875f5813 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs
@@ -18,10 +18,9 @@ private void LoadSSPILibrary()
// No - Op
}
- private void WaitForSSLHandShakeToComplete(ref uint error)
+ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersion)
{
// No - Op
-
}
private SNIErrorDetails GetSniErrorDetails()
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
index 4e60f4e7f6..d5ddb5d47d 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs
@@ -75,15 +75,13 @@ private void LoadSSPILibrary()
}
}
- private void WaitForSSLHandShakeToComplete(ref uint error)
+ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersion)
{
- if (TdsParserStateObjectFactory.UseManagedSNI)
- return;
// in the case where an async connection is made, encryption is used and Windows Authentication is used,
// wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
- error = _physicalStateObj.WaitForSSLHandShakeToComplete();
+ error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocolVersion);
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index 87d4999727..507b8cc981 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -10,6 +10,8 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Reflection;
+using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -19,6 +21,7 @@
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlTypes;
+using Newtonsoft.Json.Serialization;
namespace Microsoft.Data.SqlClient
{
@@ -39,6 +42,9 @@ internal struct SNIErrorDetails
internal sealed partial class TdsParser
{
private static int _objectTypeCount; // EventSource counter
+ private readonly SqlClientLogger _logger = new SqlClientLogger();
+ private readonly string _typeName;
+
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
internal int ObjectID => _objectID;
@@ -174,6 +180,7 @@ internal TdsParser(bool MARS, bool fAsynchronous)
_physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this);
DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
+ _typeName = GetType().Name;
}
internal SqlInternalConnectionTds Connection
@@ -906,7 +913,15 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus
ThrowExceptionAndWarning(_physicalStateObj);
}
- WaitForSSLHandShakeToComplete(ref error);
+ int protocolVersion = 0;
+ WaitForSSLHandShakeToComplete(ref error, ref protocolVersion);
+
+ SslProtocols protocol = (SslProtocols)protocolVersion;
+ string warningMessage = protocol.GetProtocolWarning();
+ if(!string.IsNullOrEmpty(warningMessage))
+ {
+ _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage);
+ }
// create a new packet encryption changes the internal packet size
_physicalStateObj.ClearAllWritePackets();
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index 1e24817b85..66c0966e7d 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -12,6 +12,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Security;
+using System.Security.Authentication;
using System.Text;
using Microsoft.Data.Common;
using Microsoft.Data.SqlTypes;
@@ -917,4 +918,64 @@ private void ParseMultipartName()
internal static readonly MultiPartTableName Null = new MultiPartTableName(new string[] { null, null, null, null });
}
+
+ internal static class SslProtocolsHelper
+ {
+ private static string ToFriendlyName(this SslProtocols protocol)
+ {
+ string name;
+
+ /* The SslProtocols.Tls13 is supported by netcoreapp3.1 and later
+ * This driver does not support this version yet!
+ if ((protocol & SslProtocols.Tls13) == SslProtocols.Tls13)
+ {
+ name = "TLS 1.3";
+ }*/
+ if((protocol & SslProtocols.Tls12) == SslProtocols.Tls12)
+ {
+ name = "TLS 1.2";
+ }
+ else if ((protocol & SslProtocols.Tls11) == SslProtocols.Tls11)
+ {
+ name = "TLS 1.1";
+ }
+ else if ((protocol & SslProtocols.Tls) == SslProtocols.Tls)
+ {
+ name = "TLS 1.0";
+ }
+#pragma warning disable CS0618 // Type or member is obsolete: SSL is depricated
+ else if ((protocol & SslProtocols.Ssl3) == SslProtocols.Ssl3)
+ {
+ name = "SSL 3.0";
+ }
+ else if ((protocol & SslProtocols.Ssl2) == SslProtocols.Ssl2)
+#pragma warning restore CS0618 // Type or member is obsolete: SSL is depricated
+ {
+ name = "SSL 2.0";
+ }
+ else
+ {
+ name = protocol.ToString();
+ }
+
+ return name;
+ }
+
+ ///
+ /// check the negotiated secure protocol if it's under TLS 1.2
+ ///
+ ///
+ /// Localized warning message
+ public static string GetProtocolWarning(this SslProtocols protocol)
+ {
+ string message = string.Empty;
+#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated
+ if ((protocol & (SslProtocols.Ssl2 | SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11)) != SslProtocols.None)
+#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated
+ {
+ message = SRHelper.Format(SR.SEC_ProtocolWarning, protocol.ToFriendlyName());
+ }
+ return message;
+ }
+ }
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index 9c197b9c37..6888ad0453 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -774,7 +774,7 @@ private void ResetCancelAndProcessAttention()
internal abstract uint EnableSsl(ref uint info);
- internal abstract uint WaitForSSLHandShakeToComplete();
+ internal abstract uint WaitForSSLHandShakeToComplete(out int protocolVersion);
internal abstract void Dispose();
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
index 46ae4c4dfb..6e25589986 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
@@ -220,6 +220,10 @@ internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint recei
return 0;
}
- internal override uint WaitForSSLHandShakeToComplete() => 0;
+ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion)
+ {
+ protocolVersion = Handle.ProtocolVersion;
+ return 0;
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
index 97825278ec..a38b5524df 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Security.Authentication;
using System.Threading.Tasks;
using Microsoft.Data.Common;
@@ -13,6 +14,25 @@ namespace Microsoft.Data.SqlClient
{
internal class TdsParserStateObjectNative : TdsParserStateObject
{
+ // protocol versions from native sni
+ [Flags]
+ private enum NativeProtocols
+ {
+ SP_PROT_SSL2_SERVER = 0x00000004,
+ SP_PROT_SSL2_CLIENT = 0x00000008,
+ SP_PROT_SSL3_SERVER = 0x00000010,
+ SP_PROT_SSL3_CLIENT = 0x00000020,
+ SP_PROT_TLS1_0_SERVER = 0x00000040,
+ SP_PROT_TLS1_0_CLIENT = 0x00000080,
+ SP_PROT_TLS1_1_SERVER = 0x00000100,
+ SP_PROT_TLS1_1_CLIENT = 0x00000200,
+ SP_PROT_TLS1_2_SERVER = 0x00000400,
+ SP_PROT_TLS1_2_CLIENT = 0x00000800,
+ SP_PROT_TLS1_3_SERVER = 0x00001000,
+ SP_PROT_TLS1_3_CLIENT = 0x00002000,
+ SP_PROT_NONE = 0x0
+ }
+
private SNIHandle _sessionHandle = null; // the SNI handle we're to work on
private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS
@@ -309,8 +329,49 @@ internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize)
internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[] _sniSpnBuffer)
=> SNINativeMethodWrapper.SNISecGenClientContext(Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer);
- internal override uint WaitForSSLHandShakeToComplete()
- => SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining());
+ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion)
+ {
+ uint returnValue = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining(), out uint nativeProtocolVersion);
+ var nativeProtocol = (NativeProtocols)nativeProtocolVersion;
+
+ /* The SslProtocols.Tls13 is supported by netcoreapp3.1 and later
+ * This driver does not support this version yet!
+ if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls13;
+ }*/
+ if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls12;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls11;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Tls;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER))
+ {
+#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated
+ protocolVersion = (int)SslProtocols.Ssl3;
+ }
+ else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_SERVER))
+ {
+ protocolVersion = (int)SslProtocols.Ssl2;
+#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated
+ }
+ else if(nativeProtocol.HasFlag(NativeProtocols.SP_PROT_NONE))
+ {
+ protocolVersion = (int)SslProtocols.None;
+ }
+ else
+ {
+ throw new ArgumentException(SRHelper.Format(SRHelper.net_invalid_enum, nameof(NativeProtocols)), nameof(NativeProtocols));
+ }
+ return returnValue;
+ }
internal override void DisposePacketCache()
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
index fd5175504c..23cbaa4efe 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
@@ -1428,6 +1428,15 @@ internal class SR {
}
}
+ ///
+ /// Looks up a localized string similar to Security Warning: The negotiated '{0}' is an insecured protocol and is supported for backward compatibility only. The recommended protocol is TLS 1.2 and later..
+ ///
+ internal static string SEC_ProtocolWarning {
+ get {
+ return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to I/O Error detected in read/write operation.
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
index bab71b9600..47d203f9ff 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
@@ -1860,6 +1860,9 @@
The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
A column order hint cannot have an unspecified sort order.
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
index dd585ededa..edfb5e960f 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
@@ -61,7 +61,7 @@ internal static class SNINativeManagedWrapperX64
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
index 339760c1a2..89c9af997b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
@@ -61,7 +61,7 @@ internal static class SNINativeManagedWrapperX86
internal static extern uint SNITerminate();
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")]
- internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds);
+ internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
index 499b29b4d3..fefdeea4b7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
@@ -505,11 +505,11 @@ internal static uint SNITerminate()
SNINativeManagedWrapperX86.SNITerminate();
}
- internal static uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds)
+ internal static uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion)
{
return s_is64bitProcess ?
- SNINativeManagedWrapperX64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds) :
- SNINativeManagedWrapperX86.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds);
+ SNINativeManagedWrapperX64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion) :
+ SNINativeManagedWrapperX86.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion);
}
internal static uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 930deee878..859c8bd3bb 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -30,6 +31,9 @@ namespace Microsoft.Data.SqlClient
sealed internal class TdsParser
{
private static int _objectTypeCount; // EventSource Counter
+ private readonly SqlClientLogger _logger = new SqlClientLogger();
+ private readonly string _typeName;
+
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
static Task completedTask;
@@ -300,6 +304,7 @@ internal TdsParser(bool MARS, bool fAsynchronous)
_fMARS = MARS; // may change during Connect to pre Yukon servers
_physicalStateObj = new TdsParserStateObject(this);
DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
+ _typeName = GetType().Name;
}
internal SqlInternalConnectionTds Connection
@@ -1204,15 +1209,21 @@ private void SendPreLoginHandshake(byte[] instanceName, bool encrypt, bool clien
// wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
- error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining());
+ error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion);
+
if (error != TdsEnums.SNI_SUCCESS)
{
_physicalStateObj.AddError(ProcessSNIError(_physicalStateObj));
ThrowExceptionAndWarning(_physicalStateObj);
}
+ string warningMessage = SslProtocolsHelper.GetProtocolWarning(protocolVersion);
+ if (!string.IsNullOrEmpty(warningMessage))
+ {
+ _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage);
+ }
+
// Validate server certificate
- //
if (serverCallback != null)
{
X509Certificate2 serverCert = null;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
index 96eb11c0d2..873aeff914 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs
@@ -1421,4 +1421,87 @@ private void ParseMultipartName()
internal static readonly MultiPartTableName Null = new MultiPartTableName(new string[] { null, null, null, null });
}
+
+ internal static class SslProtocolsHelper
+ {
+ // protocol versions from native sni
+ [Flags]
+ private enum NativeProtocols
+ {
+ SP_PROT_SSL2_SERVER = 0x00000004,
+ SP_PROT_SSL2_CLIENT = 0x00000008,
+ SP_PROT_SSL3_SERVER = 0x00000010,
+ SP_PROT_SSL3_CLIENT = 0x00000020,
+ SP_PROT_TLS1_0_SERVER = 0x00000040,
+ SP_PROT_TLS1_0_CLIENT = 0x00000080,
+ SP_PROT_TLS1_1_SERVER = 0x00000100,
+ SP_PROT_TLS1_1_CLIENT = 0x00000200,
+ SP_PROT_TLS1_2_SERVER = 0x00000400,
+ SP_PROT_TLS1_2_CLIENT = 0x00000800,
+ SP_PROT_TLS1_3_SERVER = 0x00001000,
+ SP_PROT_TLS1_3_CLIENT = 0x00002000,
+ SP_PROT_SSL2 = SP_PROT_SSL2_SERVER | SP_PROT_SSL2_CLIENT,
+ SP_PROT_SSL3 = SP_PROT_SSL3_SERVER | SP_PROT_SSL3_CLIENT,
+ SP_PROT_TLS1_0 = SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_0_CLIENT,
+ SP_PROT_TLS1_1 = SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_1_CLIENT,
+ SP_PROT_TLS1_2 = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_2_CLIENT,
+ SP_PROT_TLS1_3 = SP_PROT_TLS1_3_SERVER | SP_PROT_TLS1_3_CLIENT,
+ SP_PROT_NONE = 0x0
+ }
+
+ private static string ToFriendlyName(this NativeProtocols protocol)
+ {
+ string name;
+
+ if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_3_SERVER))
+ {
+ name = "TLS 1.3";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_2_SERVER))
+ {
+ name = "TLS 1.2";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_1_SERVER))
+ {
+ name = "TLS 1.1";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_TLS1_0_SERVER))
+ {
+ name = "TLS 1.0";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER))
+ {
+ name = "SSL 3.0";
+ }
+ else if (protocol.HasFlag(NativeProtocols.SP_PROT_SSL2_CLIENT) || protocol.HasFlag(NativeProtocols.SP_PROT_SSL2_SERVER))
+ {
+ name = "SSL 2.0";
+ }
+ else if(protocol.HasFlag(NativeProtocols.SP_PROT_NONE))
+ {
+ name = "None";
+ }
+ else
+ {
+ throw new ArgumentException(StringsHelper.GetString(StringsHelper.net_invalid_enum, nameof(NativeProtocols)), nameof(NativeProtocols));
+ }
+ return name;
+ }
+
+ ///
+ /// check the negotiated secure protocol if it's under TLS 1.2
+ ///
+ ///
+ /// Localized warning message
+ public static string GetProtocolWarning(uint protocol)
+ {
+ var nativeProtocol = (NativeProtocols)protocol;
+ string message = string.Empty;
+ if ((nativeProtocol & (NativeProtocols.SP_PROT_SSL2 | NativeProtocols.SP_PROT_SSL3 | NativeProtocols.SP_PROT_TLS1_1)) != NativeProtocols.SP_PROT_NONE)
+ {
+ message = StringsHelper.GetString(Strings.SEC_ProtocolWarning, nativeProtocol.ToFriendlyName());
+ }
+ return message;
+ }
+ }
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index 9f40b57bdd..a4ca42aeb4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -7062,6 +7062,15 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to The specified value is not valid in the '{0}' enumeration..
+ ///
+ internal static string net_invalid_enum {
+ get {
+ return ResourceManager.GetString("net_invalid_enum", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to DateType column for field '{0}' in schema table is null. DataType must be non-null..
///
@@ -8082,6 +8091,15 @@ internal class Strings {
}
}
+ ///
+ /// Looks up a localized string similar to Security Warning: The negotiated '{0}' is an insecured protocol and is supported for backward compatibility only. The recommended protocol is TLS 1.2 and later..
+ ///
+ internal static string SEC_ProtocolWarning {
+ get {
+ return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to I/O Error detected in read/write operation.
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 0bca401c21..3cc34302a4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4530,6 +4530,12 @@
UDT size must be less than {1}, size: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
The given column order hint is not valid.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
index 5ff2f42e75..68afb07e49 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+
namespace Microsoft.Data.SqlClient
{
///
@@ -10,7 +12,8 @@ public class SqlClientLogger
internal enum LogLevel
{
Info = 0,
- Error,
+ Warning,
+ Error
}
///
@@ -19,10 +22,18 @@ public void LogInfo(string type, string method, string message)
SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Info, message);
}
+ ///
+ public void LogWarning(string type, string method, string message)
+ {
+ Console.Out.WriteLine(message);
+ SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Warning, message);
+ }
+
///
public void LogError(string type, string method, string message)
{
- SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Info, message);
+ Console.Out.WriteLine(message);
+ SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Error, message);
}
///
From 0d1997050ce9aa2fc80ead6bf250f9db9d69cb42 Mon Sep 17 00:00:00 2001
From: SqlClient DevOps
Date: Wed, 10 Jun 2020 01:55:54 +0000
Subject: [PATCH 5/5] [Scheduled Run] Localized resource files from OneLocBuild
---
.../netfx/src/Resources/Strings.de.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.es.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.fr.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.it.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.ja.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.ko.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.pt-BR.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.ru.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.zh-Hans.resx | 30 +++++++++++++++++++
.../netfx/src/Resources/Strings.zh-Hant.resx | 30 +++++++++++++++++++
10 files changed, 300 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx
index 58a66f1782..a25204efdf 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx
@@ -4530,4 +4530,34 @@
Die UDT-Größe muss kleiner als {1} sein. Größe: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx
index 4ad3c8946e..06cc7767db 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx
@@ -4530,4 +4530,34 @@
El tamaño de UDT debe ser menor que {1}; tamaño: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx
index a8ccbee9b1..bd22c8feeb 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx
@@ -4530,4 +4530,34 @@
La taille UDT doit être inférieure à {1}, taille : {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx
index f0b497b0d7..12c60c0209 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx
@@ -4530,4 +4530,34 @@
Le dimensioni del tipo definito dall'utente (UDT) devono essere inferiori a {1}, dimensioni: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx
index daaed69a9f..544bcdb26c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx
@@ -4530,4 +4530,34 @@
UDT のサイズは {1} より小さくなければなりません。サイズ: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx
index b943f50402..5da87b350d 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx
@@ -4530,4 +4530,34 @@
UDT 크기는 {1}보다 작아야 합니다. 크기: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx
index 328c0665ff..beeb608a90 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx
@@ -4530,4 +4530,34 @@
O tamanho do UDT precisa ser menor que {1}. Tamanho: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx
index d76623d14d..335adb907c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx
@@ -4530,4 +4530,34 @@
Размер пользовательского типа должен быть меньше {1}, текущий размер: {0}.
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx
index a28628a017..7fe1f2eaf0 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx
@@ -4530,4 +4530,34 @@
UDT 大小必须小于 {1},大小为 {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx
index 0cc42a06b7..67e644c8a4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx
@@ -4530,4 +4530,34 @@
UDT 大小必須小於 {1},大小: {0}
+
+ Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.
+
+
+ The specified value is not valid in the '{0}' enumeration.
+
+
+ The given column order hint is not valid.
+
+
+ The column '{0}' was specified more than once.
+
+
+ The sorted column '{0}' is not valid in the destination table.
+
+
+ A column order hint cannot have an unspecified sort order.
+
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
+ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.
+
+
+ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.
+
\ No newline at end of file