-
Notifications
You must be signed in to change notification settings - Fork 317
[6.1] Fixing NullReferenceException issue with SqlDataAdapter #3846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/6.1
Are you sure you want to change the base?
Changes from all commits
fbdcc90
d98ee90
aed9e25
328fa35
7160abe
b78f299
c9677a8
c2dde41
8ee7c01
560d2c6
48fa6a8
03398c7
c9cfd01
f0f4d43
3824beb
bbcca93
17bf6a5
d625cd2
ba78044
84f704f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,247 @@ | ||||||||||||||||||||
| // 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.Threading.Tasks; | ||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||
| using Microsoft.Data.SqlClient; | ||||||||||||||||||||
| using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; | ||||||||||||||||||||
| using Xunit; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted | ||||||||||||||||||||
| { | ||||||||||||||||||||
| public sealed class SqlDataAdapterBatchUpdateTests : IClassFixture<SQLSetupStrategyCertStoreProvider>, IDisposable | ||||||||||||||||||||
| { | ||||||||||||||||||||
| private readonly SQLSetupStrategy _fixture; | ||||||||||||||||||||
| private readonly string _tableName; | ||||||||||||||||||||
| private readonly BuyerSellerTable _buyerSellerTable; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public SqlDataAdapterBatchUpdateTests(SQLSetupStrategyCertStoreProvider context) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| _fixture = context; | ||||||||||||||||||||
| _buyerSellerTable = _fixture.BuyerSellerTable as BuyerSellerTable; | ||||||||||||||||||||
| _tableName = _fixture.BuyerSellerTable.Name; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // ---------- TESTS ---------- | ||||||||||||||||||||
|
|
||||||||||||||||||||
| [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] | ||||||||||||||||||||
| [ClassData(typeof(AEConnectionStringProvider))] | ||||||||||||||||||||
| public async Task AdapterUpdate_BatchSizeGreaterThanOne_Succeeds(string connectionString) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| // Arrange | ||||||||||||||||||||
| TruncateTable(connectionString); | ||||||||||||||||||||
| int idBase = GetUniqueIdBase(); | ||||||||||||||||||||
| PopulateTable(new (int id, string s1, string s2)[] { | ||||||||||||||||||||
| (idBase + 10, "123-45-6789", "987-65-4321"), | ||||||||||||||||||||
| (idBase + 20, "234-56-7890", "876-54-3210"), | ||||||||||||||||||||
| (idBase + 30, "345-67-8901", "765-43-2109"), | ||||||||||||||||||||
| (idBase + 40, "456-78-9012", "654-32-1098"), | ||||||||||||||||||||
| }, connectionString); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var conn = new SqlConnection(GetConnectionString(connectionString, encryptionEnabled: true)); | ||||||||||||||||||||
| await conn.OpenAsync(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var adapter = CreateAdapter(conn, updateBatchSize: 10); | ||||||||||||||||||||
| var dataTable = BuildBuyerSellerDataTable(); | ||||||||||||||||||||
| LoadCurrentRowsIntoDataTable(dataTable, conn); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| MutateForUpdate(dataTable); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Act - With batch updates (UpdateBatchSize > 1), this previously threw NullReferenceException due to null systemParams in batch RPC mode | ||||||||||||||||||||
| var updated = await Task.Run(() => adapter.Update(dataTable)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Assert | ||||||||||||||||||||
| Assert.Equal(dataTable.Rows.Count, updated); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] | ||||||||||||||||||||
| [ClassData(typeof(AEConnectionStringProvider))] | ||||||||||||||||||||
| public async Task AdapterUpdate_BatchSizeOne_Succeeds(string connectionString) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| // Arrange | ||||||||||||||||||||
| TruncateTable(connectionString); | ||||||||||||||||||||
| int idBase = GetUniqueIdBase(); | ||||||||||||||||||||
| PopulateTable(new (int id, string s1, string s2)[] { | ||||||||||||||||||||
| (idBase + 100, "123-45-6789", "987-65-4321"), | ||||||||||||||||||||
| (idBase + 200, "234-56-7890", "876-54-3210"), | ||||||||||||||||||||
| (idBase + 300, "345-67-8901", "765-43-2109"), | ||||||||||||||||||||
| (idBase + 400, "456-78-9012", "654-32-1098"), | ||||||||||||||||||||
| }, connectionString); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var conn = new SqlConnection(GetConnectionString(connectionString, encryptionEnabled: true)); | ||||||||||||||||||||
| await conn.OpenAsync(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var adapter = CreateAdapter(conn, updateBatchSize: 1); // success path | ||||||||||||||||||||
| var dataTable = BuildBuyerSellerDataTable(); | ||||||||||||||||||||
| LoadCurrentRowsIntoDataTable(dataTable, conn); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| MutateForUpdate(dataTable); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Act | ||||||||||||||||||||
| var updatedRows = await Task.Run(() => adapter.Update(dataTable)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Assert | ||||||||||||||||||||
| Assert.Equal(dataTable.Rows.Count, updatedRows); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // ---------- HELPERS ---------- | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private int GetUniqueIdBase() => Math.Abs(Guid.NewGuid().GetHashCode()) % 1000000; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private SqlDataAdapter CreateAdapter(SqlConnection connection, int updateBatchSize) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| var insertCmd = new SqlCommand(_buyerSellerTable.InsertProcedureName, connection) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| CommandType = CommandType.StoredProcedure | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| insertCmd.Parameters.AddRange(new[] | ||||||||||||||||||||
| { | ||||||||||||||||||||
| new SqlParameter("@BuyerSellerID", SqlDbType.Int) { SourceColumn = "BuyerSellerID" }, | ||||||||||||||||||||
| new SqlParameter("@SSN1", SqlDbType.VarChar, 255) { SourceColumn = "SSN1" }, | ||||||||||||||||||||
| new SqlParameter("@SSN2", SqlDbType.VarChar, 255) { SourceColumn = "SSN2" }, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| insertCmd.UpdatedRowSource = UpdateRowSource.None; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| var updateCmd = new SqlCommand(_buyerSellerTable.UpdateProcedureName, connection) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| CommandType = CommandType.StoredProcedure | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| updateCmd.Parameters.AddRange(new[] | ||||||||||||||||||||
| { | ||||||||||||||||||||
| new SqlParameter("@BuyerSellerID", SqlDbType.Int) { SourceColumn = "BuyerSellerID" }, | ||||||||||||||||||||
| new SqlParameter("@SSN1", SqlDbType.VarChar, 255) { SourceColumn = "SSN1" }, | ||||||||||||||||||||
| new SqlParameter("@SSN2", SqlDbType.VarChar, 255) { SourceColumn = "SSN2" }, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| updateCmd.UpdatedRowSource = UpdateRowSource.None; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return new SqlDataAdapter | ||||||||||||||||||||
| { | ||||||||||||||||||||
| InsertCommand = insertCmd, | ||||||||||||||||||||
| UpdateCommand = updateCmd, | ||||||||||||||||||||
| UpdateBatchSize = updateBatchSize | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private DataTable BuildBuyerSellerDataTable() | ||||||||||||||||||||
| { | ||||||||||||||||||||
| var dt = new DataTable(_tableName); | ||||||||||||||||||||
| dt.Columns.AddRange(new[] | ||||||||||||||||||||
| { | ||||||||||||||||||||
| new DataColumn("BuyerSellerID", typeof(int)), | ||||||||||||||||||||
priyankatiwari08 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| new DataColumn("SSN1", typeof(string)), | ||||||||||||||||||||
priyankatiwari08 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| new DataColumn("SSN2", typeof(string)), | ||||||||||||||||||||
priyankatiwari08 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
| }); | ||||||||||||||||||||
|
Comment on lines
+131
to
+136
|
||||||||||||||||||||
| dt.Columns.AddRange(new[] | |
| { | |
| new DataColumn("BuyerSellerID", typeof(int)), | |
| new DataColumn("SSN1", typeof(string)), | |
| new DataColumn("SSN2", typeof(string)), | |
| }); | |
| dt.Columns.Add("BuyerSellerID", typeof(int)); | |
| dt.Columns.Add("SSN1", typeof(string)); | |
| dt.Columns.Add("SSN2", typeof(string)); |
priyankatiwari08 marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // 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; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup | ||
| { | ||
| public class BuyerSellerTable : Table | ||
| { | ||
| private const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256"; | ||
| private readonly ColumnEncryptionKey _columnEncryptionKey1; | ||
| private readonly ColumnEncryptionKey _columnEncryptionKey2; | ||
|
|
||
| // ✅ ADD: Unique stored procedure names based on table name | ||
|
||
| public string InsertProcedureName => $"InsertBuyerSeller_{Name}"; | ||
| public string UpdateProcedureName => $"UpdateBuyerSeller_{Name}"; | ||
|
|
||
| public BuyerSellerTable(string tableName, ColumnEncryptionKey columnEncryptionKey1, ColumnEncryptionKey columnEncryptionKey2) | ||
| : base(tableName) | ||
| { | ||
| _columnEncryptionKey1 = columnEncryptionKey1; | ||
| _columnEncryptionKey2 = columnEncryptionKey2; | ||
| } | ||
|
|
||
| public override void Create(SqlConnection sqlConnection) | ||
| { | ||
| // Create the table with encrypted columns | ||
| string createTableSql = $@" | ||
| CREATE TABLE [dbo].[{Name}] | ||
| ( | ||
| [BuyerSellerID] [int] NOT NULL PRIMARY KEY, | ||
| [SSN1] [varchar](255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( | ||
| COLUMN_ENCRYPTION_KEY = [{_columnEncryptionKey1.Name}], | ||
| ENCRYPTION_TYPE = DETERMINISTIC, | ||
| ALGORITHM = '{ColumnEncryptionAlgorithmName}' | ||
| ), | ||
| [SSN2] [varchar](255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( | ||
| COLUMN_ENCRYPTION_KEY = [{_columnEncryptionKey2.Name}], | ||
| ENCRYPTION_TYPE = DETERMINISTIC, | ||
| ALGORITHM = '{ColumnEncryptionAlgorithmName}' | ||
| ) | ||
| )"; | ||
|
|
||
| using (SqlCommand command = sqlConnection.CreateCommand()) | ||
| { | ||
| command.CommandText = createTableSql; | ||
| command.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| // ✅ CHANGED: Use unique SP names | ||
|
||
| string createInsertProcSql = $@" | ||
| CREATE PROCEDURE [dbo].[{InsertProcedureName}] | ||
| @BuyerSellerID int, | ||
| @SSN1 varchar(255), | ||
| @SSN2 varchar(255) | ||
| AS | ||
| BEGIN | ||
| INSERT INTO [dbo].[{Name}] (BuyerSellerID, SSN1, SSN2) | ||
| VALUES (@BuyerSellerID, @SSN1, @SSN2) | ||
| END"; | ||
|
|
||
| using (SqlCommand command = sqlConnection.CreateCommand()) | ||
| { | ||
| command.CommandText = createInsertProcSql; | ||
| command.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| // ✅ CHANGED: Use unique SP names | ||
|
||
| string createUpdateProcSql = $@" | ||
| CREATE PROCEDURE [dbo].[{UpdateProcedureName}] | ||
| @BuyerSellerID int, | ||
| @SSN1 varchar(255), | ||
| @SSN2 varchar(255) | ||
| AS | ||
| BEGIN | ||
| UPDATE [dbo].[{Name}] | ||
| SET SSN1 = @SSN1, SSN2 = @SSN2 | ||
| WHERE BuyerSellerID = @BuyerSellerID | ||
| END"; | ||
|
|
||
| using (SqlCommand command = sqlConnection.CreateCommand()) | ||
| { | ||
| command.CommandText = createUpdateProcSql; | ||
| command.ExecuteNonQuery(); | ||
| } | ||
| } | ||
|
|
||
| public override void Drop(SqlConnection sqlConnection) | ||
| { | ||
| using (SqlCommand command = sqlConnection.CreateCommand()) | ||
| { | ||
| command.CommandText = $"IF OBJECT_ID('[dbo].[{InsertProcedureName}]', 'P') IS NOT NULL DROP PROCEDURE [dbo].[{InsertProcedureName}]"; | ||
| command.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| using (SqlCommand command = sqlConnection.CreateCommand()) | ||
| { | ||
| command.CommandText = $"IF OBJECT_ID('[dbo].[{UpdateProcedureName}]', 'P') IS NOT NULL DROP PROCEDURE [dbo].[{UpdateProcedureName}]"; | ||
| command.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| // Drop table | ||
| base.Drop(sqlConnection); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.