@@ -588,4 +588,130 @@ await _context.ExecuteBulkInsertAsync(blogs, options =>
588588 dbPosts . Select ( p => p . Id ) . Should ( ) . Contain ( post2 . Id ) ;
589589 dbPosts . Select ( p => p . Id ) . Should ( ) . Contain ( post3 . Id ) ;
590590 }
591+
592+ [ SkippableFact ]
593+ public async Task InsertGraph_LargeScale ( )
594+ {
595+ // Arrange - Create many blogs with many children each (Posts, Tags, BlogSettings)
596+ // This tests performance and correctness at scale
597+ const int blogCount = 5000 ;
598+ const int postsPerBlog = 100 ;
599+ const int tagsPerPost = 10 ;
600+
601+ var blogs = new List < Blog > ( ) ;
602+ var allTags = new List < Tag > ( ) ;
603+
604+ // Pre-create a pool of tags that will be shared across posts
605+ for ( var i = 0 ; i < 50 ; i ++ )
606+ {
607+ allTags . Add ( new Tag
608+ {
609+ TestRun = _run ,
610+ Name = $ "{ _run } _SharedTag_{ i } "
611+ } ) ;
612+ }
613+
614+ for ( var blogIndex = 0 ; blogIndex < blogCount ; blogIndex ++ )
615+ {
616+ var posts = new List < Post > ( ) ;
617+
618+ // Create posts for this blog
619+ for ( var postIndex = 0 ; postIndex < postsPerBlog ; postIndex ++ )
620+ {
621+ var post = new Post
622+ {
623+ TestRun = _run ,
624+ Title = $ "{ _run } _Blog{ blogIndex } _Post{ postIndex } "
625+ } ;
626+
627+ // Add some tags to this post (from the shared pool)
628+ var postTags = new List < Tag > ( ) ;
629+ for ( var tagIndex = 0 ; tagIndex < tagsPerPost ; tagIndex ++ )
630+ {
631+ var tagPoolIndex = ( blogIndex * postsPerBlog + postIndex + tagIndex ) % allTags . Count ;
632+ postTags . Add ( allTags [ tagPoolIndex ] ) ;
633+ }
634+ post . Tags = postTags ;
635+
636+ posts . Add ( post ) ;
637+ }
638+
639+ // Create the blog with its children
640+ var blog = new Blog
641+ {
642+ TestRun = _run ,
643+ Name = $ "{ _run } _LargeScaleBlog_{ blogIndex } ",
644+ Posts = posts ,
645+ Settings = new BlogSettings
646+ {
647+ TestRun = _run ,
648+ EnableComments = blogIndex % 2 == 0
649+ }
650+ } ;
651+
652+ blogs . Add ( blog ) ;
653+ }
654+
655+ // Act - Insert all 1000 blogs with their children
656+ var stopwatch = System . Diagnostics . Stopwatch . StartNew ( ) ;
657+ await _context . ExecuteBulkInsertAsync ( blogs , options =>
658+ {
659+ options . IncludeGraph = true ;
660+ } ) ;
661+ stopwatch . Stop ( ) ;
662+
663+ // Assert - Verify all entities were inserted correctly
664+ var insertedBlogs = _context . Blogs . Where ( b => b . TestRun == _run ) . ToList ( ) ;
665+ insertedBlogs . Should ( ) . HaveCount ( blogCount , "All blogs should be inserted" ) ;
666+ insertedBlogs . Should ( ) . AllSatisfy ( b => b . Id . Should ( ) . BeGreaterThan ( 0 ) , "All blogs should have generated IDs" ) ;
667+
668+ var insertedPosts = _context . Posts . Where ( p => p . TestRun == _run ) . ToList ( ) ;
669+ insertedPosts . Should ( ) . HaveCount ( blogCount * postsPerBlog , "All posts should be inserted" ) ;
670+ insertedPosts . Should ( ) . AllSatisfy ( p =>
671+ {
672+ p . Id . Should ( ) . BeGreaterThan ( 0 , "Post should have generated ID" ) ;
673+ p . BlogId . Should ( ) . BeGreaterThan ( 0 , "Post should have valid BlogId FK" ) ;
674+ } ) ;
675+
676+ var insertedSettings = _context . BlogSettings . Where ( s => s . TestRun == _run ) . ToList ( ) ;
677+ insertedSettings . Should ( ) . HaveCount ( blogCount , "All blog settings should be inserted" ) ;
678+ insertedSettings . Should ( ) . AllSatisfy ( s =>
679+ {
680+ s . Id . Should ( ) . BeGreaterThan ( 0 , "Settings should have generated ID" ) ;
681+ s . BlogId . Should ( ) . BeGreaterThan ( 0 , "Settings should have valid BlogId FK" ) ;
682+ } ) ;
683+
684+ var insertedTags = _context . Tags . Where ( t => t . TestRun == _run ) . ToList ( ) ;
685+ insertedTags . Should ( ) . HaveCount ( allTags . Count , "All unique tags should be inserted" ) ;
686+ insertedTags . Should ( ) . AllSatisfy ( t => t . Id . Should ( ) . BeGreaterThan ( 0 ) , "All tags should have generated IDs" ) ;
687+
688+ // Verify original entities have been updated with generated IDs
689+ blogs . Should ( ) . AllSatisfy ( b =>
690+ {
691+ b . Id . Should ( ) . BeGreaterThan ( 0 , "Original blog should have ID populated" ) ;
692+ b . Posts . Should ( ) . AllSatisfy ( p =>
693+ {
694+ p . Id . Should ( ) . BeGreaterThan ( 0 , "Original post should have ID populated" ) ;
695+ p . BlogId . Should ( ) . Be ( b . Id , "Original post FK should reference its blog" ) ;
696+ } ) ;
697+ b . Settings . Should ( ) . NotBeNull ( ) ;
698+ b . Settings ! . Id . Should ( ) . BeGreaterThan ( 0 , "Original settings should have ID populated" ) ;
699+ b . Settings . BlogId . Should ( ) . Be ( b . Id , "Original settings FK should reference its blog" ) ;
700+ } ) ;
701+
702+ allTags . Should ( ) . AllSatisfy ( t =>
703+ {
704+ t . Id . Should ( ) . BeGreaterThan ( 0 , "Original tag should have ID populated" ) ;
705+ } ) ;
706+
707+ // Report performance metrics
708+ var totalEntities = blogCount + ( blogCount * postsPerBlog ) + blogCount + allTags . Count ;
709+ var entitiesPerSecond = totalEntities / stopwatch . Elapsed . TotalSeconds ;
710+
711+ // Note: This is informational, not an assertion
712+ // Output is visible in test logs
713+ _context . GetType ( ) . Name . Should ( ) . NotBeNullOrEmpty (
714+ $ "Inserted { totalEntities : N0} entities in { stopwatch . Elapsed . TotalSeconds : F2} s " +
715+ $ "({ entitiesPerSecond : F0} entities/sec) using { _context . GetType ( ) . Name } ") ;
716+ }
591717}
0 commit comments