Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions EfCore.BulkOperations.API/Models/Log.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;

namespace EfCore.BulkOperations.API.Models;

/// <summary>
/// A dummy entity without UniqueKey
/// </summary>
public class Log
{
public Log(string content)
{
Id = Guid.NewGuid();
Timestamp = DateTime.UtcNow;
Content = content;
}

[Key] public Guid Id { get; init; }
[StringLength(1000)] public string Content { get; init; }
public DateTime Timestamp { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)

public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<Log> Logs => Set<Log>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand Down
33 changes: 33 additions & 0 deletions EfCore.BulkOperations.Test/BulkCommandTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Data;
using EfCore.BulkOperations.API.Models;
using EfCore.BulkOperations.Test.Setup;

Expand Down Expand Up @@ -306,4 +307,36 @@ public void ShouldError_WhenPassNonEntity()
// Assert
Assert.StartsWith("Unable to resolve EntityType", exception.Message);
}

[Fact]
public void ShouldError_WhenUpdateEntityHasNoUniqueKey()
{
// Arrange
var items = new List<Log> { new("Test") };

// Act
var exception = Assert.Throws<MissingPrimaryKeyException>(() =>
{
var _ = BulkCommand.GenerateUpdateBatches(DbContext, items, null).ToList();
});

// Assert
Assert.StartsWith("A unique key in the database is required to perform a bulk operation", exception.Message);
}

[Fact]
public void ShouldError_WhenDeleteEntityHasNoUniqueKey()
{
// Arrange
var items = new List<Log> { new("Test") };

// Act
var exception = Assert.Throws<MissingPrimaryKeyException>(() =>
{
var _ = BulkCommand.GenerateDeleteBatches(DbContext, items, null).ToList();
});

// Assert
Assert.StartsWith("A unique key in the database is required to perform a bulk operation", exception.Message);
}
}
18 changes: 13 additions & 5 deletions EfCore.BulkOperations/BulkCommand.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Data;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
Expand Down Expand Up @@ -164,6 +165,9 @@ internal static IEnumerable<BatchData> GenerateUpdateBatches<T>(DbContext dbCont
.ToList();
}

if (keys.Count == 0)
throw new MissingPrimaryKeyException(
"A unique key in the database is required to perform a bulk operation");

var index = 0;
foreach (var key in keys)
Expand Down Expand Up @@ -196,39 +200,43 @@ internal static IEnumerable<BatchData> GenerateDeleteBatches<T>(DbContext dbCont
if (items.Count == 0) yield return new BatchData(new StringBuilder(), []);
var info = GetEntityInfo<T>(dbContext);

List<ColumnInfo> columns;
List<ColumnInfo> keys;
if (option?.UniqueKeys is null)
{
// Auto detects unique keys
columns = info.Columns
keys = info.Columns
.Where(x => x.IsUniqueIndex)
.ToList();
}
else
{
// Specific custom unique keys
var uniqueKeys = GetExpressionFields(option.UniqueKeys);
columns = info.Columns
keys = info.Columns
.Where(x => uniqueKeys.Contains(x.RefName))
.ToList();
}

if (keys.Count == 0)
throw new MissingPrimaryKeyException(
"A unique key in the database is required to perform a bulk operation");

var chunkList = items
.ToList()
.ChunkSplit(option?.BatchSize ?? BatchSize);
var offset = 0;

foreach (var chunk in chunkList)
{
var tmpTable = ToTempTable(columns, chunk, offset);
var tmpTable = ToTempTable(keys, chunk, offset);
if (tmpTable is null) yield return new BatchData(new StringBuilder(), []);

tmpTable!.Sql.Insert(0,
@$"DELETE tb
FROM `{info.TableName}` AS tb
INNER JOIN ");
var index = 0;
foreach (var key in columns)
foreach (var key in keys)
{
tmpTable.Sql.Append(index++ == 0 ? "ON " : "AND ");
tmpTable.Sql.AppendLine($"tb.`{key.Name}` = tmp.`{key.Name}`");
Expand Down