Skip to content

Commit 7304f75

Browse files
committed
feat(version): support dotnet 6, 7, 8
1 parent 63b4ea6 commit 7304f75

9 files changed

Lines changed: 178 additions & 45 deletions

File tree

EfCore.BulkOperations.Test/EfCoreTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public EfCoreTest()
1919
.Options;
2020
_dbContext = new ApplicationDbContext(dbContextOptions);
2121
}
22-
22+
2323

2424
[Fact]
2525
public void Should_GenerateInsertScript()
@@ -37,7 +37,7 @@ ORDER BY zRowNo
3737

3838
// Act
3939
var batches = BulkCommand.GenerateInsertBatches(_dbContext, items, null);
40-
40+
4141
// Assert
4242
Assert.Single(batches);
4343
Assert.Equal(4, batches.First().Parameters.Count);

EfCore.BulkOperations.Test/Setup/ApplicationDbContext.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
namespace EfCore.BulkOperations.Test.Setup;
44

5-
public class ApplicationDbContext:DbContext
5+
public class ApplicationDbContext : DbContext
66
{
7-
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
7+
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
88
: base(options)
99
{
1010
}
11-
11+
1212
public DbSet<Product> EquityTransactions => Set<Product>();
13-
13+
1414
protected override void OnModelCreating(ModelBuilder modelBuilder)
1515
{
1616
base.OnModelCreating(modelBuilder);
17-
17+
1818
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductMap).Assembly);
1919

2020
}

EfCore.BulkOperations.Test/Setup/ProductMap.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33

44
namespace EfCore.BulkOperations.Test.Setup;
55

6-
public class ProductMap: IEntityTypeConfiguration<Product>
6+
public class ProductMap : IEntityTypeConfiguration<Product>
77
{
88
public void Configure(EntityTypeBuilder<Product> builder)
99
{
1010
builder.HasKey(i => i.Id);
1111

1212
builder.HasIndex(x => x.Id)
1313
.IsUnique();
14-
14+
1515
builder.Property(x => x.Id)
1616
.ValueGeneratedOnAdd();
1717

1818
builder.Property(x => x.Name)
1919
.HasMaxLength(100)
2020
.IsRequired();
21-
21+
2222
builder.Property(x => x.Price)
2323
.HasPrecision(19, 6)
2424
.IsRequired();

EfCore.BulkOperations/BulkCommand.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ private static EntityInfo GetEntityInfo<T>(DbContext dbContext)
3434
.GetProperties()
3535
.Select(x =>
3636
{
37+
#if NET7_0_OR_GREATER
3738
var name = x.GetColumnName();
39+
#else
40+
var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, schema);
41+
var name = x.GetColumnName(storeObjectIdentifier);
42+
#endif
3843
var refName = x.Name;
3944
var isIdentity = x.ValueGenerated == ValueGenerated.OnAddOrUpdate;
4045
var skipInsert = x.ValueGenerated == ValueGenerated.OnAddOrUpdate;
@@ -43,7 +48,7 @@ private static EntityInfo GetEntityInfo<T>(DbContext dbContext)
4348
var isPrimaryKey = x.IsPrimaryKey();
4449
var isKey = x.IsKey();
4550

46-
return new ColumnInfo(name, refName, isPrimaryKey, isUniqueIndex, isKey, isIdentity, skipInsert,
51+
return new ColumnInfo(name!, refName, isPrimaryKey, isUniqueIndex, isKey, isIdentity, skipInsert,
4752
skipUpdate)
4853
{
4954
ValueConverter = x.GetValueConverter()
@@ -59,9 +64,9 @@ private static EntityInfo GetEntityInfo<T>(DbContext dbContext)
5964
/// </summary>
6065
private static string[] GetExpressionFields<T>(Expression<Func<T, object>>? expression)
6166
{
62-
if (expression is null) return [];
67+
if (expression is null) return Array.Empty<string>();
6368
var instance = JsonSerializer.Deserialize<T>("{}");
64-
if (instance is null) return [];
69+
if (instance is null) return Array.Empty<string>();
6570

6671
var expr = expression.Compile();
6772
var anonymousInstance = expr.Invoke(instance);
@@ -82,10 +87,10 @@ internal static List<BatchData> GenerateInsertBatches<T>(DbContext dbContext, IR
8287
BulkOption<T>? option)
8388
where T : class
8489
{
85-
if (items.Count == 0) return [];
90+
if (items.Count == 0) return new List<BatchData>();
8691

8792
var info = GetEntityInfo<T>(dbContext);
88-
string[] ignoreFields = [];
93+
string[] ignoreFields = Array.Empty<string>();
8994
if (option?.IgnoreOnInsert is not null) ignoreFields = GetExpressionFields(option.IgnoreOnInsert);
9095

9196
var columns = info.Columns
@@ -101,7 +106,7 @@ internal static List<BatchData> GenerateInsertBatches<T>(DbContext dbContext, IR
101106
.Select(rows =>
102107
{
103108
var tmpTable = ToTempTable(columns, rows, offset);
104-
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
109+
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
105110

106111
tmpTable.Sql.Insert(0,
107112
@$"INSERT INTO `{info.TableName}`
@@ -129,10 +134,10 @@ internal static List<BatchData> GenerateUpdateBatches<T>(DbContext dbContext, IR
129134
BulkOption<T>? option)
130135
where T : class
131136
{
132-
if (items.Count == 0) return [];
137+
if (items.Count == 0) return new List<BatchData>();
133138
var info = GetEntityInfo<T>(dbContext);
134139

135-
string[] ignoreFields = [];
140+
string[] ignoreFields = Array.Empty<string>();
136141
if (option?.IgnoreOnUpdate is not null) ignoreFields = GetExpressionFields(option.IgnoreOnUpdate);
137142

138143
var columns = info.Columns
@@ -148,7 +153,7 @@ internal static List<BatchData> GenerateUpdateBatches<T>(DbContext dbContext, IR
148153
.Select(rows =>
149154
{
150155
var tmpTable = ToTempTable(columns, rows, offset);
151-
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
156+
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
152157

153158
tmpTable.Sql.Insert(0,
154159
@$"UPDATE `{info.TableName}` AS tb
@@ -194,7 +199,7 @@ internal static List<BatchData> GenerateDeleteBatches<T>(DbContext dbContext, IR
194199
BulkOption<T>? option)
195200
where T : class
196201
{
197-
if (items.Count == 0) return [];
202+
if (items.Count == 0) return new List<BatchData>();
198203
var info = GetEntityInfo<T>(dbContext);
199204

200205
List<ColumnInfo> columns;
@@ -223,7 +228,7 @@ internal static List<BatchData> GenerateDeleteBatches<T>(DbContext dbContext, IR
223228
.Select(rows =>
224229
{
225230
var tmpTable = ToTempTable(columns, rows, offset);
226-
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
231+
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
227232

228233
tmpTable.Sql.Insert(0,
229234
@$"DELETE tb
@@ -257,18 +262,18 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
257262
BulkOption<T>? option)
258263
where T : class
259264
{
260-
if (items.Count == 0) return [];
265+
if (items.Count == 0) return new List<BatchData>();
261266

262267
var info = GetEntityInfo<T>(dbContext);
263-
string[] ignoreInsertFields = [];
268+
string[] ignoreInsertFields = Array.Empty<string>();
264269
if (option?.IgnoreOnInsert is not null) ignoreInsertFields = GetExpressionFields(option.IgnoreOnInsert);
265270
var insertCols = info.Columns
266271
.Where(x => x is { SkipInsert: false }
267272
&& !ignoreInsertFields.Contains(x.RefName)
268273
)
269274
.ToList();
270275

271-
string[] ignoreUpdateFields = [];
276+
string[] ignoreUpdateFields = Array.Empty<string>();
272277
if (option?.IgnoreOnUpdate is not null) ignoreUpdateFields = GetExpressionFields(option.IgnoreOnUpdate);
273278
var updateCols = info.Columns
274279
.Where(x => x is { IsPrimaryKey: false, IsUniqueIndex: false, SkipUpdate: false }
@@ -288,7 +293,7 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
288293
.Select(rows =>
289294
{
290295
var tmpTable = ToTempTable(combineColumns, rows, offset);
291-
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
296+
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
292297

293298
tmpTable.Sql.Insert(0,
294299
@$"INSERT INTO `{info.TableName}`
@@ -325,13 +330,13 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
325330
where T : class
326331
{
327332
if (rows.Count == 0) return null;
328-
List<SqlParameter> parameters = [];
333+
List<SqlParameter> parameters = new List<SqlParameter>();
329334
var sql = new StringBuilder("(");
330335
sql.AppendLine();
331336
rows.ForEachWithIndex((row, rowIndex) =>
332337
{
333338
sql.Append(rowIndex == 0 ? "SELECT " : "UNION ALL SELECT ");
334-
List<SqlParameter> list = [];
339+
List<SqlParameter> list = new List<SqlParameter>();
335340
var type = row.GetType();
336341
var colIndex = 0;
337342
columns.ToList().ForEach(column =>

EfCore.BulkOperations/BulkOption.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@ namespace EfCore.BulkOperations;
66
/// <summary>
77
/// The configurable options for bulk operations (insert/update) on entities using EfCoreBulkUtils.
88
/// </summary>
9-
public class BulkOption<T>(
10-
int? batchSize = null,
11-
Expression<Func<T, object>>? ignoreOnInsert = null,
12-
Expression<Func<T, object>>? ignoreOnUpdate = null,
13-
Expression<Func<T, object>>? uniqueKeys = null
14-
) where T : class
9+
public class BulkOption<T> where T : class
1510
{
11+
public BulkOption(int? batchSize = null,
12+
Expression<Func<T, object>>? ignoreOnInsert = null,
13+
Expression<Func<T, object>>? ignoreOnUpdate = null,
14+
Expression<Func<T, object>>? uniqueKeys = null)
15+
{
16+
BatchSize = batchSize ?? 200;
17+
IgnoreOnInsert = ignoreOnInsert;
18+
IgnoreOnUpdate = ignoreOnUpdate;
19+
UniqueKeys = uniqueKeys;
20+
}
21+
1622
/// <summary>
1723
/// Gets or sets the batch size for bulk operations. Defaults to 200 if not specified.
1824
/// </summary>
19-
public int BatchSize { get; set; } = batchSize ?? 200;
25+
public int BatchSize { get; private set; }
2026

2127
/// <summary>
2228
/// Gets or sets an expression that identifies a property on the entity type `T` to be ignored during insert
@@ -27,7 +33,7 @@ public class BulkOption<T>(
2733
/// new BulkOption(ignoreOnInsert: x => new { x.CreatedAt }))
2834
/// This would ignore the 'CreatedAt' property during bulk inserts.
2935
/// </example>
30-
public Expression<Func<T, object>>? IgnoreOnInsert { get; set; } = ignoreOnInsert;
36+
public Expression<Func<T, object>>? IgnoreOnInsert { get; private set; }
3137

3238
/// <summary>
3339
/// Gets or sets an expression that identifies a property on the entity type `T` to be ignored during update
@@ -38,11 +44,11 @@ public class BulkOption<T>(
3844
/// new BulkOption(ignoreOnUpdate: x => new { x.CreatedAt }))
3945
/// This would ignore the 'CreatedAt' property during bulk updates.
4046
/// </example>
41-
public Expression<Func<T, object>>? IgnoreOnUpdate { get; set; } = ignoreOnUpdate;
47+
public Expression<Func<T, object>>? IgnoreOnUpdate { get; private set; }
4248

4349
/// <summary>
4450
/// Gets or sets an expression that identifies a property on the entity type 'T' as a custom unique key for update or
4551
/// delete operations.
4652
/// </summary>
47-
public Expression<Func<T, object>>? UniqueKeys { get; set; } = uniqueKeys;
53+
public Expression<Func<T, object>>? UniqueKeys { get; private set; }
4854
}

EfCore.BulkOperations/DbContextExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace EfCore.BulkOperations;
55

66
public static class DbContextExtensions
77
{
8-
/// <summary>
8+
/// <summary>
99
/// Performs an asynchronous bulk insert operation.
1010
/// </summary>
1111
/// <param name="dbContext">The DbContext instance to use for the operation.</param>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# EfCore.BulkOperations
2+
3+
EfCore.BulkOperations enables bulk operations like BulkInsert, BulkUpdate, BulkDelete, and BulkMerge. While most operations use simple SQL queries, BulkMerge requires a more complex approach.
4+
5+
EfCore.BulkOperations Mapping columns from unique keys. You can configure custom column mapping if needed.
6+
7+
[Go to NuGet](https://www.nuget.org/packages/EfCore.BulkOperations)
8+
9+
---
10+
11+
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=hongjs_EfCore.BulkOperations&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=hongjs_EfCore.BulkOperations) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=hongjs_EfCore.BulkOperations&metric=coverage)](https://sonarcloud.io/summary/new_code?id=hongjs_EfCore.BulkOperations) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=hongjs_EfCore.BulkOperations&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=hongjs_EfCore.BulkOperations) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=hongjs_EfCore.BulkOperations&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=hongjs_EfCore.BulkOperations)
12+
13+
14+
15+
## Example
16+
17+
### Bulk Insert
18+
```csharp
19+
var items = new List<Product> { new Product("Product1", 100m) };
20+
await _dbContext.BulkInsertAsync(items);
21+
```
22+
23+
```csharp
24+
var items = new List<Product> { new Product("Product1", 100m) };
25+
await _dbContext.BulkInsertAsync(
26+
items,
27+
option => { option.IgnoreOnInsert = x => new { x.CreatedAt }; }
28+
);
29+
```
30+
31+
### Bulk Update
32+
```csharp
33+
var items = new List<Product> { new Product("Product1", 100m) };
34+
await _dbContext.BulkUpdateAsync(items);
35+
```
36+
37+
```csharp
38+
var items = new List<Product> { new Product("Product1", 100m) };
39+
await _dbContext.BulkUpdateAsync(
40+
items,
41+
option => { option.IgnoreOnUpdate = x => new { x.CreatedAt }; }
42+
);
43+
```
44+
45+
```csharp
46+
var items = new List<Product> { new Product("Product1", 100m) };
47+
await _dbContext.BulkUpdateAsync(
48+
items,
49+
option => { option.UniqueKeys = x => new { x.Id }; }
50+
);
51+
```
52+
53+
### Bulk Delete
54+
```csharp
55+
var items = new List<Product> { new Product("Product1", 100m) };
56+
await _dbContext.BulkDeleteAsync(items);
57+
```
58+
```csharp
59+
var items = new List<Product> { new Product("Product1", 100m) };
60+
await _dbContext.BulkDeleteAsync(
61+
items,
62+
option => { option.UniqueKeys = x => new { x.Id }; }
63+
);
64+
```
65+
66+
67+
68+
### Bulk Merge (MySql only)
69+
70+
Do not use BulkMergeAsync with other databases; it relies on a MySQL-specific query.
71+
72+
```csharp
73+
var items = new List<Product> { new Product("Product1", 100m) };
74+
await _dbContext.BulkMergeAsync(items);
75+
```
76+
77+
```csharp
78+
await _dbContext.BulkMergeAsync(
79+
items,
80+
option =>
81+
{
82+
option.IgnoreOnInsert = x => new { x.CreatedAt };
83+
option.IgnoreOnUpdate = x => new { x.CreatedAt };
84+
});
85+
```
86+
87+
### Working with Global Transaction
88+
EfCore.BulkOperations utilizes local transactions within bulk processes. If you require manual transaction control, you can pass an existing transaction into the bulk process.
89+
90+
91+
```csharp
92+
var connection = _dbContext.Database.GetDbConnection();
93+
DbTransaction? transaction = null;
94+
try
95+
{
96+
transaction = await connection.BeginTransactionAsync();
97+
var insertItems = new List<Product> { new Product("Product1", 100m) };
98+
await _dbContext.BulkInsertAsync(insertItems, null, transaction);
99+
var updateItems = new List<Product> { new Product("Product2", 200m) };
100+
await _dbContext.BulkUpdateAsync(updateItems, null, transaction);
101+
await transaction.CommitAsync();
102+
}
103+
catch (Exception e)
104+
{
105+
if (transaction is not null) await transaction.RollbackAsync();
106+
}
107+
finally
108+
{
109+
if(transaction is not null) await transaction.DisposeAsync();
110+
await connection.CloseAsync();
111+
}
112+
```

0 commit comments

Comments
 (0)