Skip to content

Commit d2eab25

Browse files
authored
fix(uniquekey): fix unique key bugs (#7)
1 parent a4bb3ae commit d2eab25

4 files changed

Lines changed: 66 additions & 63 deletions

File tree

EfCore.BulkOperations/BulkCommand.cs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,7 @@ private static EntityInfo GetEntityInfo<T>(DbContext dbContext)
3434
.GetProperties()
3535
.Select(x =>
3636
{
37-
#if NET7_0_OR_GREATER
3837
var name = x.GetColumnName();
39-
#else
40-
var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, schema);
41-
var name = x.GetColumnName(storeObjectIdentifier);
42-
#endif
4338
var refName = x.Name;
4439
var isIdentity = x.ValueGenerated == ValueGenerated.OnAddOrUpdate;
4540
var skipInsert = x.ValueGenerated == ValueGenerated.OnAddOrUpdate;
@@ -48,7 +43,7 @@ private static EntityInfo GetEntityInfo<T>(DbContext dbContext)
4843
var isPrimaryKey = x.IsPrimaryKey();
4944
var isKey = x.IsKey();
5045

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

7166
var expr = expression.Compile();
7267
var anonymousInstance = expr.Invoke(instance);
@@ -87,10 +82,10 @@ internal static List<BatchData> GenerateInsertBatches<T>(DbContext dbContext, IR
8782
BulkOption<T>? option)
8883
where T : class
8984
{
90-
if (items.Count == 0) return new List<BatchData>();
85+
if (items.Count == 0) return [];
9186

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

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

111106
tmpTable.Sql.Insert(0,
112107
@$"INSERT INTO `{info.TableName}`
@@ -134,10 +129,10 @@ internal static List<BatchData> GenerateUpdateBatches<T>(DbContext dbContext, IR
134129
BulkOption<T>? option)
135130
where T : class
136131
{
137-
if (items.Count == 0) return new List<BatchData>();
132+
if (items.Count == 0) return [];
138133
var info = GetEntityInfo<T>(dbContext);
139134

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

143138
var columns = info.Columns
@@ -153,16 +148,31 @@ internal static List<BatchData> GenerateUpdateBatches<T>(DbContext dbContext, IR
153148
.Select(rows =>
154149
{
155150
var tmpTable = ToTempTable(columns, rows, offset);
156-
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
151+
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
157152

158153
tmpTable.Sql.Insert(0,
159154
@$"UPDATE `{info.TableName}` AS tb
160155
INNER JOIN ");
161156

162-
// Auto detects unique keys or specific custom unique keys
163-
var keys = option?.UniqueKeys is not null
164-
? GetExpressionFields(option.UniqueKeys)
165-
: columns.Where(x => x.IsUniqueIndex).Select(x => x.Name);
157+
List<string> keys;
158+
if (option?.UniqueKeys is not null)
159+
{
160+
// Specific custom unique keys
161+
var uniqueKeys = GetExpressionFields(option.UniqueKeys);
162+
keys = columns
163+
.Where(x => uniqueKeys.Contains(x.RefName))
164+
.Select(x => x.Name)
165+
.ToList();
166+
}
167+
else
168+
{
169+
// Auto detects unique keys
170+
keys = columns
171+
.Where(x => x.IsUniqueIndex)
172+
.Select(x => x.Name)
173+
.ToList();
174+
}
175+
166176
keys
167177
.ForEachWithIndex((key, index) =>
168178
{
@@ -199,7 +209,7 @@ internal static List<BatchData> GenerateDeleteBatches<T>(DbContext dbContext, IR
199209
BulkOption<T>? option)
200210
where T : class
201211
{
202-
if (items.Count == 0) return new List<BatchData>();
212+
if (items.Count == 0) return [];
203213
var info = GetEntityInfo<T>(dbContext);
204214

205215
List<ColumnInfo> columns;
@@ -213,9 +223,9 @@ internal static List<BatchData> GenerateDeleteBatches<T>(DbContext dbContext, IR
213223
else
214224
{
215225
// Specific custom unique keys
216-
var keys = GetExpressionFields(option.UniqueKeys);
226+
var uniqueKeys = GetExpressionFields(option.UniqueKeys);
217227
columns = info.Columns
218-
.Where(x => keys.Contains(x.Name))
228+
.Where(x => uniqueKeys.Contains(x.RefName))
219229
.ToList();
220230
}
221231

@@ -228,7 +238,7 @@ internal static List<BatchData> GenerateDeleteBatches<T>(DbContext dbContext, IR
228238
.Select(rows =>
229239
{
230240
var tmpTable = ToTempTable(columns, rows, offset);
231-
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
241+
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
232242

233243
tmpTable.Sql.Insert(0,
234244
@$"DELETE tb
@@ -262,18 +272,18 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
262272
BulkOption<T>? option)
263273
where T : class
264274
{
265-
if (items.Count == 0) return new List<BatchData>();
275+
if (items.Count == 0) return [];
266276

267277
var info = GetEntityInfo<T>(dbContext);
268-
string[] ignoreInsertFields = Array.Empty<string>();
278+
string[] ignoreInsertFields = [];
269279
if (option?.IgnoreOnInsert is not null) ignoreInsertFields = GetExpressionFields(option.IgnoreOnInsert);
270280
var insertCols = info.Columns
271281
.Where(x => x is { SkipInsert: false }
272282
&& !ignoreInsertFields.Contains(x.RefName)
273283
)
274284
.ToList();
275285

276-
string[] ignoreUpdateFields = Array.Empty<string>();
286+
string[] ignoreUpdateFields = [];
277287
if (option?.IgnoreOnUpdate is not null) ignoreUpdateFields = GetExpressionFields(option.IgnoreOnUpdate);
278288
var updateCols = info.Columns
279289
.Where(x => x is { IsPrimaryKey: false, IsUniqueIndex: false, SkipUpdate: false }
@@ -293,7 +303,7 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
293303
.Select(rows =>
294304
{
295305
var tmpTable = ToTempTable(combineColumns, rows, offset);
296-
if (tmpTable is null) return new BatchData(new StringBuilder(), new List<SqlParameter>());
306+
if (tmpTable is null) return new BatchData(new StringBuilder(), []);
297307

298308
tmpTable.Sql.Insert(0,
299309
@$"INSERT INTO `{info.TableName}`
@@ -330,13 +340,13 @@ internal static List<BatchData> GenerateMergeBatches<T>(DbContext dbContext,
330340
where T : class
331341
{
332342
if (rows.Count == 0) return null;
333-
List<SqlParameter> parameters = new List<SqlParameter>();
343+
List<SqlParameter> parameters = [];
334344
var sql = new StringBuilder("(");
335345
sql.AppendLine();
336346
rows.ForEachWithIndex((row, rowIndex) =>
337347
{
338348
sql.Append(rowIndex == 0 ? "SELECT " : "UNION ALL SELECT ");
339-
List<SqlParameter> list = new List<SqlParameter>();
349+
List<SqlParameter> list = [];
340350
var type = row.GetType();
341351
var colIndex = 0;
342352
columns.ToList().ForEach(column =>

EfCore.BulkOperations/BulkOption.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,17 @@ 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> where T : class
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
1015
{
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-
2216
/// <summary>
2317
/// Gets or sets the batch size for bulk operations. Defaults to 200 if not specified.
2418
/// </summary>
25-
public int BatchSize { get; private set; }
19+
public int BatchSize { get; set; } = batchSize ?? 200;
2620

2721
/// <summary>
2822
/// Gets or sets an expression that identifies a property on the entity type `T` to be ignored during insert
@@ -33,7 +27,7 @@ public BulkOption(int? batchSize = null,
3327
/// new BulkOption(ignoreOnInsert: x => new { x.CreatedAt }))
3428
/// This would ignore the 'CreatedAt' property during bulk inserts.
3529
/// </example>
36-
public Expression<Func<T, object>>? IgnoreOnInsert { get; private set; }
30+
public Expression<Func<T, object>>? IgnoreOnInsert { get; set; } = ignoreOnInsert;
3731

3832
/// <summary>
3933
/// Gets or sets an expression that identifies a property on the entity type `T` to be ignored during update
@@ -44,11 +38,11 @@ public BulkOption(int? batchSize = null,
4438
/// new BulkOption(ignoreOnUpdate: x => new { x.CreatedAt }))
4539
/// This would ignore the 'CreatedAt' property during bulk updates.
4640
/// </example>
47-
public Expression<Func<T, object>>? IgnoreOnUpdate { get; private set; }
41+
public Expression<Func<T, object>>? IgnoreOnUpdate { get; set; } = ignoreOnUpdate;
4842

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

EfCore.BulkOperations/Docs/README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# EfCore.BulkOperations
22

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.
3+
EfCore.BulkOperations simplifies bulk operations like insert, update, and delete with efficient SQL queries compatible with most databases.
44

55
EfCore.BulkOperations Mapping columns from unique keys. You can configure custom column mapping if needed.
66

7+
ps. BulkMerge works with MySQL only.
8+
79
[Go to NuGet](https://www.nuget.org/packages/EfCore.BulkOperations)
810

911
---
@@ -89,15 +91,20 @@ EfCore.BulkOperations utilizes local transactions within bulk processes. If you
8991

9092

9193
```csharp
92-
var connection = _dbContext.Database.GetDbConnection();
93-
DbTransaction? transaction = null;
94+
IDbContextTransaction? transaction = null;
95+
DbConnection? connection = null;
9496
try
9597
{
96-
transaction = await connection.BeginTransactionAsync();
98+
connection = _dbContext.Database.GetDbConnection();
99+
if (connection.State != ConnectionState.Open) await connection.OpenAsync();
100+
transaction = await _dbContext.Database.BeginTransactionAsync();
101+
var dbTransaction = transaction.GetDbTransaction();
102+
97103
var insertItems = new List<Product> { new Product("Product1", 100m) };
98-
await _dbContext.BulkInsertAsync(insertItems, null, transaction);
104+
await _dbContext.BulkInsertAsync(insertItems, null, dbTransaction);
99105
var updateItems = new List<Product> { new Product("Product2", 200m) };
100-
await _dbContext.BulkUpdateAsync(updateItems, null, transaction);
106+
await _dbContext.BulkUpdateAsync(updateItems, null, dbTransaction);
107+
101108
await transaction.CommitAsync();
102109
}
103110
catch (Exception e)
@@ -106,7 +113,7 @@ catch (Exception e)
106113
}
107114
finally
108115
{
109-
if(transaction is not null) await transaction.DisposeAsync();
110-
await connection.CloseAsync();
116+
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
117+
if (transaction != null) await transaction.DisposeAsync();
111118
}
112119
```
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<Title>EFCore BulkOperations</Title>
@@ -10,23 +10,15 @@
1010
<RepositoryUrl>https://github.com/hongjs/EfCore.BulkOperations</RepositoryUrl>
1111
<RepositoryType>github</RepositoryType>
1212
<PackageTags>BulkInsert,BulkUpdate,BulkDelete,BulkMerge</PackageTags>
13-
<Version>1.1.0</Version>
13+
<Version>1.1.1</Version>
1414
<PackageReadmeFile>README.md</PackageReadmeFile>
1515
</PropertyGroup>
1616

1717
<ItemGroup>
1818
<None Include="Docs\README.md" Pack="true" PackagePath="\" />
1919
</ItemGroup>
20-
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
20+
<ItemGroup>
2121
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
2222
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
2323
</ItemGroup>
24-
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
25-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.18" />
26-
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.18" />
27-
</ItemGroup>
28-
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
29-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.29" />
30-
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.29" />
31-
</ItemGroup>
3224
</Project>

0 commit comments

Comments
 (0)