1- using System . Collections . Concurrent ;
2- using System . Linq . Expressions ;
31using System . Reflection ;
42
53using Microsoft . EntityFrameworkCore ;
@@ -17,8 +15,6 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Graph;
1715/// </summary>
1816internal sealed class GraphBulkInsertOrchestrator
1917{
20- private static readonly ConcurrentDictionary < ( Type , string ) , Action < object , object ? > > PropertySetters = new ( ) ;
21- private static readonly ConcurrentDictionary < ( Type , string ) , Func < object , object ? > > PropertyGetters = new ( ) ;
2218
2319 private readonly DbContext _context ;
2420 private readonly MetadataProvider _metadataProvider ;
@@ -76,15 +72,15 @@ public async Task<GraphInsertResult<T>> InsertGraph<T>(
7672 PropagateParentForeignKeys ( entitiesToInsert , entityType , graphMetadata ) ;
7773
7874 // Insert entities of this type
79- await InsertEntitiesOfType ( sync , _context , entityType , entitiesToInsert , options , provider , ctk ) ;
75+ await InsertEntitiesOfType ( sync , _context , entityType , entitiesToInsert , options , provider , graphMetadata , ctk ) ;
8076
8177 totalInserted += entitiesToInsert . Count ;
8278 }
8379
8480 // 3. Insert join table records for many-to-many relationships
8581 if ( collectionResult . JoinRecords . Count > 0 )
8682 {
87- await InsertJoinRecords ( sync , _context , collectionResult . JoinRecords , options , provider , ctk ) ;
83+ await InsertJoinRecords ( sync , _context , collectionResult . JoinRecords , options , provider , graphMetadata , ctk ) ;
8884 }
8985
9086 // Return root entities
@@ -110,6 +106,12 @@ private static void PropagateParentForeignKeys(
110106 return ;
111107 }
112108
109+ var entityMetadata = graphMetadata . GetEntityMetadata ( entityType ) ;
110+ if ( entityMetadata == null )
111+ {
112+ return ;
113+ }
114+
113115 // For each FK relationship, propagate PK values from parent entities
114116 foreach ( var fk in efEntityType . GetForeignKeys ( ) )
115117 {
@@ -125,12 +127,18 @@ private static void PropagateParentForeignKeys(
125127 foreach ( var entity in entities )
126128 {
127129 // Get the parent entity via navigation property
128- var parentEntity = GetPropertyValue ( entity , navigationPropertyName ) ;
130+ var parentEntity = entityMetadata . GetPropertyValue ( entity , navigationPropertyName ) ;
129131 if ( parentEntity == null )
130132 {
131133 continue ;
132134 }
133135
136+ var parentMetadata = graphMetadata . GetEntityMetadata ( parentEntity . GetType ( ) ) ;
137+ if ( parentMetadata == null )
138+ {
139+ continue ;
140+ }
141+
134142 // Copy PK values from parent to FK properties on this entity
135143 var fkProperties = fk . Properties ;
136144 var pkProperties = fk . PrincipalKey . Properties ;
@@ -147,8 +155,8 @@ private static void PropagateParentForeignKeys(
147155 continue ;
148156 }
149157
150- var pkValue = GetPropertyValue ( parentEntity , pkProp . Name ) ;
151- SetPropertyValue ( entity , fkProp . Name , pkValue ) ;
158+ var pkValue = parentMetadata . GetPropertyValue ( parentEntity , pkProp . Name ) ;
159+ entityMetadata . SetPropertyValue ( entity , fkProp . Name , pkValue ) ;
152160 }
153161 }
154162 }
@@ -161,14 +169,15 @@ private async Task InsertEntitiesOfType(
161169 List < object > entities ,
162170 BulkInsertOptions options ,
163171 IBulkInsertProvider provider ,
172+ GraphMetadata graphMetadata ,
164173 CancellationToken ctk )
165174 {
166175 // Use reflection to call the generic BulkInsert method
167176 var method = typeof ( GraphBulkInsertOrchestrator )
168177 . GetMethod ( nameof ( InsertEntitiesGenericAsync ) , BindingFlags . NonPublic | BindingFlags . Instance ) !
169178 . MakeGenericMethod ( entityType ) ;
170179
171- var task = ( Task ) method . Invoke ( this , [ context , entities , options , provider , ctk ] ) ! ;
180+ var task = ( Task ) method . Invoke ( this , [ context , entities , options , provider , graphMetadata , ctk ] ) ! ;
172181 await task ;
173182 }
174183
@@ -177,6 +186,7 @@ private async Task InsertEntitiesGenericAsync<TEntity>(
177186 List < object > entities ,
178187 BulkInsertOptions options ,
179188 IBulkInsertProvider provider ,
189+ GraphMetadata graphMetadata ,
180190 CancellationToken ctk ) where TEntity : class
181191 {
182192 var typedEntities = entities . Cast < TEntity > ( ) . ToList ( ) ;
@@ -202,7 +212,11 @@ private async Task InsertEntitiesGenericAsync<TEntity>(
202212 }
203213
204214 // Copy generated IDs back to original entities
205- CopyGeneratedIds ( typedEntities , insertedEntities , tableInfo ) ;
215+ var entityMetadata = graphMetadata . GetEntityMetadata ( typeof ( TEntity ) ) ;
216+ if ( entityMetadata != null )
217+ {
218+ CopyGeneratedIds ( typedEntities , insertedEntities , tableInfo , entityMetadata ) ;
219+ }
206220 }
207221 else
208222 {
@@ -214,7 +228,8 @@ private async Task InsertEntitiesGenericAsync<TEntity>(
214228 private void CopyGeneratedIds < TEntity > (
215229 List < TEntity > originalEntities ,
216230 List < TEntity > insertedEntities ,
217- TableMetadata tableInfo ) where TEntity : class
231+ TableMetadata tableInfo ,
232+ EntityMetadata entityMetadata ) where TEntity : class
218233 {
219234 if ( originalEntities . Count != insertedEntities . Count )
220235 {
@@ -241,8 +256,8 @@ private void CopyGeneratedIds<TEntity>(
241256
242257 foreach ( var pkProp in pkProps )
243258 {
244- var value = GetPropertyValue ( inserted , pkProp . PropertyName ) ;
245- SetPropertyValue ( original , pkProp . PropertyName , value ) ;
259+ var value = entityMetadata . GetPropertyValue ( inserted , pkProp . PropertyName ) ;
260+ entityMetadata . SetPropertyValue ( original , pkProp . PropertyName , value ) ;
246261 }
247262 }
248263 }
@@ -253,6 +268,7 @@ private async Task InsertJoinRecords(
253268 List < JoinRecord > joinRecords ,
254269 BulkInsertOptions options ,
255270 IBulkInsertProvider provider ,
271+ GraphMetadata graphMetadata ,
256272 CancellationToken ctk )
257273 {
258274 // Group join records by join entity type
@@ -278,11 +294,23 @@ private async Task InsertJoinRecords(
278294 continue ;
279295 }
280296
297+ // Get entity metadata for join type
298+ var joinEntityMetadata = graphMetadata . GetEntityMetadata ( joinEntityType ) ;
299+
281300 // Create join table entries
282301 var joinEntities = new List < object > ( ) ;
283302
284303 foreach ( var record in records )
285304 {
305+ // Get metadata for left and right entities
306+ var leftMetadata = graphMetadata . GetEntityMetadata ( record . LeftEntity . GetType ( ) ) ;
307+ var rightMetadata = graphMetadata . GetEntityMetadata ( record . RightEntity . GetType ( ) ) ;
308+
309+ if ( leftMetadata == null || rightMetadata == null )
310+ {
311+ continue ;
312+ }
313+
286314 // Create a dictionary-based join entity
287315 var joinEntry = Activator . CreateInstance ( joinEntityType ) ;
288316 if ( joinEntry == null )
@@ -304,14 +332,14 @@ private async Task InsertJoinRecords(
304332 var fkProp = fk . Properties [ i ] ;
305333 var pkProp = fk . PrincipalKey . Properties [ i ] ;
306334
307- var pkValue = GetPropertyValue ( record . LeftEntity , pkProp . Name ) ;
335+ var pkValue = leftMetadata . GetPropertyValue ( record . LeftEntity , pkProp . Name ) ;
308336 if ( dictEntry != null )
309337 {
310338 dictEntry [ fkProp . Name ] = pkValue ! ;
311339 }
312- else
340+ else if ( joinEntityMetadata != null )
313341 {
314- SetPropertyValue ( joinEntry , fkProp . Name , pkValue ) ;
342+ joinEntityMetadata . SetPropertyValue ( joinEntry , fkProp . Name , pkValue ) ;
315343 }
316344 }
317345
@@ -321,14 +349,14 @@ private async Task InsertJoinRecords(
321349 var fkProp = inverseFk . Properties [ i ] ;
322350 var pkProp = inverseFk . PrincipalKey . Properties [ i ] ;
323351
324- var pkValue = GetPropertyValue ( record . RightEntity , pkProp . Name ) ;
352+ var pkValue = rightMetadata . GetPropertyValue ( record . RightEntity , pkProp . Name ) ;
325353 if ( dictEntry != null )
326354 {
327355 dictEntry [ fkProp . Name ] = pkValue ! ;
328356 }
329- else
357+ else if ( joinEntityMetadata != null )
330358 {
331- SetPropertyValue ( joinEntry , fkProp . Name , pkValue ) ;
359+ joinEntityMetadata . SetPropertyValue ( joinEntry , fkProp . Name , pkValue ) ;
332360 }
333361 }
334362
@@ -374,50 +402,4 @@ private async Task InsertJoinEntities(
374402 }
375403 await task ;
376404 }
377-
378- private static object ? GetPropertyValue ( object entity , string propertyName )
379- {
380- var key = ( entity . GetType ( ) , propertyName ) ;
381- var getter = PropertyGetters . GetOrAdd ( key , k =>
382- {
383- var property = k . Item1 . GetProperty ( k . Item2 , BindingFlags . Public | BindingFlags . Instance ) ;
384- if ( property == null )
385- {
386- return _ => null ;
387- }
388-
389- var param = Expression . Parameter ( typeof ( object ) , "obj" ) ;
390- var cast = Expression . Convert ( param , k . Item1 ) ;
391- var access = Expression . Property ( cast , property ) ;
392- var convertResult = Expression . Convert ( access , typeof ( object ) ) ;
393-
394- return Expression . Lambda < Func < object , object ? > > ( convertResult , param ) . Compile ( ) ;
395- } ) ;
396-
397- return getter ( entity ) ;
398- }
399-
400- private static void SetPropertyValue ( object entity , string propertyName , object ? value )
401- {
402- var key = ( entity . GetType ( ) , propertyName ) ;
403- var setter = PropertySetters . GetOrAdd ( key , k =>
404- {
405- var property = k . Item1 . GetProperty ( k . Item2 , BindingFlags . Public | BindingFlags . Instance ) ;
406- if ( property == null || ! property . CanWrite )
407- {
408- return ( _ , _ ) => { } ;
409- }
410-
411- var param = Expression . Parameter ( typeof ( object ) , "obj" ) ;
412- var valueParam = Expression . Parameter ( typeof ( object ) , "value" ) ;
413- var cast = Expression . Convert ( param , k . Item1 ) ;
414- var access = Expression . Property ( cast , property ) ;
415- var convertValue = Expression . Convert ( valueParam , property . PropertyType ) ;
416- var assign = Expression . Assign ( access , convertValue ) ;
417-
418- return Expression . Lambda < Action < object , object ? > > ( assign , param , valueParam ) . Compile ( ) ;
419- } ) ;
420-
421- setter ( entity , value ) ;
422- }
423405}
0 commit comments