From e1569c93d59dcc65f1fda73c4977ab6dbfafff36 Mon Sep 17 00:00:00 2001 From: NorthernLight1 <49600465+NorthernLight1@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:26:21 -0500 Subject: [PATCH] BulkMergeAsync Not Adding Default Value On Insert Or Updating Computed Fixed Bug In BulkMergeAsync So That A Default Value On Insert Fixed Bug In BulkMergeAsync To Update Computed Column On Added Test Cases 'With_ValueGenerated_Default', 'With_ValueGenerated_Computed' to cover BulkInsert, BulkInsertAsync, BulkMerge, BulkMergeAsync methods --- .../Data/Order.cs | 1 + .../Data/TestDbContext.cs | 1 + .../DbContextExtensions/BulkInsert.cs | 51 ++++++++++++------- .../DbContextExtensions/BulkInsertAsync.cs | 34 +++++++++++++ .../DbContextExtensions/BulkMerge.cs | 35 +++++++++++++ .../DbContextExtensions/BulkMergeAsync.cs | 37 +++++++++++++- .../Data/DbContextExtensions.cs | 7 +-- .../Data/DbContextExtensionsAsync.cs | 7 +-- .../N.EntityFrameworkCore.Extensions.csproj | 2 +- 9 files changed, 150 insertions(+), 25 deletions(-) diff --git a/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs b/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs index 9a82ae7..94cc4c8 100644 --- a/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs +++ b/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs @@ -13,6 +13,7 @@ public class Order public DateTime AddedDateTime { get; set; } public DateTime? ModifiedDateTime { get; set; } public DateTime DbAddedDateTime { get; set; } + public DateTime DbModifiedDateTime { get; set; } public bool? Trigger { get; set; } public bool Active { get; set; } public Order() diff --git a/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs b/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs index 0578c51..5f2f6ea 100644 --- a/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs +++ b/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs @@ -32,6 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().Property("Key2").HasDefaultValueSql("newsequentialid()"); modelBuilder.Entity().Property("Key3").HasDefaultValueSql("newsequentialid()"); modelBuilder.Entity().Property("DbAddedDateTime").HasDefaultValueSql("getdate()"); + modelBuilder.Entity().Property("DbModifiedDateTime").HasComputedColumnSql("getdate()"); modelBuilder.Entity().UseTpcMappingStrategy(); modelBuilder.Entity().ToTable("TpcCustomer"); modelBuilder.Entity().ToTable("TpcVendor"); diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs index 69a4686..744edc9 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs @@ -28,23 +28,6 @@ public void With_Complex_Key() Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); } [TestMethod] - public void With_Database_Generated_Column() - { - var dbContext = SetupDbContext(false); - var dbAddedDateTime = DateTime.Now; - var orders = new List(); - for (int i = 0; i < 20000; i++) - { - orders.Add(new Order { Id = i, Price = 1.57M }); - } - int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); - int rowsInserted = dbContext.BulkInsert(orders); - int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbAddedDateTime > dbAddedDateTime).Count(); - - Assert.IsTrue(rowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); - Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); - } - [TestMethod] public void With_Default_Options() { var dbContext = SetupDbContext(false); @@ -338,5 +321,39 @@ public void With_Options_InsertIfNotExists() Assert.IsTrue(rowsInserted == expectedRowsInserted, "The number of rows inserted must match the count of order list"); Assert.IsTrue(newTotal - oldTotal == expectedRowsInserted, "The new count minus the old count should match the number of rows inserted."); } + [TestMethod] + public void With_ValueGenerated_Default() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + int rowsInserted = dbContext.BulkInsert(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbAddedDateTime > nowDateTime).Count(); + + Assert.IsTrue(rowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); + } + [TestMethod] + public void With_ValueGenerated_Computed() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M, DbModifiedDateTime = nowDateTime }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + int rowsInserted = dbContext.BulkInsert(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbModifiedDateTime > nowDateTime).Count(); + + Assert.IsTrue(rowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); + } } } diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs index d846e13..a1d1d5b 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs @@ -285,5 +285,39 @@ public async Task With_Options_InsertIfNotExists() Assert.IsTrue(rowsInserted == expectedRowsInserted, "The number of rows inserted must match the count of order list"); Assert.IsTrue(newTotal - oldTotal == expectedRowsInserted, "The new count minus the old count should match the number of rows inserted."); } + [TestMethod] + public async Task With_ValueGenerated_Default() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + int rowsInserted = await dbContext.BulkInsertAsync(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbAddedDateTime > nowDateTime).Count(); + + Assert.IsTrue(rowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); + } + [TestMethod] + public async Task With_ValueGenerated_Computed() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M, DbModifiedDateTime = nowDateTime }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + int rowsInserted = await dbContext.BulkInsertAsync(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbModifiedDateTime > nowDateTime).Count(); + + Assert.IsTrue(rowsInserted == orders.Count(), "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == rowsInserted, "The new count minus the old count should match the number of rows inserted."); + } } } diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs index 13bc9d4..1265c9c 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs @@ -259,5 +259,40 @@ public void With_Transaction() Assert.IsTrue(ordersAdded == 0, "The number of rows added must equal 0 since transaction was rollbacked"); Assert.IsTrue(ordersUpdated == 0, "The number of rows updated must equal 0 since transaction was rollbacked"); } + [TestMethod] + public void With_ValueGenerated_Default() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 1000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M }); + } + int oldTotal = dbContext.Orders.Where(o => o.DbAddedDateTime > nowDateTime).Count(); + var mergeResult = dbContext.BulkMerge(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 1.57M + && o.DbAddedDateTime > nowDateTime).Count(); + + Assert.IsTrue(mergeResult.RowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == mergeResult.RowsInserted, "The new count minus the old count should match the number of rows inserted."); + } + [TestMethod] + public void With_ValueGenerated_Computed() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M, DbModifiedDateTime = nowDateTime }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + var result = dbContext.BulkMerge(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbModifiedDateTime > nowDateTime).Count(); + + Assert.IsTrue(result.RowsInserted == orders.Count(), "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == result.RowsInserted, "The new count minus the old count should match the number of rows inserted."); + } } } diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs index 40a4484..63f0054 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs @@ -260,5 +260,40 @@ public async Task With_Transaction() Assert.IsTrue(ordersAdded == 0, "The number of rows added must equal 0 since transaction was rollbacked"); Assert.IsTrue(ordersUpdated == 0, "The number of rows updated must equal 0 since transaction was rollbacked"); } + [TestMethod] + public async Task With_ValueGenerated_Default() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 1000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M }); + } + int oldTotal = dbContext.Orders.Where(o => o.DbAddedDateTime > nowDateTime).Count(); + var mergeResult = await dbContext.BulkMergeAsync(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 1.57M + && o.DbAddedDateTime > nowDateTime).Count(); + + Assert.IsTrue(mergeResult.RowsInserted == orders.Count, "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == mergeResult.RowsInserted, "The new count minus the old count should match the number of rows inserted."); + } + [TestMethod] + public async Task With_ValueGenerated_Computed() + { + var dbContext = SetupDbContext(false); + var nowDateTime = DateTime.Now; + var orders = new List(); + for (int i = 0; i < 20000; i++) + { + orders.Add(new Order { Id = i, Price = 1.57M, DbModifiedDateTime = nowDateTime }); + } + int oldTotal = dbContext.Orders.Where(o => o.Price <= 10).Count(); + var result = await dbContext.BulkMergeAsync(orders); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbModifiedDateTime > nowDateTime).Count(); + + Assert.IsTrue(result.RowsInserted == orders.Count(), "The number of rows inserted must match the count of order list"); + Assert.IsTrue(newTotal - oldTotal == result.RowsInserted, "The new count minus the old count should match the number of rows inserted."); + } } -} +} \ No newline at end of file diff --git a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs index 4bb19c0..52fcdbd 100644 --- a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs +++ b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs @@ -466,16 +466,17 @@ private static BulkMergeResult InternalBulkMerge(this DbContext context, I { string stagingTableName = CommonUtil.GetStagingTableName(tableMapping, options.UsePermanentTable, dbConnection); string destinationTableName = string.Format("[{0}].[{1}]", tableMapping.Schema, tableMapping.TableName); - string[] columnNames = tableMapping.GetColumns().ToArray(); + string[] stagingColumnNames = tableMapping.GetColumns(true).ToArray(); string[] primaryKeyColumnNames = tableMapping.GetPrimaryKeyColumns().ToArray(); IEnumerable autoGeneratedColumnNames = options.AutoMapOutput ? tableMapping.GetAutoGeneratedColumns() : new string[] { }; if (primaryKeyColumnNames.Length == 0 && options.MergeOnCondition == null) throw new InvalidDataException("BulkMerge requires that the entity have a primary key or the Options.MergeOnCondition must be set."); - context.Database.CloneTable(destinationTableName, stagingTableName, null, Common.Constants.InternalId_ColumnName); - var bulkInsertResult = BulkInsert(entities, options, tableMapping, dbConnection, transaction, stagingTableName, null, SqlBulkCopyOptions.KeepIdentity, true); + context.Database.CloneTable(destinationTableName, stagingTableName, stagingColumnNames, Common.Constants.InternalId_ColumnName); + var bulkInsertResult = BulkInsert(entities, options, tableMapping, dbConnection, transaction, stagingTableName, stagingColumnNames, SqlBulkCopyOptions.KeepIdentity, true); + string[] columnNames = tableMapping.GetColumns().ToArray(); IEnumerable columnsToInsert = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnInsert().Contains(o))); IEnumerable columnstoUpdate = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnUpdate().Contains(o))).Select(o => string.Format("t.{0}=s.{0}", o)); List columnsToOutput = new List { "$Action", string.Format("{0}.{1}", "s", Constants.InternalId_ColumnName) }; diff --git a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs index 17de275..cec2799 100644 --- a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs +++ b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs @@ -320,16 +320,17 @@ private async static Task> InternalBulkMergeAsync(this DbC { string stagingTableName = CommonUtil.GetStagingTableName(tableMapping, options.UsePermanentTable, dbConnection); string destinationTableName = string.Format("[{0}].[{1}]", tableMapping.Schema, tableMapping.TableName); - string[] columnNames = tableMapping.GetColumns().ToArray(); + string[] stagingColumnNames = tableMapping.GetColumns(true).ToArray(); string[] primaryKeyColumnNames = tableMapping.GetPrimaryKeyColumns().ToArray(); IEnumerable autoGeneratedColumnNames = options.AutoMapOutput ? tableMapping.GetAutoGeneratedColumns() : new string[] { }; if (primaryKeyColumnNames.Length == 0 && options.MergeOnCondition == null) throw new InvalidDataException("BulkMerge requires that the entity have a primary key or the Options.MergeOnCondition must be set."); - await context.Database.CloneTableAsync(destinationTableName, stagingTableName, null, Common.Constants.InternalId_ColumnName, cancellationToken); - var bulkInsertResult = await BulkInsertAsync(entities, options, tableMapping, dbConnection, transaction, stagingTableName, null, SqlBulkCopyOptions.KeepIdentity, true, cancellationToken); + await context.Database.CloneTableAsync(destinationTableName, stagingTableName, stagingColumnNames, Common.Constants.InternalId_ColumnName, cancellationToken); + var bulkInsertResult = await BulkInsertAsync(entities, options, tableMapping, dbConnection, transaction, stagingTableName, stagingColumnNames, SqlBulkCopyOptions.KeepIdentity, true, cancellationToken); + string[] columnNames = tableMapping.GetColumns().ToArray(); IEnumerable columnsToInsert = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnInsert().Contains(o))); IEnumerable columnstoUpdate = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnUpdate().Contains(o))).Select(o => string.Format("t.{0}=s.{0}", o)); List columnsToOutput = new List { "$Action", string.Format("{0}.{1}", "s", Constants.InternalId_ColumnName) }; diff --git a/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj b/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj index 24172cb..66f4867 100644 --- a/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj +++ b/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj @@ -2,7 +2,7 @@ net6.0 - 7.0.13.0 + 7.0.13.2 true https://github.com/NorthernLight1/N.EntityFrameworkCore.Extensions/ Northern25