Skip to content
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

BulkMergeAsync Not Adding Default Value On Insert Or Updating Computed Column #38

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions N.EntityFrameworkCore.Extensions.Test/Data/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<ProductWithComplexKey>().Property<Guid>("Key2").HasDefaultValueSql("newsequentialid()");
modelBuilder.Entity<ProductWithComplexKey>().Property<Guid>("Key3").HasDefaultValueSql("newsequentialid()");
modelBuilder.Entity<Order>().Property<DateTime>("DbAddedDateTime").HasDefaultValueSql("getdate()");
modelBuilder.Entity<Order>().Property<DateTime>("DbModifiedDateTime").HasComputedColumnSql("getdate()");
modelBuilder.Entity<TpcPerson>().UseTpcMappingStrategy();
modelBuilder.Entity<TpcCustomer>().ToTable("TpcCustomer");
modelBuilder.Entity<TpcVendor>().ToTable("TpcVendor");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Order>();
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);
Expand Down Expand Up @@ -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<Order>();
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<Order>();
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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Order>();
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<Order>();
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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Order>();
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<Order>();
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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Order>();
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<Order>();
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.");
}
}
}
}
7 changes: 4 additions & 3 deletions N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@

dbTransactionContext.Commit();
}
catch (Exception ex)

Check warning on line 124 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'ex' is declared but never used
{
dbTransactionContext.Rollback();
throw;
Expand Down Expand Up @@ -324,22 +324,22 @@
internal static void SetStoreGeneratedValues<T>(this DbContext context, T entity, List<IProperty> properties, object[] values)
{
int index = 0;
var updateEntry = entity as InternalEntityEntry;

Check warning on line 327 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.
if(updateEntry == null)
{
var entry = context.Entry(entity);
updateEntry = entry.GetInfrastructure();

Check warning on line 331 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.
}

if(updateEntry != null)
{
foreach(var property in properties)
{
updateEntry.SetStoreGeneratedValue(property, values[index]);

Check warning on line 338 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.
index++;
}
if(updateEntry.EntityState == EntityState.Detached)

Check warning on line 341 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.
updateEntry.AcceptChanges();

Check warning on line 342 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.
}
else
{
Expand Down Expand Up @@ -408,10 +408,10 @@
public static int BulkSaveChanges(this DbContext dbContext, bool acceptAllChangesOnSuccess=true)
{
int rowsAffected = 0;
var stateManager = dbContext.ChangeTracker.GetPrivateFieldValue("StateManager") as StateManager;

Check warning on line 411 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.

dbContext.ChangeTracker.DetectChanges();
var entries = stateManager.GetEntriesToSave(true);

Check warning on line 414 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.

foreach (var saveEntryGroup in entries.GroupBy(o => new { o.EntityType, o.EntityState }))
{
Expand Down Expand Up @@ -466,16 +466,17 @@
{
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<string> 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<string> columnsToInsert = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnInsert().Contains(o)));
IEnumerable<string> columnstoUpdate = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnUpdate().Contains(o))).Select(o => string.Format("t.{0}=s.{0}", o));
List<string> columnsToOutput = new List<string> { "$Action", string.Format("{0}.{1}", "s", Constants.InternalId_ColumnName) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,10 @@
public async static Task<int> BulkSaveChangesAsync(this DbContext dbContext, bool acceptAllChangesOnSuccess = true)
{
int rowsAffected = 0;
var stateManager = dbContext.ChangeTracker.GetPrivateFieldValue("StateManager") as StateManager;

Check warning on line 265 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.

dbContext.ChangeTracker.DetectChanges();
var entries = stateManager.GetEntriesToSave(true);

Check warning on line 268 in N.EntityFrameworkCore.Extensions/Data/DbContextExtensionsAsync.cs

View workflow job for this annotation

GitHub Actions / build

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager is an internal API that supports the Entity Framework Core infrastructure and not subject to the same compatibility standards as public APIs. It may be changed or removed without notice in any release.

foreach (var saveEntryGroup in entries.GroupBy(o => new { o.EntityType, o.EntityState }))
{
Expand Down Expand Up @@ -320,16 +320,17 @@
{
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<string> 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<string> columnsToInsert = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnInsert().Contains(o)));
IEnumerable<string> columnstoUpdate = CommonUtil.FormatColumns(columnNames.Where(o => !options.GetIgnoreColumnsOnUpdate().Contains(o))).Select(o => string.Format("t.{0}=s.{0}", o));
List<string> columnsToOutput = new List<string> { "$Action", string.Format("{0}.{1}", "s", Constants.InternalId_ColumnName) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>7.0.13.0</Version>
<Version>7.0.13.2</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/NorthernLight1/N.EntityFrameworkCore.Extensions/</PackageProjectUrl>
<Authors>Northern25</Authors>
Expand Down