From c8dcecffdc3a5d87d82db54da06b0981797173c2 Mon Sep 17 00:00:00 2001 From: NorthernLight1 <49600465+NorthernLight1@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:10:07 -0500 Subject: [PATCH] Fixed Complex Key Bug In BulkMerge(), BulkMergeAsync() Fixed Bug In Framwork For Chinese_Taiwan_Stroke_CS_AS Collation --- .../Data/Order.cs | 1 + .../Data/ProductWithComplexKey.cs | 7 ++- .../Data/TestDbContext.cs | 3 +- .../DbContextExtensions/BulkInsert.cs | 2 +- .../DbContextExtensions/BulkInsertAsync.cs | 2 +- .../DbContextExtensions/BulkMerge.cs | 61 +++++++++++-------- .../DbContextExtensions/BulkMergeAsync.cs | 35 +++++++++++ .../Data/BulkOperation.cs | 3 +- .../Data/DbContextExtensions.cs | 1 + .../N.EntityFrameworkCore.Extensions.csproj | 2 +- .../Util/CommonUtil.cs | 2 +- 11 files changed, 84 insertions(+), 35 deletions(-) diff --git a/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs b/N.EntityFrameworkCore.Extensions.Test/Data/Order.cs index 8e50f11..48f7715 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 DateTimeOffset? ModifiedDateTimeOffset { get; set; } + public bool DbActive { get; set; } public DateTime DbAddedDateTime { get; set; } public DateTime DbModifiedDateTime { get; set; } public bool? Trigger { get; set; } diff --git a/N.EntityFrameworkCore.Extensions.Test/Data/ProductWithComplexKey.cs b/N.EntityFrameworkCore.Extensions.Test/Data/ProductWithComplexKey.cs index d7ebe4e..948fc86 100644 --- a/N.EntityFrameworkCore.Extensions.Test/Data/ProductWithComplexKey.cs +++ b/N.EntityFrameworkCore.Extensions.Test/Data/ProductWithComplexKey.cs @@ -9,6 +9,8 @@ public class ProductWithComplexKey public Guid Key1 { get; set; } public Guid Key2 { get; set; } public Guid Key3 { get; set; } + public Guid Key4 { get; set; } + public string ExternalId { get; set; } public decimal Price { get; set; } public bool OutOfStock { get; set; } [Column("Status")] @@ -17,8 +19,7 @@ public class ProductWithComplexKey public DateTime? UpdatedDateTime { get; set; } public ProductWithComplexKey() { - //Key1 = Guid.NewGuid(); - //Key2 = Guid.NewGuid(); - //Key3 = Guid.NewGuid(); + Key3 = Guid.NewGuid(); + Key4 = Guid.NewGuid(); } } \ No newline at end of file diff --git a/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs b/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs index cfb22a1..5b54413 100644 --- a/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs +++ b/N.EntityFrameworkCore.Extensions.Test/Data/TestDbContext.cs @@ -32,9 +32,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasKey(c => new { c.Key1 }); modelBuilder.Entity().Property("Key1").HasDefaultValueSql("newsequentialid()"); modelBuilder.Entity().Property("Key2").HasDefaultValueSql("newsequentialid()"); - modelBuilder.Entity().Property("Key3").HasDefaultValueSql("newsequentialid()"); + modelBuilder.Entity().HasKey(p => new { p.Key3, p.Key4 }); modelBuilder.Entity().Property("DbAddedDateTime").HasDefaultValueSql("getdate()"); modelBuilder.Entity().Property("DbModifiedDateTime").HasComputedColumnSql("getdate()"); + modelBuilder.Entity().Property(p => p.DbActive).HasDefaultValueSql("((1))"); modelBuilder.Entity().Property(p => p.Status).HasConversion(); modelBuilder.Entity().UseTpcMappingStrategy(); modelBuilder.Entity().ToTable("TpcCustomer"); diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs index b0be457..fbc597d 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsert.cs @@ -381,7 +381,7 @@ public void With_ValueGenerated_Default() } 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(); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbAddedDateTime > nowDateTime && o.DbActive).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 a66d7ba..ec04c5f 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkInsertAsync.cs @@ -400,7 +400,7 @@ public async Task With_ValueGenerated_Default() } 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(); + int newTotal = dbContext.Orders.Where(o => o.Price <= 10 && o.DbAddedDateTime > nowDateTime && o.DbActive).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 c49a369..61d2f2c 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMerge.cs @@ -9,6 +9,41 @@ namespace N.EntityFrameworkCore.Extensions.Test.DbContextExtensions; [TestClass] public class BulkMerge : DbContextExtensionsBase { + [TestMethod] + public void With_Complex_Key() + { + var dbContext = SetupDbContext(true); + var products = dbContext.ProductsWithComplexKey.Where(o => o.Price == 1.25M).ToList(); + int productsToAdd = 5000; + decimal updatedPrice = 5.25M; + var productsToUpdate = products.ToList(); + foreach (var product in products) + { + product.Price = updatedPrice; + } + for (int i = 0; i < productsToAdd; i++) + { + products.Add(new ProductWithComplexKey { ExternalId = (20000 + i).ToString(), Price = 3.55M }); + } + var result = dbContext.BulkMerge(products); + var allProducts = dbContext.ProductsWithComplexKey.ToList(); + bool areAddedOrdersMerged = true; + bool areUpdatedOrdersMerged = true; + foreach (var product in allProducts) + { + if (productsToUpdate.Contains(product) && product.Price != updatedPrice) + { + areUpdatedOrdersMerged = false; + break; + } + } + + Assert.IsTrue(result.RowsAffected == products.Count(), "The number of rows inserted must match the count of order list"); + Assert.IsTrue(result.RowsUpdated == productsToUpdate.Count, "The number of rows updated must match"); + Assert.IsTrue(result.RowsInserted == productsToAdd, "The number of rows added must match"); + Assert.IsTrue(areAddedOrdersMerged, "The orders that were added did not merge correctly"); + Assert.IsTrue(areUpdatedOrdersMerged, "The orders that were updated did not merge correctly"); + } [TestMethod] public void With_Default_Options() { @@ -218,32 +253,6 @@ public void With_Options_AutoMapIdentity() Assert.IsTrue(autoMapIdentityMatched, "The auto mapping of ids of entities that were merged failed to match up"); } [TestMethod] - public void With_merge_Should_include_all_auto_generated_properties() - { - var dbContext = SetupDbContext(true); - int ordersToUpdate = 3; - int ordersToAdd = 2; - var orders = new List - { - new Order { ExternalId = "id-1", Price=7.10M }, - new Order { ExternalId = "id-2", Price=9.33M }, - new Order { ExternalId = "id-3", Price=3.25M }, - new Order { ExternalId = "id-1000001", Price=2.15M }, - new Order { ExternalId = "id-1000002", Price=5.75M }, - }; - var result = dbContext.BulkMerge(orders, new BulkMergeOptions - { - MergeOnCondition = (s, t) => s.ExternalId == t.ExternalId, - AutoMapOutput = true - }); - var autoMapIdentityMatched = orders.All(x => x.Id != 0); - - Assert.IsTrue(result.RowsAffected == ordersToAdd + ordersToUpdate, "The number of rows inserted must match the count of order list"); - Assert.IsTrue(result.RowsUpdated == ordersToUpdate, "The number of rows updated must match"); - Assert.IsTrue(result.RowsInserted == ordersToAdd, "The number of rows added must match"); - Assert.IsTrue(autoMapIdentityMatched, "The auto mapping of ids of entities that were merged failed to match up"); - } - [TestMethod] public void With_Key() { var dbContext = SetupDbContext(true); diff --git a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs index 74e3cda..b160c68 100644 --- a/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs +++ b/N.EntityFrameworkCore.Extensions.Test/DbContextExtensions/BulkMergeAsync.cs @@ -10,6 +10,41 @@ namespace N.EntityFrameworkCore.Extensions.Test.DbContextExtensions; [TestClass] public class BulkMergeAsync : DbContextExtensionsBase { + [TestMethod] + public async Task With_Complex_Key() + { + var dbContext = SetupDbContext(true); + var products = dbContext.ProductsWithComplexKey.Where(o => o.Price == 1.25M).ToList(); + int productsToAdd = 5000; + decimal updatedPrice = 5.25M; + var productsToUpdate = products.ToList(); + foreach (var product in products) + { + product.Price = updatedPrice; + } + for (int i = 0; i < productsToAdd; i++) + { + products.Add(new ProductWithComplexKey { ExternalId = (20000 + i).ToString(), Price = 3.55M }); + } + var result = await dbContext.BulkMergeAsync(products); + var allProducts = dbContext.ProductsWithComplexKey.ToList(); + bool areAddedOrdersMerged = true; + bool areUpdatedOrdersMerged = true; + foreach (var product in allProducts) + { + if (productsToUpdate.Contains(product) && product.Price != updatedPrice) + { + areUpdatedOrdersMerged = false; + break; + } + } + + Assert.IsTrue(result.RowsAffected == products.Count(), "The number of rows inserted must match the count of order list"); + Assert.IsTrue(result.RowsUpdated == productsToUpdate.Count, "The number of rows updated must match"); + Assert.IsTrue(result.RowsInserted == productsToAdd, "The number of rows added must match"); + Assert.IsTrue(areAddedOrdersMerged, "The orders that were added did not merge correctly"); + Assert.IsTrue(areUpdatedOrdersMerged, "The orders that were updated did not merge correctly"); + } [TestMethod] public async Task With_Default_Options() { diff --git a/N.EntityFrameworkCore.Extensions/Data/BulkOperation.cs b/N.EntityFrameworkCore.Extensions/Data/BulkOperation.cs index 042ad51..0bb7860 100644 --- a/N.EntityFrameworkCore.Extensions/Data/BulkOperation.cs +++ b/N.EntityFrameworkCore.Extensions/Data/BulkOperation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -138,7 +139,7 @@ .. TableMapping.GetEntityProperties(entityType, ValueGenerated.OnAddOrUpdate).To private IEnumerable GetMergeOutputColumns(IEnumerable autoGeneratedColumns, bool delete = false) { - List columnsToOutput = new List { "$Action", string.Format("[{0}].[{1}]", "s", Constants.InternalId_ColumnName) }; + List columnsToOutput = new List { "$action", string.Format("[{0}].[{1}]", "s", Constants.InternalId_ColumnName) }; columnsToOutput.AddRange(autoGeneratedColumns.Select(o => string.Format("[inserted].[{0}]", o))); return columnsToOutput.AsEnumerable(); } diff --git a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs index 255f9f6..fccd9fb 100644 --- a/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs +++ b/N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.IO; using System.Linq; using System.Linq.Expressions; diff --git a/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj b/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj index fc6f8e0..54607a8 100644 --- a/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj +++ b/N.EntityFrameworkCore.Extensions/N.EntityFrameworkCore.Extensions.csproj @@ -2,7 +2,7 @@ net8.0 - 8.0.0.11 + 8.0.0.12 true https://github.com/NorthernLight1/N.EntityFrameworkCore.Extensions/ Northern25 diff --git a/N.EntityFrameworkCore.Extensions/Util/CommonUtil.cs b/N.EntityFrameworkCore.Extensions/Util/CommonUtil.cs index ab4b9e2..7d0d693 100644 --- a/N.EntityFrameworkCore.Extensions/Util/CommonUtil.cs +++ b/N.EntityFrameworkCore.Extensions/Util/CommonUtil.cs @@ -98,7 +98,7 @@ internal static string GetJoinConditionSql(Expression> joinKeyE int i = 1; foreach (var storeGeneratedColumnName in storeGeneratedColumnNames) { - joinConditionSql += (i > 1 ? "AND" : "") + string.Format("{0}.{2}={1}.{2}", sourceTableName, targetTableName, storeGeneratedColumnName); + joinConditionSql += (i > 1 ? " AND " : "") + string.Format("{0}.{2}={1}.{2}", sourceTableName, targetTableName, storeGeneratedColumnName); i++; } }