-
Notifications
You must be signed in to change notification settings - Fork 330
[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
Changes from 12 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,305 @@ | ||||||||||||||||||||
| // 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 Dictionary<string, string> tableNames = new(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public SqlDataAdapterBatchUpdateTests(SQLSetupStrategyCertStoreProvider context) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| _fixture = context; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Provide table names to mirror repo patterns. | ||||||||||||||||||||
| // If your fixture already exposes specific names for BuyerSeller and procs, wire them here. | ||||||||||||||||||||
| // Otherwise use literal names as below. | ||||||||||||||||||||
|
priyankatiwari08 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||
| tableNames["BuyerSeller"] = "BuyerSeller"; | ||||||||||||||||||||
| tableNames["ProcInsertBuyerSeller"] = "InsertBuyerSeller"; | ||||||||||||||||||||
| tableNames["ProcUpdateBuyerSeller"] = "UpdateBuyerSeller"; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
priyankatiwari08 marked this conversation as resolved.
Outdated
|
||||||||||||||||||||
| // ---------- TESTS ---------- | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
priyankatiwari08 marked this conversation as resolved.
|
||||||||||||||||||||
| [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] | ||||||||||||||||||||
| [ClassData(typeof(AEConnectionStringProvider))] | ||||||||||||||||||||
| public async Task AdapterUpdate_BatchSizeGreaterThanOne_Succeeds(string connectionString) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| // Arrange | ||||||||||||||||||||
| // Ensure baseline rows exist | ||||||||||||||||||||
| EnsureBuyerSellerObjectsExist(connectionString); | ||||||||||||||||||||
| TruncateTables("BuyerSeller", connectionString); | ||||||||||||||||||||
| PopulateTable("BuyerSeller", new (int id, string s1, string s2)[] { | ||||||||||||||||||||
| (1, "123-45-6789", "987-65-4321"), | ||||||||||||||||||||
| (2, "234-56-7890", "876-54-3210"), | ||||||||||||||||||||
| (3, "345-67-8901", "765-43-2109"), | ||||||||||||||||||||
| (4, "456-78-9012", "654-32-1098"), | ||||||||||||||||||||
| }, connectionString); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var conn = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true)); | ||||||||||||||||||||
| await conn.OpenAsync(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var adapter = CreateAdapter(conn, updateBatchSize: 10); // failure repro: > 1 | ||||||||||||||||||||
| var dataTable = BuildBuyerSellerDataTable(); | ||||||||||||||||||||
| LoadCurrentRowsIntoDataTable(dataTable, conn); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Mutate values for update | ||||||||||||||||||||
| 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 | ||||||||||||||||||||
| EnsureBuyerSellerObjectsExist(connectionString); | ||||||||||||||||||||
| TruncateTables("BuyerSeller", connectionString); | ||||||||||||||||||||
| PopulateTable("BuyerSeller", new (int id, string s1, string s2)[] { | ||||||||||||||||||||
| (1, "123-45-6789", "987-65-4321"), | ||||||||||||||||||||
| (2, "234-56-7890", "876-54-3210"), | ||||||||||||||||||||
| (3, "345-67-8901", "765-43-2109"), | ||||||||||||||||||||
| (4, "456-78-9012", "654-32-1098"), | ||||||||||||||||||||
| }, connectionString); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var conn = new SqlConnection(GetOpenConnectionString(connectionString, encryptionEnabled: true)); | ||||||||||||||||||||
| await conn.OpenAsync(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| using var adapter = CreateAdapter(conn, updateBatchSize: 1); // success path | ||||||||||||||||||||
| var dataTable = BuildBuyerSellerDataTable(); | ||||||||||||||||||||
| LoadCurrentRowsIntoDataTable(dataTable, conn); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| MutateForUpdate(dataTable); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Act (should not throw) | ||||||||||||||||||||
| var updatedRows = await Task.Run(() => adapter.Update(dataTable)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Assert | ||||||||||||||||||||
| Assert.Equal(dataTable.Rows.Count, updatedRows); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // ---------- HELPERS ---------- | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private SqlDataAdapter CreateAdapter(SqlConnection connection, int updateBatchSize) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| // Insert | ||||||||||||||||||||
| var insertCmd = new SqlCommand(tableNames["ProcInsertBuyerSeller"], 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; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Update | ||||||||||||||||||||
| var updateCmd = new SqlCommand(tableNames["ProcUpdateBuyerSeller"], 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(tableNames["BuyerSeller"]); | ||||||||||||||||||||
| dt.Columns.AddRange(new[] | ||||||||||||||||||||
| { | ||||||||||||||||||||
| new DataColumn("BuyerSellerID", typeof(int)), | ||||||||||||||||||||
|
priyankatiwari08 marked this conversation as resolved.
|
||||||||||||||||||||
| new DataColumn("SSN1", typeof(string)), | ||||||||||||||||||||
|
priyankatiwari08 marked this conversation as resolved.
|
||||||||||||||||||||
| new DataColumn("SSN2", typeof(string)), | ||||||||||||||||||||
|
priyankatiwari08 marked this conversation as 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)); |
Uh oh!
There was an error while loading. Please reload this page.