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 d368a25d50..55af304a79 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 @@ -547,6 +547,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i HashSet destColumnNames = new HashSet(); + Dictionary columnMappingStatusLookup = new Dictionary(); // Loop over the metadata for each column _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); @@ -569,9 +570,16 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i int assocId; for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) { + if (!columnMappingStatusLookup.ContainsKey(_localColumnMappings[assocId].DestinationColumn)) + { + columnMappingStatusLookup.Add(_localColumnMappings[assocId].DestinationColumn, false); + } + if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) { + columnMappingStatusLookup[_localColumnMappings[assocId].DestinationColumn] = true; + if (rejectColumn) { nrejected++; // Count matched columns only @@ -703,6 +711,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i break; } } + if (assocId == _localColumnMappings.Count) { // Remove metadata for unmatched columns @@ -713,7 +722,17 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // All columnmappings should have matched up if (nmatched + nrejected != _localColumnMappings.Count) { - throw (SQL.BulkLoadNonMatchingColumnMapping()); + List unmatchedColumns = new List(); + + foreach(KeyValuePair keyValuePair in columnMappingStatusLookup) + { + if (!keyValuePair.Value) + { + unmatchedColumns.Add(keyValuePair.Key); + } + } + + throw SQL.BulkLoadNonMatchingColumnName(unmatchedColumns); } updateBulkCommandText.Append(")"); 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 040ef93723..9e41460cdf 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 @@ -582,6 +582,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i HashSet destColumnNames = new HashSet(); + Dictionary columnMappingStatusLookup = new Dictionary(); // Loop over the metadata for each column _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); @@ -604,9 +605,16 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i int assocId; for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) { + if (!columnMappingStatusLookup.ContainsKey(_localColumnMappings[assocId].DestinationColumn)) + { + columnMappingStatusLookup.Add(_localColumnMappings[assocId].DestinationColumn, false); + } + if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) { + columnMappingStatusLookup[_localColumnMappings[assocId].DestinationColumn] = true; + if (rejectColumn) { nrejected++; // Count matched columns only @@ -754,7 +762,17 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // All columnmappings should have matched up if (nmatched + nrejected != _localColumnMappings.Count) { - throw (SQL.BulkLoadNonMatchingColumnMapping()); + List unmatchedColumns = new List(); + + foreach(KeyValuePair keyValuePair in columnMappingStatusLookup) + { + if (!keyValuePair.Value) + { + unmatchedColumns.Add(keyValuePair.Key); + } + } + + throw SQL.BulkLoadNonMatchingColumnName(unmatchedColumns); } updateBulkCommandText.Append(")"); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index 3a01ca9b5a..64d8c6fdd2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1257,6 +1257,10 @@ internal static Exception BulkLoadNonMatchingColumnName(string columnName) { return BulkLoadNonMatchingColumnName(columnName, null); } + internal static Exception BulkLoadNonMatchingColumnName(IEnumerable columns) + { + return BulkLoadNonMatchingColumnName(String.Join(",", columns), null); + } internal static Exception BulkLoadNonMatchingColumnName(string columnName, Exception e) { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadNonMatchingColumnName, columnName), e); 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 112814212e..d5339b6257 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 @@ -145,6 +145,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs index 481a99a83c..d8fe8f66da 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs @@ -35,7 +35,9 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) ColumnMappings.Add("LastName", "col2"); // this column does not exist ColumnMappings.Add("FirstName", "col3"); - string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnMapping; + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col2"); + DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs new file mode 100644 index 0000000000..2696770f69 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs @@ -0,0 +1,52 @@ +// 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 MissingTargetColumns + { + public static void Test(string srcConstr, string dstConstr, string dstTable) + { + using (SqlConnection dstConn = new SqlConnection(dstConstr)) + using (SqlCommand dstCmd = dstConn.CreateCommand()) + { + dstConn.Open(); + + try + { + Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + { + srcConn.Open(); + + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("LastName", "col3"); // this column does not exist + ColumnMappings.Add("FirstName", "col4"); // this column does not exist + + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col3,col4"); + + DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); + } + } + } + finally + { + Helpers.TryExecute(dstCmd, "drop table " + dstTable); + } + } + } + } +} 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 f7cb3e7490..4672adb242 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs @@ -104,6 +104,12 @@ public void MissingTargetColumnTest() MissingTargetColumn.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_MissingTargetColumn")); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void MissingTargetColumnsTest() + { + MissingTargetColumns.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_MissingTargetColumns")); + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Bug85007Test() {