Skip to content

Commit b07b1eb

Browse files
committed
Fixed BulkUpdate/BulkInsertOrUpdate for entities with auto increment.
1 parent 8ae911a commit b07b1eb

File tree

13 files changed

+317
-121
lines changed

13 files changed

+317
-121
lines changed
Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,70 @@
1-
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
2-
3-
/// <summary>
4-
/// Bulk insert or update options for SQL Server.
5-
/// </summary>
6-
public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions
7-
{
8-
/// <inheritdoc />
9-
public IEntityPropertiesProvider? PropertiesToInsert { get; set; }
10-
11-
/// <inheritdoc />
12-
public IEntityPropertiesProvider? PropertiesToUpdate { get; set; }
13-
14-
/// <inheritdoc />
15-
public IEntityPropertiesProvider? KeyProperties { get; set; }
16-
17-
/// <inheritdoc />
18-
public List<SqlServerTableHintLimited> MergeTableHints { get; }
19-
20-
/// <inheritdoc />
21-
public SqlServerBulkOperationTempTableOptions TempTableOptions { get; }
22-
23-
/// <summary>
24-
/// Initializes new instance of <see cref="SqlServerBulkUpdateOptions"/>.
25-
/// </summary>
26-
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
27-
public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
28-
{
29-
if (optionsToInitializeFrom is not null)
30-
{
31-
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
32-
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
33-
KeyProperties = optionsToInitializeFrom.KeyProperties;
34-
}
35-
36-
if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions)
37-
{
38-
TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions);
39-
MergeTableHints = mergeOptions.MergeTableHints.ToList();
40-
}
41-
else
42-
{
43-
TempTableOptions = new SqlServerBulkOperationTempTableOptions();
44-
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
45-
}
46-
}
47-
48-
/// <summary>
49-
/// Gets the options for bulk insert into a temp table.
50-
/// </summary>
51-
public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions()
52-
{
53-
var options = new SqlServerTempTableBulkInsertOptions
54-
{
55-
PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null
56-
? null
57-
: CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties),
58-
Advanced = { UsePropertiesToInsertForTempTableCreation = true }
59-
};
60-
61-
TempTableOptions.Populate(options);
62-
63-
return options;
64-
}
65-
}
1+
using Microsoft.Data.SqlClient;
2+
3+
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
4+
5+
/// <summary>
6+
/// Bulk insert or update options for SQL Server.
7+
/// </summary>
8+
public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions
9+
{
10+
/// <inheritdoc />
11+
public IEntityPropertiesProvider? PropertiesToInsert { get; set; }
12+
13+
/// <inheritdoc />
14+
public IEntityPropertiesProvider? PropertiesToUpdate { get; set; }
15+
16+
/// <inheritdoc />
17+
public IEntityPropertiesProvider? KeyProperties { get; set; }
18+
19+
/// <inheritdoc />
20+
public List<SqlServerTableHintLimited> MergeTableHints { get; }
21+
22+
/// <inheritdoc />
23+
public SqlServerBulkOperationTempTableOptions TempTableOptions { get; }
24+
25+
/// <summary>
26+
/// Initializes new instance of <see cref="SqlServerBulkUpdateOptions"/>.
27+
/// </summary>
28+
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
29+
public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
30+
{
31+
if (optionsToInitializeFrom is not null)
32+
{
33+
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
34+
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
35+
KeyProperties = optionsToInitializeFrom.KeyProperties;
36+
}
37+
38+
if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions)
39+
{
40+
TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions);
41+
MergeTableHints = mergeOptions.MergeTableHints.ToList();
42+
}
43+
else
44+
{
45+
TempTableOptions = new SqlServerBulkOperationTempTableOptions
46+
{
47+
SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity
48+
};
49+
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Gets the options for bulk insert into a temp table.
55+
/// </summary>
56+
public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions()
57+
{
58+
var options = new SqlServerTempTableBulkInsertOptions
59+
{
60+
PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null
61+
? null
62+
: CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties),
63+
Advanced = { UsePropertiesToInsertForTempTableCreation = true }
64+
};
65+
66+
TempTableOptions.Populate(options);
67+
68+
return options;
69+
}
70+
}

src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkUpdateOptions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.Data.SqlClient;
2+
13
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
24

35
/// <summary>
@@ -36,7 +38,10 @@ public SqlServerBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom =
3638
}
3739
else
3840
{
39-
TempTableOptions = new SqlServerBulkOperationTempTableOptions();
41+
TempTableOptions = new SqlServerBulkOperationTempTableOptions
42+
{
43+
SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity
44+
};
4045
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
4146
}
4247
}

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertOrUpdateContext.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public IEntityDataReader<T> CreateReader<T>(IEnumerable<T> entities)
2323
return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties);
2424
}
2525

26-
public SqliteAutoIncrementBehavior AutoIncrementBehavior => SqliteAutoIncrementBehavior.KeepValueAsIs;
26+
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; }
2727
public SqliteConnection Connection { get; }
2828

2929
public BulkInsertOrUpdateContext(
@@ -32,12 +32,14 @@ public BulkInsertOrUpdateContext(
3232
SqliteConnection connection,
3333
IReadOnlyList<PropertyWithNavigations> keyProperties,
3434
IReadOnlyList<PropertyWithNavigations> propertiesToInsert,
35-
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate)
35+
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate,
36+
SqliteAutoIncrementBehavior autoIncrementBehavior)
3637
{
3738
_ctx = ctx;
3839
_readerFactory = factory;
3940
Connection = connection;
4041
_keyProperties = keyProperties;
42+
AutoIncrementBehavior = autoIncrementBehavior;
4143

4244
var (ownPropertiesToInsert, externalPropertiesToInsert) = propertiesToInsert.SeparateProperties();
4345
_propertiesToInsert = ownPropertiesToInsert;
@@ -75,7 +77,7 @@ public IReadOnlyList<ISqliteOwnedTypeBulkOperationContext> GetChildren(IReadOnly
7577

7678
propertiesToUpdateData.Remove(propertiesToUpdateTuple);
7779

78-
var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities);
80+
var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior);
7981
childCtx.Add(ownedTypeCtx);
8082
}
8183

@@ -100,8 +102,9 @@ public OwnedTypeBulkInsertOrUpdateContext(
100102
IReadOnlyList<PropertyWithNavigations> propertiesToInsert,
101103
IReadOnlyList<PropertyWithNavigations> propertiesToUpdate,
102104
IEntityType entityType,
103-
IEnumerable<object> entities)
104-
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate)
105+
IEnumerable<object> entities,
106+
SqliteAutoIncrementBehavior autoIncrementBehavior)
107+
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate, autoIncrementBehavior)
105108
{
106109
EntityType = entityType;
107110
Entities = entities;

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkUpdateContext.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ public IEntityDataReader<T> CreateReader<T>(IEnumerable<T> entities)
2323
return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties);
2424
}
2525

26-
public SqliteAutoIncrementBehavior AutoIncrementBehavior => SqliteAutoIncrementBehavior.KeepValueAsIs;
26+
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; }
2727

2828
public BulkUpdateContext(
2929
DbContext ctx,
3030
IEntityDataReaderFactory factory,
3131
SqliteConnection connection,
3232
IReadOnlyList<PropertyWithNavigations> keyProperties,
33-
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate)
33+
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate,
34+
SqliteAutoIncrementBehavior autoIncrementBehavior)
3435
{
3536
_ctx = ctx;
3637
_readerFactory = factory;
@@ -40,6 +41,7 @@ public BulkUpdateContext(
4041
_propertiesToUpdate = ownProperties;
4142
_externalProperties = externalProperties;
4243
Properties = ownProperties.Union(keyProperties).ToList();
44+
AutoIncrementBehavior = autoIncrementBehavior;
4345
}
4446

4547
/// <inheritdoc />
@@ -58,7 +60,7 @@ public IReadOnlyList<ISqliteOwnedTypeBulkOperationContext> GetChildren(IReadOnly
5860

5961
foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities))
6062
{
61-
var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities);
63+
var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior);
6264
childCtx.Add(ownedTypeCtx);
6365
}
6466

@@ -76,8 +78,9 @@ public OwnedTypeBulkUpdateContext(
7678
SqliteConnection sqlCon,
7779
IReadOnlyList<PropertyWithNavigations> properties,
7880
IEntityType entityType,
79-
IEnumerable<object> entities)
80-
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties)
81+
IEnumerable<object> entities,
82+
SqliteAutoIncrementBehavior autoIncrementBehavior)
83+
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties, autoIncrementBehavior)
8184
{
8285
EntityType = entityType;
8386
Entities = entities;

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOptions.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ public sealed class SqliteBulkInsertOptions : IBulkInsertOptions
2020
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
2121
public SqliteBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null)
2222
{
23-
if (optionsToInitializeFrom is null)
24-
{
25-
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;
26-
}
27-
else
23+
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;
24+
25+
if (optionsToInitializeFrom is not null)
2826
{
2927
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
3028

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOrUpdateOptions.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,16 @@ public class SqliteBulkInsertOrUpdateOptions : ISqliteBulkInsertOrUpdateOptions
2323
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
2424
public SqliteBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
2525
{
26-
if (optionsToInitializeFrom is null)
27-
{
28-
AutoIncrementBehavior = SqliteAutoIncrementBehavior.KeepValueAsIs;
29-
return;
30-
}
26+
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;
3127

32-
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
33-
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
34-
KeyProperties = optionsToInitializeFrom.KeyProperties;
28+
if (optionsToInitializeFrom is not null)
29+
{
30+
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
31+
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
32+
KeyProperties = optionsToInitializeFrom.KeyProperties;
3533

36-
if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions)
37-
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
34+
if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions)
35+
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
36+
}
3837
}
39-
}
38+
}

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,15 @@ public async Task<int> BulkUpdateAsync<T>(
143143

144144
var entityType = _ctx.Model.GetEntityType(typeof(T));
145145

146+
if (options is not SqliteBulkUpdateOptions sqliteOptions)
147+
sqliteOptions = new SqliteBulkUpdateOptions(options);
148+
146149
var ctx = new BulkUpdateContext(_ctx,
147150
_ctx.GetService<IEntityDataReaderFactory>(),
148151
(SqliteConnection)_ctx.Database.GetDbConnection(),
149-
options.KeyProperties.DetermineKeyProperties(entityType, true),
150-
options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null));
152+
sqliteOptions.KeyProperties.DetermineKeyProperties(entityType, true),
153+
sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null),
154+
sqliteOptions.AutoIncrementBehavior);
151155
var tableName = entityType.GetTableName()
152156
?? throw new Exception($"The entity '{entityType.Name}' has no table name.");
153157

@@ -173,7 +177,8 @@ public async Task<int> BulkInsertOrUpdateAsync<T>(
173177
(SqliteConnection)_ctx.Database.GetDbConnection(),
174178
sqliteOptions.KeyProperties.DetermineKeyProperties(entityType, true),
175179
sqliteOptions.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null),
176-
sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true));
180+
sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true),
181+
sqliteOptions.AutoIncrementBehavior);
177182
var tableName = entityType.GetTableName()
178183
?? throw new Exception($"The entity '{entityType.Name}' has no table name.");
179184

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkUpdateOptions.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,27 @@ public class SqliteBulkUpdateOptions : IBulkUpdateOptions
1111
/// <inheritdoc />
1212
public IEntityPropertiesProvider? KeyProperties { get; set; }
1313

14+
/// <summary>
15+
/// Behavior for auto-increment columns.
16+
/// Default is <see cref="SqliteAutoIncrementBehavior.SetZeroToNull"/>
17+
/// </summary>
18+
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; }
19+
1420
/// <summary>
1521
/// Initializes new instance of <see cref="SqliteBulkUpdateOptions"/>.
1622
/// </summary>
1723
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
1824
public SqliteBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null)
1925
{
20-
if (optionsToInitializeFrom is null)
21-
return;
26+
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;
27+
28+
if (optionsToInitializeFrom is not null)
29+
{
30+
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
31+
KeyProperties = optionsToInitializeFrom.KeyProperties;
2232

23-
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
24-
KeyProperties = optionsToInitializeFrom.KeyProperties;
33+
if (optionsToInitializeFrom is SqliteBulkUpdateOptions sqliteOptions)
34+
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
35+
}
2536
}
26-
}
37+
}

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteCommandBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public override string GetStatement(
222222

223223
try
224224
{
225-
GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert);
225+
GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert.Union(_keyProperties).ToList());
226226

227227
sb.AppendLine()
228228
.Append("\tON CONFLICT(");

0 commit comments

Comments
 (0)