From 53a9e9fc48909aa690f8bde94cb3842ce355ffb9 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] 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 ebbdc7ddb0..856ce9d204 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 SqlBulkCopy(string connectionString, Microsoft.Data.SqlClient.SqlBulkCopy
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 void Remove(Microsoft.Data.SqlClient.SqlBulkCopyColumnMapping value) { }
///
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 900f99b0fb..4328cfeebc 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 static string SQL_BulkLoadInvalidOperationInsideEvent {
}
}
+ ///
+ /// 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 static string SQL_BulkLoadNotAllowDBNull {
}
}
+ ///
+ /// 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 static string SQL_BulkLoadStringTooLong {
}
}
+ ///
+ /// 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 c8f4338941..205b696b80 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 SqlBulkCopy(string connectionString, Microsoft.Data.SqlClient.SqlBulkCopy
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 void Remove(Microsoft.Data.SqlClient.SqlBulkCopyColumnMapping value) { }
///
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 b3b7abecfe..f90f36784d 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 static string SQL_BulkLoadInvalidOperationInsideEvent {
}
}
+ ///
+ /// 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 static string SQL_BulkLoadNotAllowDBNull {
}
}
+ ///
+ /// 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 static string SQL_BulkLoadStringTooLong {
}
}
+ ///
+ /// 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 9e03a7792a..34bd1e6ead 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"));
+ }
}
}