Skip to content

Commit f21b3a8

Browse files
CopilotPhenX
andcommitted
Allow IncludeGraph on Oracle/MySQL when keys are client-generated, include join records in total count, filter keyless entities
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
1 parent 3e636b1 commit f21b3a8

3 files changed

Lines changed: 29 additions & 10 deletions

File tree

src/PhenX.EntityFrameworkCore.BulkInsert/Graph/GraphBulkInsertOrchestrator.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ public async Task<GraphInsertResult<T>> InsertGraph<T>(
3737
IBulkInsertProvider provider,
3838
CancellationToken ctk) where T : class
3939
{
40-
if (!provider.SupportsOutputInsertedIds)
41-
{
42-
throw new NotSupportedException(
43-
$"The bulk insert provider '{provider.GetType().Name}' does not support returning generated IDs, which is required for IncludeGraph operations.");
44-
}
45-
4640
// 1. Collect and sort entities
4741
var collector = new GraphEntityCollector(_context, options);
4842
var collectionResult = collector.Collect(entities);
@@ -59,6 +53,21 @@ public async Task<GraphInsertResult<T>> InsertGraph<T>(
5953
var totalInserted = 0;
6054
var graphMetadata = new GraphMetadata(_context, options);
6155

56+
// Check if any entity types have generated keys - if so, provider must support returning IDs
57+
var hasAnyGeneratedKeys = collectionResult.InsertionOrder.Any(entityType =>
58+
{
59+
var efEntityType = graphMetadata.GetEntityType(entityType);
60+
return efEntityType?.FindPrimaryKey()?.Properties.Any(p => p.ValueGenerated != Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never) == true;
61+
});
62+
63+
if (hasAnyGeneratedKeys && !provider.SupportsOutputInsertedIds)
64+
{
65+
throw new NotSupportedException(
66+
$"The bulk insert provider '{provider.GetType().Name}' does not support returning generated IDs, " +
67+
$"which is required for IncludeGraph operations when entities have database-generated keys. " +
68+
$"Consider using client-generated keys (e.g., GUIDs with ValueGeneratedNever()).");
69+
}
70+
6271
// 2. Insert in dependency order (parents first)
6372
foreach (var entityType in collectionResult.InsertionOrder)
6473
{
@@ -78,9 +87,11 @@ public async Task<GraphInsertResult<T>> InsertGraph<T>(
7887
}
7988

8089
// 3. Insert join table records for many-to-many relationships
90+
var joinRecordsInserted = 0;
8191
if (collectionResult.JoinRecords.Count > 0)
8292
{
83-
await InsertJoinRecords(sync, _context, collectionResult.JoinRecords, options, provider, graphMetadata, ctk);
93+
joinRecordsInserted = await InsertJoinRecords(sync, _context, collectionResult.JoinRecords, options, provider, graphMetadata, ctk);
94+
totalInserted += joinRecordsInserted;
8495
}
8596

8697
// Return root entities
@@ -263,7 +274,7 @@ private void CopyGeneratedIds<TEntity>(
263274
}
264275
}
265276

266-
private async Task InsertJoinRecords(
277+
private async Task<int> InsertJoinRecords(
267278
bool sync,
268279
DbContext context,
269280
List<JoinRecord> joinRecords,
@@ -272,6 +283,8 @@ private async Task InsertJoinRecords(
272283
GraphMetadata graphMetadata,
273284
CancellationToken ctk)
274285
{
286+
var totalJoinRecordsInserted = 0;
287+
275288
// Group join records by join entity type
276289
var groupedRecords = joinRecords.GroupBy(jr => jr.JoinEntityType);
277290

@@ -368,8 +381,11 @@ private async Task InsertJoinRecords(
368381
{
369382
// Insert join entities
370383
await InsertJoinEntities(sync, context, joinEntityType, joinEntities, options, provider, ctk);
384+
totalJoinRecordsInserted += joinEntities.Count;
371385
}
372386
}
387+
388+
return totalJoinRecordsInserted;
373389
}
374390

375391
private async Task InsertJoinEntities(

src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/GraphMetadata.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ public GraphMetadata(DbContext context, BulkInsertOptions options)
2424
_entityTypes = [];
2525
foreach (var entityType in context.Model.GetEntityTypes())
2626
{
27-
if (entityType.IsOwned() || entityType.ClrType == null || entityType.GetTableName() == null)
27+
if (entityType.IsOwned() ||
28+
entityType.ClrType == null ||
29+
entityType.GetTableName() == null ||
30+
entityType.FindPrimaryKey() == null) // Keyless entities have no primary key
2831
{
2932
continue;
3033
}

tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Graph/GraphTestsBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ public async Task InsertGraph_LargeScale()
652652
blogs.Add(blog);
653653
}
654654

655-
// Act - Insert all 1000 blogs with their children
655+
// Act - Insert all 50 blogs with their children
656656
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
657657
await _context.ExecuteBulkInsertAsync(blogs, options =>
658658
{

0 commit comments

Comments
 (0)