Skip to content
11 changes: 9 additions & 2 deletions EfCore.BulkOperations.API/Repositories/ProductRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ public async Task SyncDataThenCommit(List<Product> list1, List<Product> list2)
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

await dbContext.BulkInsertAsync(list1, null, dbTransaction);
await dbContext.BulkInsertAsync(
list1,
option =>
{
option.BatchSize = 1000;
option.CommandTimeout = 60;
},
dbTransaction);
await dbContext.BulkInsertAsync(list2, null, dbTransaction);

await transaction.CommitAsync();
Expand All @@ -91,7 +98,7 @@ public async Task SyncDataThenRollback(Product item1, List<Product> list2, List<
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

dbContext.Products.Add(item1);
await dbContext.Products.AddAsync(item1);
await dbContext.SaveChangesAsync();
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
await dbContext.BulkInsertAsync(list3, null, dbTransaction);
Expand Down
16 changes: 11 additions & 5 deletions EfCore.BulkOperations/BulkOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

namespace EfCore.BulkOperations;


/// <summary>
/// The configurable options for bulk operations (insert/update) on entities using EfCoreBulkUtils.
/// </summary>
public class BulkOption<T>(
int? batchSize = null,
Expression<Func<T, object>>? ignoreOnInsert = null,
Expression<Func<T, object>>? ignoreOnUpdate = null,
Expression<Func<T, object>>? uniqueKeys = null
int? batchSize = null,
int? commandTimeout = null,
Expression<Func<T, object>>? ignoreOnInsert = null,
Expression<Func<T, object>>? ignoreOnUpdate = null,
Expression<Func<T, object>>? uniqueKeys = null
) where T : class
{
/// <summary>
/// Gets or sets the batch size for bulk operations. Defaults to 200 if not specified.
/// </summary>
public int BatchSize { get; set; } = batchSize ?? 200;

/// <summary>
/// Gets or sets the wait time (in seconds) before terminating the attempt to execute the command and generating an
/// error.
/// </summary>
public int? CommandTimeout { get; set; } = commandTimeout;

/// <summary>
/// Gets or sets an expression that identifies a property on the entity type `T` to be ignored during insert
/// operations.
Expand Down
11 changes: 8 additions & 3 deletions EfCore.BulkOperations/Docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ await _dbContext.BulkInsertAsync(items);
var items = new List<Product> { new Product("Product1", 100m) };
await _dbContext.BulkInsertAsync(
items,
option => { option.IgnoreOnInsert = x => new { x.CreatedAt }; }
option =>
{
option.BatchSize = 1000;
option.CommandTimeout = 120;
option.IgnoreOnInsert = x => new { x.CreatedAt };
}
);
```

Expand Down Expand Up @@ -102,7 +107,7 @@ try
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

dbContext.Products.Add(item1);
await dbContext.Products.AddAsync(item1);
await dbContext.SaveChangesAsync();
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
await dbContext.BulkInsertAsync(list3, null, dbTransaction);
Expand All @@ -118,4 +123,4 @@ finally
{
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
}
```
```
8 changes: 4 additions & 4 deletions EfCore.BulkOperations/EfCore.BulkOperations.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
<RepositoryUrl>https://github.com/hongjs/EfCore.BulkOperations</RepositoryUrl>
<RepositoryType>github</RepositoryType>
<PackageTags>BulkInsert,BulkUpdate,BulkDelete,BulkMerge</PackageTags>
<Version>1.2.0</Version>
<Version>1.3.0</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/hongjs/EfCore.BulkOperations</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/hongjs/EfCore.BulkOperations?tab=MIT-1-ov-file</PackageLicenseUrl>
</PropertyGroup>

<ItemGroup>
<None Include="Docs\README.md" Pack="true" PackagePath="\" />
<None Include="Docs\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4"/>
</ItemGroup>
</Project>
19 changes: 13 additions & 6 deletions EfCore.BulkOperations/EfCoreBulkUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

namespace EfCore.BulkOperations;


/// <summary>
/// Provides a set of static methods for performing efficient bulk operations (insert, update, delete, merge)
/// on entities using Entity Framework Core.
Expand All @@ -32,7 +31,7 @@ internal async static Task<int> BulkInsertAsync<T>(DbContext dbContext,
optionFactory?.Invoke(option);

var batches = BulkCommand.GenerateInsertBatches(dbContext, items, option);
await BulkExecuteAsync(dbContext, batches, transaction, cancellationToken);
await BulkExecuteAsync(dbContext, batches, option.CommandTimeout, transaction, cancellationToken);
return batches.Sum(x => x.SuccessCount) ?? 0;
}

Expand All @@ -56,7 +55,7 @@ internal async static Task<int> BulkUpdateAsync<T>(DbContext dbContext,
optionFactory?.Invoke(option);

var batches = BulkCommand.GenerateUpdateBatches(dbContext, items, option);
await BulkExecuteAsync(dbContext, batches, transaction, cancellationToken);
await BulkExecuteAsync(dbContext, batches, option.CommandTimeout, transaction, cancellationToken);
return batches.Sum(x => x.SuccessCount) ?? 0;
}

Expand All @@ -80,7 +79,7 @@ internal async static Task<int> BulkDeleteAsync<T>(DbContext dbContext,
optionFactory?.Invoke(option);

var batches = BulkCommand.GenerateDeleteBatches(dbContext, items, option);
await BulkExecuteAsync(dbContext, batches, transaction, cancellationToken);
await BulkExecuteAsync(dbContext, batches, option.CommandTimeout, transaction, cancellationToken);
return batches.Sum(x => x.SuccessCount) ?? 0;
}

Expand All @@ -106,7 +105,7 @@ internal async static Task<int> BulkMergeAsync<T>(DbContext dbContext,
optionFactory?.Invoke(option);

var batches = BulkCommand.GenerateMergeBatches(dbContext, items, option);
await BulkExecuteAsync(dbContext, batches, transaction, cancellationToken);
await BulkExecuteAsync(dbContext, batches, option.CommandTimeout, transaction, cancellationToken);
return batches.Sum(x => x.SuccessCount) ?? 0;
}

Expand All @@ -119,6 +118,7 @@ internal async static Task<int> BulkMergeAsync<T>(DbContext dbContext,
private static async Task BulkExecuteAsync(
DbContext dbContext,
IEnumerable<BatchData> batches,
int? commandTimeout = null,
DbTransaction? externalTransaction = null,
CancellationToken? cancellationToken = null)
{
Expand All @@ -128,7 +128,7 @@ private static async Task BulkExecuteAsync(
try
{
foreach (var batch in batches)
await ExecuteBatchDataAsync(batch, connection, transaction, cancellationToken);
await ExecuteBatchDataAsync(batch, connection, commandTimeout, transaction, cancellationToken);

if (externalTransaction is null) await transaction.CommitAsync(cancellationToken ?? default);
}
Expand All @@ -150,16 +150,23 @@ private static async Task BulkExecuteAsync(
/// <summary>
/// Executes a single SQL batch asynchronously.
/// </summary>
/// <param name="batch">DbConnection</param>
/// <param name="connection">DbConnection</param>
/// <param name="commandTimeout">commandTimeout</param>
/// <param name="dbTransaction">dbTransaction</param>
/// <param name="cancellationToken">cancellationToken</param>
private static async Task ExecuteBatchDataAsync(
BatchData batch,
DbConnection connection,
int? commandTimeout,
DbTransaction? dbTransaction,
CancellationToken? cancellationToken = null)
{
await using var command = connection.CreateCommand();
if (command.Connection is null) throw new ArgumentException("Command.Connection is null");
if (dbTransaction is not null) command.Transaction = dbTransaction;
command.CommandText = batch.Sql.ToString();
if (commandTimeout is not null) command.CommandTimeout = commandTimeout.Value;

batch.Parameters.ToList().ForEach(p =>
{
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ await _dbContext.BulkInsertAsync(items);
var items = new List<Product> { new Product("Product1", 100m) };
await _dbContext.BulkInsertAsync(
items,
option => { option.IgnoreOnInsert = x => new { x.CreatedAt }; }
option =>
{
option.BatchSize = 1000;
option.CommandTimeout = 120;
option.IgnoreOnInsert = x => new { x.CreatedAt };
}
);
```

Expand Down Expand Up @@ -102,12 +107,12 @@ try
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

dbContext.Products.Add(item1);
await dbContext.Products.AddAsync (item1);
await dbContext.SaveChangesAsync();
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
await dbContext.BulkInsertAsync(list3, null, dbTransaction);

await transaction.CommitAsync();
throw new DbUpdateException("Internal Server Error");
}
catch (Exception)
{
Expand All @@ -118,4 +123,4 @@ finally
{
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
}
```
```