44
55using Microsoft . EntityFrameworkCore ;
66using Microsoft . EntityFrameworkCore . Infrastructure ;
7- using Microsoft . EntityFrameworkCore . Metadata ;
7+ using Microsoft . Extensions . Logging ;
88
99using PhenX . EntityFrameworkCore . BulkInsert . Abstractions ;
1010using PhenX . EntityFrameworkCore . BulkInsert . Metadata ;
1111using PhenX . EntityFrameworkCore . BulkInsert . Options ;
1212
1313namespace PhenX . EntityFrameworkCore . BulkInsert . Graph ;
1414
15- /// <summary>
16- /// Result of a graph insert operation.
17- /// </summary>
18- /// <typeparam name="T">The root entity type.</typeparam>
19- internal sealed class GraphInsertResult < T > where T : class
20- {
21- /// <summary>
22- /// The root entities that were inserted.
23- /// </summary>
24- public required IReadOnlyList < T > RootEntities { get ; init ; }
25-
26- /// <summary>
27- /// Total count of all entities inserted across all types.
28- /// </summary>
29- public required int TotalInsertedCount { get ; init ; }
30- }
31-
3215/// <summary>
3316/// Orchestrates bulk insertion of entity graphs with FK propagation.
3417/// </summary>
@@ -37,25 +20,35 @@ internal sealed class GraphBulkInsertOrchestrator
3720 private static readonly ConcurrentDictionary < ( Type , string ) , Action < object , object ? > > PropertySetters = new ( ) ;
3821 private static readonly ConcurrentDictionary < ( Type , string ) , Func < object , object ? > > PropertyGetters = new ( ) ;
3922
23+ private readonly DbContext _context ;
4024 private readonly MetadataProvider _metadataProvider ;
25+ private readonly ILogger < GraphBulkInsertOrchestrator > ? _logger ;
4126
42- public GraphBulkInsertOrchestrator ( )
27+ public GraphBulkInsertOrchestrator ( DbContext context )
4328 {
44- _metadataProvider = new MetadataProvider ( ) ;
29+ _context = context ;
30+ _metadataProvider = context . GetService < MetadataProvider > ( ) ;
31+ _logger = context . GetService < ILogger < GraphBulkInsertOrchestrator > > ( ) ;
4532 }
4633
4734 /// <summary>
4835 /// Orchestrates the bulk insert of an entity graph.
4936 /// </summary>
50- public async Task < GraphInsertResult < T > > InsertGraphAsync < T > (
51- DbContext context ,
37+ public async Task < GraphInsertResult < T > > InsertGraph < T > (
38+ bool sync ,
5239 IEnumerable < T > entities ,
5340 BulkInsertOptions options ,
5441 IBulkInsertProvider provider ,
5542 CancellationToken ctk ) where T : class
5643 {
44+ if ( ! provider . SupportsOutputInsertedIds )
45+ {
46+ throw new NotSupportedException (
47+ $ "The bulk insert provider '{ provider . GetType ( ) . Name } ' does not support returning generated IDs, which is required for IncludeGraph operations.") ;
48+ }
49+
5750 // 1. Collect and sort entities
58- var collector = new GraphEntityCollector ( context , options ) ;
51+ var collector = new GraphEntityCollector ( _context , options ) ;
5952 var collectionResult = collector . Collect ( entities ) ;
6053
6154 if ( collectionResult . EntitiesByType . Count == 0 )
@@ -68,7 +61,7 @@ public async Task<GraphInsertResult<T>> InsertGraphAsync<T>(
6861 }
6962
7063 var totalInserted = 0 ;
71- var graphMetadata = new GraphMetadata ( context , options ) ;
64+ var graphMetadata = new GraphMetadata ( _context , options ) ;
7265
7366 // 2. Insert in dependency order (parents first)
7467 foreach ( var entityType in collectionResult . InsertionOrder )
@@ -80,18 +73,18 @@ public async Task<GraphInsertResult<T>> InsertGraphAsync<T>(
8073 }
8174
8275 // Propagate FK values from already-inserted parents
83- PropagateParentForeignKeys ( entitiesToInsert , entityType , graphMetadata , context ) ;
76+ PropagateParentForeignKeys ( entitiesToInsert , entityType , graphMetadata ) ;
8477
8578 // Insert entities of this type
86- await InsertEntitiesOfTypeAsync ( context , entityType , entitiesToInsert , options , provider , ctk ) ;
79+ await InsertEntitiesOfType ( sync , _context , entityType , entitiesToInsert , options , provider , ctk ) ;
8780
8881 totalInserted += entitiesToInsert . Count ;
8982 }
9083
9184 // 3. Insert join table records for many-to-many relationships
9285 if ( collectionResult . JoinRecords . Count > 0 )
9386 {
94- await InsertJoinRecordsAsync ( context , collectionResult . JoinRecords , options , provider , ctk ) ;
87+ await InsertJoinRecords ( sync , _context , collectionResult . JoinRecords , options , provider , ctk ) ;
9588 }
9689
9790 // Return root entities
@@ -109,8 +102,7 @@ public async Task<GraphInsertResult<T>> InsertGraphAsync<T>(
109102 private static void PropagateParentForeignKeys (
110103 List < object > entities ,
111104 Type entityType ,
112- GraphMetadata graphMetadata ,
113- DbContext context )
105+ GraphMetadata graphMetadata )
114106 {
115107 var efEntityType = graphMetadata . GetEntityType ( entityType ) ;
116108 if ( efEntityType == null )
@@ -162,7 +154,8 @@ private static void PropagateParentForeignKeys(
162154 }
163155 }
164156
165- private async Task InsertEntitiesOfTypeAsync (
157+ private async Task InsertEntitiesOfType (
158+ bool sync ,
166159 DbContext context ,
167160 Type entityType ,
168161 List < object > entities ,
@@ -218,7 +211,7 @@ private async Task InsertEntitiesGenericAsync<TEntity>(
218211 }
219212 }
220213
221- private static void CopyGeneratedIds < TEntity > (
214+ private void CopyGeneratedIds < TEntity > (
222215 List < TEntity > originalEntities ,
223216 List < TEntity > insertedEntities ,
224217 TableMetadata tableInfo ) where TEntity : class
@@ -228,10 +221,10 @@ private static void CopyGeneratedIds<TEntity>(
228221 // Count mismatch - this can happen if the bulk insert operation
229222 // doesn't preserve order. Log a warning for debugging purposes.
230223 // The graph insert will continue but FK propagation may be incomplete.
231- System . Diagnostics . Debug . WriteLine (
232- $ "Warning: IncludeGraph ID propagation failed for { typeof ( TEntity ) . Name } . " +
233- $ "Original count: { originalEntities . Count } , Inserted count: { insertedEntities . Count } . " +
234- "Foreign key values may not be correctly propagated to dependent entities." ) ;
224+ _logger ? . LogWarning (
225+ " IncludeGraph ID propagation failed for {EntityType}. Original count: {OriginalCount}, Inserted count: {InsertedCount}. Foreign key values may not be correctly propagated to dependent entities." ,
226+ typeof ( TEntity ) . Name , originalEntities . Count , insertedEntities . Count ) ;
227+
235228 return ;
236229 }
237230
@@ -254,7 +247,8 @@ private static void CopyGeneratedIds<TEntity>(
254247 }
255248 }
256249
257- private async Task InsertJoinRecordsAsync (
250+ private async Task InsertJoinRecords (
251+ bool sync ,
258252 DbContext context ,
259253 List < JoinRecord > joinRecords ,
260254 BulkInsertOptions options ,
@@ -293,9 +287,11 @@ private async Task InsertJoinRecordsAsync(
293287 var joinEntry = Activator . CreateInstance ( joinEntityType ) ;
294288 if ( joinEntry == null )
295289 {
296- System . Diagnostics . Debug . WriteLine (
297- $ "Warning: IncludeGraph failed to create join entry for { joinEntityType . Name } . " +
298- "Many-to-many relationship may be incomplete." ) ;
290+ _logger ? . LogWarning (
291+ "IncludeGraph failed to create join entry for {EntityType}. Many-to-many relationship may be incomplete." ,
292+ joinEntityType . Name
293+ ) ;
294+
299295 continue ;
300296 }
301297
@@ -342,12 +338,13 @@ private async Task InsertJoinRecordsAsync(
342338 if ( joinEntities . Count > 0 )
343339 {
344340 // Insert join entities
345- await InsertJoinEntitiesAsync ( context , joinEntityType , joinEntities , options , provider , ctk ) ;
341+ await InsertJoinEntities ( sync , context , joinEntityType , joinEntities , options , provider , ctk ) ;
346342 }
347343 }
348344 }
349345
350- private async Task InsertJoinEntitiesAsync (
346+ private async Task InsertJoinEntities (
347+ bool sync ,
351348 DbContext context ,
352349 Type joinEntityType ,
353350 List < object > joinEntities ,
@@ -369,7 +366,7 @@ private async Task InsertJoinEntitiesAsync(
369366 . GetMethod ( nameof ( IBulkInsertProvider . BulkInsert ) ) !
370367 . MakeGenericMethod ( joinEntityType ) ;
371368
372- var result = method . Invoke ( provider , [ false , context , tableInfo , joinEntities , options , null , ctk ] ) ;
369+ var result = method . Invoke ( provider , [ sync , context , tableInfo , joinEntities , options , null , ctk ] ) ;
373370 if ( result is not Task task )
374371 {
375372 throw new InvalidOperationException (
0 commit comments