11// ---------------------------------------------------------------------------------------------------------------------
22// Imports
33// ---------------------------------------------------------------------------------------------------------------------
4+ using Microsoft . Extensions . DependencyInjection ;
45using Microsoft . Extensions . Logging ;
56using System . Collections . Concurrent ;
67using System . Reflection ;
@@ -9,19 +10,46 @@ namespace CodeOfChaos.Types;
910// ---------------------------------------------------------------------------------------------------------------------
1011// Code
1112// ---------------------------------------------------------------------------------------------------------------------
12- public abstract class OneTimeDataSeederService ( IServiceProvider serviceProvider , ILogger < OneTimeDataSeederService > logger ) : IDataSeederService {
13- private readonly ConcurrentQueue < SeederGroup > _seeders = [ ] ;
14- private readonly ConcurrentBag < Type > _seederTypes = [ ] ;
15- private bool _collectedRemainders ; // If set to true, the remainder seeders have been collected and thus should throw an exception if any are added
13+ public class OneTimeDataSeederService ( IServiceProvider serviceProvider , ILogger < OneTimeDataSeederService > logger ) : IDataSeederService {
14+ /// <summary>
15+ /// Represents a thread-safe queue of SeederGroup objects used within the OneTimeDataSeederService
16+ /// to maintain and manage seeding operations.
17+ /// </summary>
18+ /// <remarks>
19+ /// The <c>Seeders</c> field serves as the central storage for SeederGroups, allowing them to be
20+ /// enqueued and dequeued in a controlled manner during the seeding process. This ensures
21+ /// proper execution order and thread safety for concurrent operations.
22+ /// </remarks>
23+ protected readonly ConcurrentQueue < SeederGroup > Seeders = [ ] ;
24+
25+ /// <summary>
26+ /// Represents a thread-safe collection of seeder types used by the OneTimeDataSeederService to track
27+ /// registered data seeders. This collection ensures that each type of seeder is only added once
28+ /// and prevents duplicates during the seeding process. It plays a critical role in managing
29+ /// and validating the lifecycle of seeders added to the service.
30+ /// </summary>
31+ protected readonly ConcurrentBag < Type > SeederTypes = [ ] ;
32+
33+ /// <summary>
34+ /// Indicates whether the remainder seeders have been successfully collected.
35+ /// If set to <c>true</c>, it indicates that no additional remainder seeders can
36+ /// be added to the service, and attempting to do so will throw an exception.
37+ /// </summary>
38+ protected bool CollectedRemainders ; // If set to true, the remainder seeders have been collected and thus should throw an exception if any are added
1639
1740 // -----------------------------------------------------------------------------------------------------------------
1841 // Methods
1942 // -----------------------------------------------------------------------------------------------------------------
43+ /// <summary>
44+ /// Starts the data seeding process by executing all configured seeder groups in sequence.
45+ /// Validates seeders, collects data, and manages the lifecycle of each group using scoped services.
46+ /// </summary>
47+ /// <param name="ct">A CancellationToken used to observe cancellation requests.</param>
48+ /// <returns>A Task representing the asynchronous operation.</returns>
2049 public async Task StartAsync ( CancellationToken ct = default ) {
2150 logger . LogInformation ( "DataSeederService starting..." ) ;
22-
23- // User has to collect the seeders
24- await CollectAsync ( ct ) ;
51+
52+ await CollectAsync ( ct ) ; // If user choose the old format of adding seeders, this will be what ads the seeders to the queue
2553 ct . ThrowIfCancellationRequested ( ) ; // Don't throw during collection, but throw afterward
2654
2755 // Validation has to succeed before we continue
@@ -30,7 +58,7 @@ public async Task StartAsync(CancellationToken ct = default) {
3058 if ( ! ValidateSeeders ( ) ) return ;
3159
3260 int i = 0 ;
33- foreach ( SeederGroup seederGroup in _seeders ) {
61+ while ( Seeders . TryDequeue ( out SeederGroup seederGroup ) ) {
3462 if ( ct . IsCancellationRequested ) {
3563 logger . LogWarning ( "Seeding process canceled during execution." ) ;
3664 break ;
@@ -41,54 +69,79 @@ public async Task StartAsync(CancellationToken ct = default) {
4169 continue ;
4270 }
4371
44- logger . LogDebug ( "ExecutionStep {step} : {count} Seeder(s) found, executing..." , i ++ , seederGroup . Count ) ;
45- await Task . WhenAll ( seederGroup . Select ( seeder => seeder . StartAsync ( logger , ct ) ) ) ;
72+ // Each group should have their own scope
73+ using IServiceScope scope = serviceProvider . CreateScope ( ) ;
74+ IServiceProvider scopeProvider = scope . ServiceProvider ;
75+ List < ISeeder > seeders = [ ] ;
76+
77+ while ( seederGroup . SeederTypes . TryDequeue ( out Type ? seederType ) ) {
78+ var seeder = ( ISeeder ) scopeProvider . GetRequiredService ( seederType ) ;
79+ seeders . Add ( seeder ) ;
80+
81+ }
82+
83+ logger . LogDebug ( "ExecutionStep {step} : {count} Seeder(s) found, executing..." , i ++ , seeders . Count ) ;
84+ await Task . WhenAll ( seeders . Select ( seeder => seeder . StartAsync ( logger , ct ) ) ) ;
4685 }
4786
4887 logger . LogInformation ( "All seeders completed in {i} steps" , i ) ;
4988 // Cleanup
50- _seeders . Clear ( ) ;
51- _seederTypes . Clear ( ) ;
52- _collectedRemainders = false ;
89+ Seeders . Clear ( ) ;
90+ SeederTypes . Clear ( ) ;
91+ CollectedRemainders = false ;
5392 }
5493
94+ /// <summary>
95+ /// Asynchronously stops the OneTimeDataSeederService, handling any necessary cleanup or finalization logic.
96+ /// </summary>
97+ /// <param name="ct">The cancellation token to observe while awaiting the task to complete.</param>
98+ /// <returns>A task that completes when the service has been stopped.</returns>
5599 public Task StopAsync ( CancellationToken ct = default ) {
56100 logger . LogInformation ( "Stopping DataSeederService..." ) ;
57101 return Task . CompletedTask ;
58102 }
59103
104+ /// <summary>
105+ /// Collects seeders asynchronously to prepare for execution based on the seeding logic.
106+ /// This method is intended to be overridden for custom collection logic.
107+ /// </summary>
108+ /// <param name="ct">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
109+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
110+ protected virtual Task CollectAsync ( CancellationToken ct = default ) => Task . CompletedTask ;
111+
60112 // -----------------------------------------------------------------------------------------------------------------
61113 // Seeder manipulation Methods
62114 // -----------------------------------------------------------------------------------------------------------------
115+ /// <inheritdoc />
63116 public IDataSeederService AddSeeder < TSeeder > ( ) where TSeeder : ISeeder
64117 => AddSeederGroup ( group => group . AddSeeder < TSeeder > ( ) ) ;
65118
66- public IDataSeederService AddSeederGroup ( params ISeeder [ ] seeders )
67- => AddSeederGroup ( new SeederGroup ( seeders , serviceProvider ) ) ;
68-
119+ /// <inheritdoc />
69120 public IDataSeederService AddSeederGroup ( Action < SeederGroup > group ) {
70- var seeders = new SeederGroup ( serviceProvider ) ;
121+ var seeders = new SeederGroup ( ) ;
71122 group ( seeders ) ;
72123 return AddSeederGroup ( seeders ) ;
73124 }
74-
125+
126+ /// <inheritdoc />
75127 public IDataSeederService AddSeederGroup ( SeederGroup group ) {
76128 ThrowIfRemainderSeeders ( ) ;
77129
78- _seeders . Enqueue ( group ) ;
79- foreach ( ISeeder seeder in group ) {
80- _seederTypes . Add ( seeder . GetType ( ) ) ;
130+ Seeders . Enqueue ( group ) ;
131+ foreach ( Type seeder in group . SeederTypes . ToArray ( ) ) {
132+ SeederTypes . Add ( seeder ) ;
81133 }
82134
83135 return this ;
84136 }
85-
137+
138+ /// <inheritdoc />
86139 public void AddRemainderSeeders ( Assembly assembly ) {
87140 Type [ ] types = CollectTypes ( assembly ) ;
88141 var errors = new List < Exception > ( ) ;
89142
90143 foreach ( Type type in types ) {
91- if ( _seederTypes . Contains ( type ) ) {
144+ if ( SeederTypes . Contains ( type ) ) {
92145 logger . LogDebug ( "Skipping {t} as it was already assigned" , type ) ;
93146 continue ;
94147 }
@@ -104,23 +157,24 @@ public void AddRemainderSeeders(Assembly assembly) {
104157
105158 if ( errors . Count != 0 ) throw new AggregateException ( errors ) ;
106159
107- _collectedRemainders = true ;
160+ CollectedRemainders = true ;
108161 }
109-
162+
163+ /// <inheritdoc />
110164 public void AddRemainderSeedersAsOneGroup ( Assembly assembly ) {
111165 Type [ ] types = CollectTypes ( assembly ) ;
112- var group = new SeederGroup ( serviceProvider ) ;
166+ var group = new SeederGroup ( ) ;
113167 var errors = new List < Exception > ( ) ;
114168
115169 foreach ( Type type in types ) {
116- if ( _seederTypes . Contains ( type ) ) {
170+ if ( SeederTypes . Contains ( type ) ) {
117171 logger . LogDebug ( "Skipping {t} as it was already assigned" , type ) ;
118172 continue ;
119173 }
120174
121175 try {
122176 group . AddSeeder ( type ) ;
123- _seederTypes . Add ( type ) ;
177+ SeederTypes . Add ( type ) ;
124178 }
125179 catch ( Exception ex ) {
126180 logger . LogError ( ex , "Failed to instantiate {t}. Skipping..." , type ) ;
@@ -132,11 +186,9 @@ public void AddRemainderSeedersAsOneGroup(Assembly assembly) {
132186
133187 // Collect as one Concurrent step
134188 AddSeederGroup ( group ) ;
135- _collectedRemainders = true ;
189+ CollectedRemainders = true ;
136190 }
137191
138- protected virtual Task CollectAsync ( CancellationToken ct = default ) => Task . CompletedTask ;
139-
140192 private static Type [ ] CollectTypes ( Assembly assembly )
141193 => assembly . GetTypes ( )
142194 // order is deterministic
@@ -146,14 +198,14 @@ private static Type[] CollectTypes(Assembly assembly)
146198 . ToArray ( ) ;
147199
148200 private void ThrowIfRemainderSeeders ( ) {
149- if ( ! _collectedRemainders ) return ;
201+ if ( ! CollectedRemainders ) return ;
150202
151203 logger . LogError ( "Remainder seeders have already been collected" ) ;
152204 throw new InvalidOperationException ( "Remainder seeders have already been collected" ) ;
153205 }
154206
155207 protected virtual bool ValidateSeeders ( ) {
156- if ( ! _seeders . IsEmpty ) return true ;
208+ if ( ! Seeders . IsEmpty ) return true ;
157209
158210 logger . LogWarning ( "No seeders were added prior to execution." ) ;
159211 return false ;
0 commit comments