Skip to content

Commit

Permalink
Fixed BulkUpdate/BulkInsertOrUpdate for entities with auto increment.
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Dec 4, 2022
1 parent 8ae911a commit b07b1eb
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 121 deletions.
Original file line number Diff line number Diff line change
@@ -1,65 +1,70 @@
namespace Thinktecture.EntityFrameworkCore.BulkOperations;

/// <summary>
/// Bulk insert or update options for SQL Server.
/// </summary>
public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions
{
/// <inheritdoc />
public IEntityPropertiesProvider? PropertiesToInsert { get; set; }

/// <inheritdoc />
public IEntityPropertiesProvider? PropertiesToUpdate { get; set; }

/// <inheritdoc />
public IEntityPropertiesProvider? KeyProperties { get; set; }

/// <inheritdoc />
public List<SqlServerTableHintLimited> MergeTableHints { get; }

/// <inheritdoc />
public SqlServerBulkOperationTempTableOptions TempTableOptions { get; }

/// <summary>
/// Initializes new instance of <see cref="SqlServerBulkUpdateOptions"/>.
/// </summary>
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
{
if (optionsToInitializeFrom is not null)
{
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;
}

if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions)
{
TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions);
MergeTableHints = mergeOptions.MergeTableHints.ToList();
}
else
{
TempTableOptions = new SqlServerBulkOperationTempTableOptions();
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
}
}

/// <summary>
/// Gets the options for bulk insert into a temp table.
/// </summary>
public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions()
{
var options = new SqlServerTempTableBulkInsertOptions
{
PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null
? null
: CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties),
Advanced = { UsePropertiesToInsertForTempTableCreation = true }
};

TempTableOptions.Populate(options);

return options;
}
}
using Microsoft.Data.SqlClient;

namespace Thinktecture.EntityFrameworkCore.BulkOperations;

/// <summary>
/// Bulk insert or update options for SQL Server.
/// </summary>
public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions
{
/// <inheritdoc />
public IEntityPropertiesProvider? PropertiesToInsert { get; set; }

/// <inheritdoc />
public IEntityPropertiesProvider? PropertiesToUpdate { get; set; }

/// <inheritdoc />
public IEntityPropertiesProvider? KeyProperties { get; set; }

/// <inheritdoc />
public List<SqlServerTableHintLimited> MergeTableHints { get; }

/// <inheritdoc />
public SqlServerBulkOperationTempTableOptions TempTableOptions { get; }

/// <summary>
/// Initializes new instance of <see cref="SqlServerBulkUpdateOptions"/>.
/// </summary>
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
{
if (optionsToInitializeFrom is not null)
{
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;
}

if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions)
{
TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions);
MergeTableHints = mergeOptions.MergeTableHints.ToList();
}
else
{
TempTableOptions = new SqlServerBulkOperationTempTableOptions
{
SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity
};
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
}
}

/// <summary>
/// Gets the options for bulk insert into a temp table.
/// </summary>
public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions()
{
var options = new SqlServerTempTableBulkInsertOptions
{
PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null
? null
: CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties),
Advanced = { UsePropertiesToInsertForTempTableCreation = true }
};

TempTableOptions.Populate(options);

return options;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Data.SqlClient;

namespace Thinktecture.EntityFrameworkCore.BulkOperations;

/// <summary>
Expand Down Expand Up @@ -36,7 +38,10 @@ public SqlServerBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom =
}
else
{
TempTableOptions = new SqlServerBulkOperationTempTableOptions();
TempTableOptions = new SqlServerBulkOperationTempTableOptions
{
SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity
};
MergeTableHints = new List<SqlServerTableHintLimited> { SqlServerTableHintLimited.HoldLock };
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public IEntityDataReader<T> CreateReader<T>(IEnumerable<T> entities)
return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties);
}

public SqliteAutoIncrementBehavior AutoIncrementBehavior => SqliteAutoIncrementBehavior.KeepValueAsIs;
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; }
public SqliteConnection Connection { get; }

public BulkInsertOrUpdateContext(
Expand All @@ -32,12 +32,14 @@ public BulkInsertOrUpdateContext(
SqliteConnection connection,
IReadOnlyList<PropertyWithNavigations> keyProperties,
IReadOnlyList<PropertyWithNavigations> propertiesToInsert,
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate)
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate,
SqliteAutoIncrementBehavior autoIncrementBehavior)
{
_ctx = ctx;
_readerFactory = factory;
Connection = connection;
_keyProperties = keyProperties;
AutoIncrementBehavior = autoIncrementBehavior;

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

propertiesToUpdateData.Remove(propertiesToUpdateTuple);

var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities);
var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior);
childCtx.Add(ownedTypeCtx);
}

Expand All @@ -100,8 +102,9 @@ public OwnedTypeBulkInsertOrUpdateContext(
IReadOnlyList<PropertyWithNavigations> propertiesToInsert,
IReadOnlyList<PropertyWithNavigations> propertiesToUpdate,
IEntityType entityType,
IEnumerable<object> entities)
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate)
IEnumerable<object> entities,
SqliteAutoIncrementBehavior autoIncrementBehavior)
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate, autoIncrementBehavior)
{
EntityType = entityType;
Entities = entities;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ public IEntityDataReader<T> CreateReader<T>(IEnumerable<T> entities)
return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties);
}

public SqliteAutoIncrementBehavior AutoIncrementBehavior => SqliteAutoIncrementBehavior.KeepValueAsIs;
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; }

public BulkUpdateContext(
DbContext ctx,
IEntityDataReaderFactory factory,
SqliteConnection connection,
IReadOnlyList<PropertyWithNavigations> keyProperties,
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate)
IReadOnlyList<PropertyWithNavigations> propertiesForUpdate,
SqliteAutoIncrementBehavior autoIncrementBehavior)
{
_ctx = ctx;
_readerFactory = factory;
Expand All @@ -40,6 +41,7 @@ public BulkUpdateContext(
_propertiesToUpdate = ownProperties;
_externalProperties = externalProperties;
Properties = ownProperties.Union(keyProperties).ToList();
AutoIncrementBehavior = autoIncrementBehavior;
}

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

foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities))
{
var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities);
var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior);
childCtx.Add(ownedTypeCtx);
}

Expand All @@ -76,8 +78,9 @@ public OwnedTypeBulkUpdateContext(
SqliteConnection sqlCon,
IReadOnlyList<PropertyWithNavigations> properties,
IEntityType entityType,
IEnumerable<object> entities)
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties)
IEnumerable<object> entities,
SqliteAutoIncrementBehavior autoIncrementBehavior)
: base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties, autoIncrementBehavior)
{
EntityType = entityType;
Entities = entities;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ public sealed class SqliteBulkInsertOptions : IBulkInsertOptions
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
public SqliteBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null)
{
if (optionsToInitializeFrom is null)
{
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;
}
else
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;

if (optionsToInitializeFrom is not null)
{
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,16 @@ public class SqliteBulkInsertOrUpdateOptions : ISqliteBulkInsertOrUpdateOptions
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
public SqliteBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null)
{
if (optionsToInitializeFrom is null)
{
AutoIncrementBehavior = SqliteAutoIncrementBehavior.KeepValueAsIs;
return;
}
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;

PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;
if (optionsToInitializeFrom is not null)
{
PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert;
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;

if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions)
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions)
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,15 @@ public async Task<int> BulkUpdateAsync<T>(

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

if (options is not SqliteBulkUpdateOptions sqliteOptions)
sqliteOptions = new SqliteBulkUpdateOptions(options);

var ctx = new BulkUpdateContext(_ctx,
_ctx.GetService<IEntityDataReaderFactory>(),
(SqliteConnection)_ctx.Database.GetDbConnection(),
options.KeyProperties.DetermineKeyProperties(entityType, true),
options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null));
sqliteOptions.KeyProperties.DetermineKeyProperties(entityType, true),
sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null),
sqliteOptions.AutoIncrementBehavior);
var tableName = entityType.GetTableName()
?? throw new Exception($"The entity '{entityType.Name}' has no table name.");

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,27 @@ public class SqliteBulkUpdateOptions : IBulkUpdateOptions
/// <inheritdoc />
public IEntityPropertiesProvider? KeyProperties { get; set; }

/// <summary>
/// Behavior for auto-increment columns.
/// Default is <see cref="SqliteAutoIncrementBehavior.SetZeroToNull"/>
/// </summary>
public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; }

/// <summary>
/// Initializes new instance of <see cref="SqliteBulkUpdateOptions"/>.
/// </summary>
/// <param name="optionsToInitializeFrom">Options to initialize from.</param>
public SqliteBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null)
{
if (optionsToInitializeFrom is null)
return;
AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull;

if (optionsToInitializeFrom is not null)
{
PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;

PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate;
KeyProperties = optionsToInitializeFrom.KeyProperties;
if (optionsToInitializeFrom is SqliteBulkUpdateOptions sqliteOptions)
AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public override string GetStatement(

try
{
GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert);
GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert.Union(_keyProperties).ToList());

sb.AppendLine()
.Append("\tON CONFLICT(");
Expand Down
Loading

0 comments on commit b07b1eb

Please sign in to comment.