diff --git a/src/modules/Elsa.Persistence.EFCore.Common/Extensions/BulkUpsertExtensions.cs b/src/modules/Elsa.Persistence.EFCore.Common/Extensions/BulkUpsertExtensions.cs
index 2749790bd..707702589 100644
--- a/src/modules/Elsa.Persistence.EFCore.Common/Extensions/BulkUpsertExtensions.cs
+++ b/src/modules/Elsa.Persistence.EFCore.Common/Extensions/BulkUpsertExtensions.cs
@@ -16,6 +16,12 @@ public static class BulkUpsertExtensions
///
/// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector.
///
+ /// The type of the database context.
+ /// The type of the entity being upserted.
+ /// The database context where the bulk upsert operation will be executed.
+ /// The list of entities to be upserted.
+ /// An expression used to determine the key for upsert operations.
+ /// A token to observe while waiting for the operation to complete.
public static async Task BulkUpsertAsync(
this TDbContext dbContext,
IList entities,
@@ -30,6 +36,13 @@ public static class BulkUpsertExtensions
///
/// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector and optional batch size.
///
+ /// The type of the database context.
+ /// The type of the entity being upserted.
+ /// The database context where the bulk upsert operation will be executed.
+ /// The list of entities to be upserted.
+ /// An expression used to determine the key for upsert operations.
+ /// The size of each batch for processing the upsert operation. Defaults to 50.
+ /// A token to observe while waiting for the operation to complete.
/// Thrown if the database provider for the context is not supported.
public static async Task BulkUpsertAsync(
this TDbContext dbContext,
@@ -43,29 +56,30 @@ public static class BulkUpsertExtensions
if (entities.Count == 0)
return;
+ // Identify the current provider (e.g., "Microsoft.EntityFrameworkCore.SqlServer")
var providerName = dbContext.Database.ProviderName?.ToLowerInvariant() ?? string.Empty;
+ // Determine the method for generating SQL based on the provider
Func, Expression>, (string, object[])> generateSql = providerName switch
{
var pn when pn.Contains("sqlserver") => GenerateSqlServerUpsert,
- var pn when pn.Contains("sqlite") => GenerateSqliteUpsert,
- var pn when pn.Contains("postgres") => GeneratePostgresUpsert,
- var pn when pn.Contains("mysql") => GenerateMySqlUpsert,
- var pn when pn.Contains("oracle") => GenerateOracleUpsert,
+ var pn when pn.Contains("sqlite") => GenerateSqliteUpsert,
+ var pn when pn.Contains("postgres") => GeneratePostgresUpsert,
+ var pn when pn.Contains("mysql") => GenerateMySqlUpsert,
+ var pn when pn.Contains("oracle") => GenerateOracleUpsert,
_ => throw new NotSupportedException($"Provider '{providerName}' is not supported.")
};
+ // Loop through batched entities
foreach (var batch in entities.Chunk(batchSize))
{
+ // Generate SQL and parameters
var (sql, parameters) = generateSql(dbContext, batch, keySelector);
+
await dbContext.Database.ExecuteSqlRawAsync(sql, parameters, cancellationToken);
}
}
- // -------------------------------------------------------------------------
- // SQL Server
- // -------------------------------------------------------------------------
-
private static (string, object[]) GenerateSqlServerUpsert(
DbContext dbContext,
IList entities,
@@ -78,7 +92,9 @@ public static class BulkUpsertExtensions
var props = entityType.GetProperties().ToList();
var keyProp = entityType.FindProperty(keySelector.GetMemberAccess().Name)!;
var keyColumnName = $"[{keyProp.GetColumnName(storeObject)}]";
- var columnNames = props.Select(p => $"[{p.GetColumnName(storeObject)}]").ToList();
+ var columnNames = props
+ .Select(p => $"[{p.GetColumnName(storeObject)}]")
+ .ToList();
var mergeSql = new StringBuilder();
mergeSql.AppendLine($"MERGE {tableName} AS Target");
@@ -95,21 +111,27 @@ public static class BulkUpsertExtensions
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
+
+ // If it's a shadow property, retrieve value via Entry(..).Property(..)
var value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
- var converter = property.GetTypeMapping().Converter;
- if (converter != null) value = converter.ConvertToProvider(value)!;
+ var converter = property.GetTypeMapping().Converter;
+ if (converter != null)
+ value = converter.ConvertToProvider(value)!;
+
+ // Explicitly cast null values for varbinary columns
if (property.GetColumnType().StartsWith("varbinary", StringComparison.OrdinalIgnoreCase) && value is null)
- values.Add("CAST(NULL AS varbinary(max))");
+ values.Add("CAST(NULL AS varbinary(max))"); // Explicitly cast null
else
values.Add(paramName);
-
+
parameters.Add(value!);
}
- mergeSql.AppendLine($"({string.Join(", ", values)}){(i < entities.Count - 1 ? "," : string.Empty)}");
+ var line = $"({string.Join(", ", values)}){(i < entities.Count - 1 ? "," : string.Empty)}";
+ mergeSql.AppendLine(line);
}
mergeSql.AppendLine($") AS Source ({string.Join(", ", columnNames)})");
@@ -123,10 +145,6 @@ public static class BulkUpsertExtensions
return (mergeSql.ToString(), parameters.ToArray());
}
- // -------------------------------------------------------------------------
- // SQLite
- // -------------------------------------------------------------------------
-
private static (string, object[]) GenerateSqliteUpsert(
DbContext dbContext,
IList entities,
@@ -139,7 +157,9 @@ public static class BulkUpsertExtensions
var props = entityType.GetProperties().ToList();
var keyProp = entityType.FindProperty(keySelector.GetMemberAccess().Name)!;
var keyColumnName = keyProp.GetColumnName(storeObject);
- var columnNames = props.Select(p => p.GetColumnName(storeObject)!).ToList();
+ var columnNames = props
+ .Select(p => p.GetColumnName(storeObject)!)
+ .ToList();
var sb = new StringBuilder();
var parameters = new List