diff --git a/.github/workflows/dotnet-publish.yml b/.github/workflows/dotnet-publish.yml
index 0904996..827aaf5 100644
--- a/.github/workflows/dotnet-publish.yml
+++ b/.github/workflows/dotnet-publish.yml
@@ -3,14 +3,14 @@ name: Publish NuGet package
on:
push:
- tags:
+ tags:
- 'v*.*.*'
workflow_dispatch:
env:
ARTIFACTS_FEED_URL: https://api.nuget.org/v3/index.json
BUILD_CONFIGURATION: "Release"
- DOTNET_VERSION: "6.x"
+ DOTNET_VERSION: "8.x"
jobs:
build-and-deploy:
@@ -28,14 +28,14 @@ jobs:
source-url: ${{ env.ARTIFACTS_FEED_URL }}
env:
NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY_NEOLUTION }}
-
+
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.7
with:
versionSpec: '5.10.3'
-
+
- name: Determine version number
- uses: gittools/actions/gitversion/execute@v0.9.7
+ uses: gittools/actions/gitversion/execute@v0.9.7
- name: Build and pack
run: |
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index db5927d..7e8c1ff 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,6 +1,6 @@
name: CI
-on:
+on:
push:
branches: [ main ]
pull_request:
@@ -9,7 +9,7 @@ on:
env:
BUILD_CONFIGURATION: "Release"
- DOTNET_VERSION: "6.x"
+ DOTNET_VERSION: "8.x"
jobs:
build:
diff --git a/Neolution.Extensions.DataSeeding.Demo/.editorconfig b/Neolution.Extensions.DataSeeding.Demo/.editorconfig
new file mode 100644
index 0000000..62bf1d5
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/.editorconfig
@@ -0,0 +1,3 @@
+[*.{cs,vb}]
+# There is no synchronization context in our console apps, so we do not need to use ConfigureAwait(false)
+dotnet_diagnostic.CA2007.severity = none
\ No newline at end of file
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitCommand.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitCommand.cs
similarity index 58%
rename from Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitCommand.cs
rename to Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitCommand.cs
index 766bdb5..772d247 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitCommand.cs
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitCommand.cs
@@ -1,6 +1,7 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init
{
using System;
+ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.DotNet.Console.Abstractions;
@@ -9,7 +10,7 @@
///
/// The data initializer.
///
- public class InitCommand : IAsyncConsoleAppCommand
+ public class InitCommand : IDotNetConsoleCommand
{
///
/// The logger
@@ -33,31 +34,24 @@ public InitCommand(ILogger logger, ISeeder seeder)
}
///
- public Task RunAsync(InitOptions options)
+ public Task RunAsync(InitOptions options, CancellationToken cancellationToken)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
- return this.RunInternalAsync();
- }
+ return RunInternalAsync();
- ///
- /// Runs the command asynchronously.
- ///
- /// An awaitable .
- private async Task RunInternalAsync()
- {
- this.logger.LogInformation("Start data initializer...");
-
- // Automatic seeding
- await this.seeder.SeedAsync().ConfigureAwait(true);
+ async Task RunInternalAsync()
+ {
+ this.logger.LogInformation("Start data initializer...");
- // Manual seeding
- await this.seeder.SeedAsync().ConfigureAwait(true);
+ // Automatic seeding
+ await this.seeder.SeedAsync();
- this.logger.LogInformation("Data initializer finished!");
+ this.logger.LogInformation("Data initializer finished!");
+ }
}
}
}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitOptions.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitOptions.cs
similarity index 83%
rename from Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitOptions.cs
rename to Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitOptions.cs
index 8e71051..63e60a3 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/InitOptions.cs
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/InitOptions.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init
{
using CommandLine;
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/CategoriesSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/CategoriesSeed.cs
new file mode 100644
index 0000000..ccb61ce
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/CategoriesSeed.cs
@@ -0,0 +1,36 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates content categories for organizing articles and pages
+ /// Depends on system configuration for category settings
+ ///
+ [DependsOn(typeof(SystemConfigurationSeed))]
+ public class CategoriesSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public CategoriesSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating content categories: News, Blog, Products, About...");
+ await Task.Delay(120);
+ this.logger.LogInformation("Content categories created successfully");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentMetadataSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentMetadataSeed.cs
new file mode 100644
index 0000000..039d6be
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentMetadataSeed.cs
@@ -0,0 +1,39 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates content metadata and SEO tags
+ /// Has no dependencies but is used by ContentSeed for metadata assignment
+ ///
+ public class ContentMetadataSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public ContentMetadataSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating SEO metadata templates: title patterns, meta descriptions...");
+ await Task.Delay(150);
+ this.logger.LogInformation("Setting up content tags: technology, business, tutorial, news...");
+ await Task.Delay(100);
+ this.logger.LogInformation("Configuring schema.org structured data templates...");
+ await Task.Delay(100);
+ this.logger.LogInformation("Content metadata and SEO configuration completed");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentSeed.cs
new file mode 100644
index 0000000..50d3fbb
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentSeed.cs
@@ -0,0 +1,36 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates sample content including pages and articles
+ /// Depends on both categories and users being available (demonstrates multiple dependencies)
+ ///
+ [DependsOn(typeof(CategoriesSeed), typeof(UsersSeed), typeof(ContentTemplatesSeed), typeof(ContentMetadataSeed))]
+ public class ContentSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public ContentSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating sample content: Home page, About page, sample blog posts...");
+ await Task.Delay(250);
+ this.logger.LogInformation("Sample content created and assigned to categories and authors");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentTemplatesSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentTemplatesSeed.cs
new file mode 100644
index 0000000..2cad378
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/ContentTemplatesSeed.cs
@@ -0,0 +1,40 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates content templates and workflow configurations
+ /// Depends on UserRolesSeed for permission setup, used by ContentSeed and MenusSeed
+ ///
+ [DependsOn(typeof(UserRolesSeed))]
+ public class ContentTemplatesSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public ContentTemplatesSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating content templates: Article template, Page template, Blog post template...");
+ await Task.Delay(200);
+ this.logger.LogInformation("Setting up content workflows: Draft → Review → Published states...");
+ await Task.Delay(150);
+ this.logger.LogInformation("Configuring template permissions based on user roles...");
+ await Task.Delay(100);
+ this.logger.LogInformation("Content templates and workflows created successfully");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/DatabaseMigrationSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/DatabaseMigrationSeed.cs
new file mode 100644
index 0000000..620286f
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/DatabaseMigrationSeed.cs
@@ -0,0 +1,35 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Sets up the database schema and initial configuration
+ /// This seed runs first and has no dependencies
+ ///
+ public class DatabaseMigrationSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public DatabaseMigrationSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Setting up database schema: creating tables for users, content, categories, menus...");
+ await Task.Delay(200);
+ this.logger.LogInformation("Database migration completed successfully");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaFilesSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaFilesSeed.cs
new file mode 100644
index 0000000..7bfafd2
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaFilesSeed.cs
@@ -0,0 +1,35 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Uploads sample media files like images and documents
+ /// Has no dependencies and can run independently
+ ///
+ public class MediaFilesSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public MediaFilesSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Uploading sample media files: logo.png, hero-image.jpg, sample-document.pdf...");
+ await Task.Delay(300);
+ this.logger.LogInformation("Sample media files uploaded to media library");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaProcessingSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaProcessingSeed.cs
new file mode 100644
index 0000000..2d0cd5d
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MediaProcessingSeed.cs
@@ -0,0 +1,38 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Sets up media processing configuration and creates thumbnails for uploaded media files
+ /// Depends on MediaFilesSeed to have files available for processing
+ ///
+ [DependsOn(typeof(MediaFilesSeed))]
+ public class MediaProcessingSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public MediaProcessingSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Configuring media processing settings: thumbnail sizes, compression levels...");
+ await Task.Delay(200);
+ this.logger.LogInformation("Generating thumbnails for uploaded media files...");
+ await Task.Delay(300);
+ this.logger.LogInformation("Media processing configuration completed and thumbnails generated");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MenusSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MenusSeed.cs
new file mode 100644
index 0000000..a751309
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/MenusSeed.cs
@@ -0,0 +1,36 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates navigation menus and menu structures
+ /// Depends on ContentTemplatesSeed for menu templates
+ ///
+ [DependsOn(typeof(ContentTemplatesSeed))]
+ public class MenusSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public MenusSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating navigation menus: main menu, footer menu, admin menu...");
+ await Task.Delay(120);
+ this.logger.LogInformation("Navigation menus created and configured with templates");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SettingsSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SettingsSeed.cs
new file mode 100644
index 0000000..bdbe7f0
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SettingsSeed.cs
@@ -0,0 +1,43 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Configures site-wide settings like theme, language, SEO options
+ /// Depends on MenusSeed to configure theme settings for menus
+ ///
+ [DependsOn(typeof(MenusSeed))]
+ public class SettingsSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public SettingsSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Configuring site theme to 'Professional Dark'...");
+ await Task.Delay(150);
+
+ this.logger.LogInformation("Setting default language to 'English', timezone to 'UTC'...");
+ await Task.Delay(100);
+
+ this.logger.LogInformation("Configuring SEO settings: meta titles, descriptions, keywords...");
+ await Task.Delay(120);
+
+ this.logger.LogInformation("Site-wide settings configuration completed");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SystemConfigurationSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SystemConfigurationSeed.cs
new file mode 100644
index 0000000..ff3b7f2
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/SystemConfigurationSeed.cs
@@ -0,0 +1,36 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Sets up basic system configuration and application settings
+ /// Depends on database migration being completed first
+ ///
+ [DependsOn(typeof(DatabaseMigrationSeed))]
+ public class SystemConfigurationSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public SystemConfigurationSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Configuring system settings: site name, timezone, locale, email settings...");
+ await Task.Delay(150);
+ this.logger.LogInformation("System configuration completed");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserProfilesSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserProfilesSeed.cs
new file mode 100644
index 0000000..2a8d65c
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserProfilesSeed.cs
@@ -0,0 +1,40 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates user profiles and personalization settings
+ /// Depends on UsersSeed to have users available for profile creation
+ ///
+ [DependsOn(typeof(UsersSeed))]
+ public class UserProfilesSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public UserProfilesSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating user profiles: bio, avatar, contact information...");
+ await Task.Delay(200);
+ this.logger.LogInformation("Setting up user preferences: theme, language, notifications...");
+ await Task.Delay(150);
+ this.logger.LogInformation("Configuring user dashboard layouts and widgets...");
+ await Task.Delay(100);
+ this.logger.LogInformation("User profiles and personalization settings created successfully");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserRolesSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserRolesSeed.cs
new file mode 100644
index 0000000..535de4f
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UserRolesSeed.cs
@@ -0,0 +1,36 @@
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Creates user roles for the CMS application
+ /// Depends on system configuration being set up first
+ ///
+ [DependsOn(typeof(SystemConfigurationSeed))]
+ public class UserRolesSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ public UserRolesSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Creating user roles: Administrator, Editor, Contributor, Subscriber...");
+ await Task.Delay(100);
+ this.logger.LogInformation("User roles created successfully");
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/UsersSeed.cs b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UsersSeed.cs
similarity index 55%
rename from Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/UsersSeed.cs
rename to Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UsersSeed.cs
index ccc2cf3..5361e87 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/UsersSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Demo/Commands/Init/Seeds/UsersSeed.cs
@@ -1,11 +1,14 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
+namespace Neolution.Extensions.DataSeeding.Demo.Commands.Init.Seeds
{
- using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- ///
+ ///
+ /// Creates initial administrator and sample users
+ /// Depends on user roles being available
+ ///
+ [DependsOn(typeof(UserRolesSeed))]
public class UsersSeed : ISeed
{
///
@@ -23,13 +26,11 @@ public UsersSeed(ILogger logger)
}
///
- public Type DependsOn => typeof(TenantsSeed);
-
- ///
- public Task SeedAsync()
+ public async Task SeedAsync()
{
- this.logger.LogInformation($"Seed: {nameof(UsersSeed)}");
- return Task.CompletedTask;
+ this.logger.LogInformation("Creating initial users: admin@example.com, editor@example.com...");
+ await Task.Delay(180);
+ this.logger.LogInformation("Initial users created and assigned to roles");
}
}
}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Dockerfile b/Neolution.Extensions.DataSeeding.Demo/Dockerfile
similarity index 100%
rename from Neolution.Extensions.DataSeeding.Sample/Dockerfile
rename to Neolution.Extensions.DataSeeding.Demo/Dockerfile
diff --git a/Neolution.Extensions.DataSeeding.Sample/Neolution.Extensions.DataSeeding.Sample.csproj b/Neolution.Extensions.DataSeeding.Demo/Neolution.Extensions.DataSeeding.Demo.csproj
similarity index 72%
rename from Neolution.Extensions.DataSeeding.Sample/Neolution.Extensions.DataSeeding.Sample.csproj
rename to Neolution.Extensions.DataSeeding.Demo/Neolution.Extensions.DataSeeding.Demo.csproj
index e277add..7447c68 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Neolution.Extensions.DataSeeding.Sample.csproj
+++ b/Neolution.Extensions.DataSeeding.Demo/Neolution.Extensions.DataSeeding.Demo.csproj
@@ -1,16 +1,15 @@
Exe
- net6.0
+ net8.0
0BF50422-E767-4A00-8B67-38947EC07377
default
true
-
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -30,4 +29,10 @@
PreserveNewest
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Neolution.Extensions.DataSeeding.Sample/Program.cs b/Neolution.Extensions.DataSeeding.Demo/Program.cs
similarity index 56%
rename from Neolution.Extensions.DataSeeding.Sample/Program.cs
rename to Neolution.Extensions.DataSeeding.Demo/Program.cs
index 88cd1fc..49b19bf 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Program.cs
+++ b/Neolution.Extensions.DataSeeding.Demo/Program.cs
@@ -1,8 +1,8 @@
-namespace Neolution.Extensions.DataSeeding.Sample
+namespace Neolution.Extensions.DataSeeding.Demo
{
using System;
+ using System.Threading.Tasks;
using Neolution.DotNet.Console;
- using Neolution.DotNet.Console.Abstractions;
using NLog;
using NLog.Extensions.Logging;
@@ -15,14 +15,19 @@ public static class Program
/// Defines the entry point of the application.
///
/// The arguments.
- public static void Main(string[] args)
+ /// A task representing the asynchronous operation.
+ public static async Task Main(string[] args)
{
- var console = CreateConsoleAppBuilder(args).AsyncBuild();
- var logger = LogManager.Setup().LoadConfigurationFromSection(console.Configuration).GetCurrentClassLogger();
+ var builder = DotNetConsole.CreateDefaultBuilder(args);
+ var logger = LogManager.Setup().LoadConfigurationFromSection(builder.Configuration).GetCurrentClassLogger();
try
{
- console.RunAsync();
+ var startup = new Startup();
+ startup.ConfigureServices(builder.Services);
+
+ var console = builder.Build();
+ await console.RunAsync();
}
catch (Exception ex)
{
@@ -36,16 +41,5 @@ public static void Main(string[] args)
LogManager.Shutdown();
}
}
-
- ///
- /// Creates the console application builder.
- ///
- /// The arguments.
- /// The console app builder.
- private static IConsoleAppBuilder CreateConsoleAppBuilder(string[] args)
- {
- return DotNetConsole.CreateDefaultBuilder(args)
- .UseCompositionRoot();
- }
}
}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Properties/launchSettings.json b/Neolution.Extensions.DataSeeding.Demo/Properties/launchSettings.json
similarity index 74%
rename from Neolution.Extensions.DataSeeding.Sample/Properties/launchSettings.json
rename to Neolution.Extensions.DataSeeding.Demo/Properties/launchSettings.json
index f7e2f76..7499d34 100644
--- a/Neolution.Extensions.DataSeeding.Sample/Properties/launchSettings.json
+++ b/Neolution.Extensions.DataSeeding.Demo/Properties/launchSettings.json
@@ -1,6 +1,6 @@
{
"profiles": {
- "Neolution.Extensions.DataSeeding.Sample": {
+ "DataSeeding Demo App": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
diff --git a/Neolution.Extensions.DataSeeding.Demo/README.md b/Neolution.Extensions.DataSeeding.Demo/README.md
new file mode 100644
index 0000000..ccba5f4
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/README.md
@@ -0,0 +1,156 @@
+# DataSeeding Demo
+
+This demo shows how to use the `Neolution.Extensions.DataSeeding` library with a Content Management System (CMS) seeding scenario. It demonstrates dependency resolution with 13 interconnected seeds.
+
+## Overview
+
+The demo creates a fictional CMS environment with seeds that handle everything from database setup to final configuration. It shows how the framework manages dependency chains while keeping code organized.
+
+## What Gets Seeded
+
+The seeds follow a technical dependency hierarchy:
+
+```mermaid
+flowchart TD
+ A[DatabaseMigrationSeed] --> D[SystemConfigurationSeed]
+ B[MediaFilesSeed] --> E[MediaProcessingSeed]
+ C[ContentMetadataSeed]
+
+ D --> F[CategoriesSeed]
+ D --> G[UserRolesSeed]
+ G --> H[ContentTemplatesSeed]
+ G --> I[UsersSeed]
+ I --> J[UserProfilesSeed]
+
+ F --> K[ContentSeed]
+ I --> K
+ H --> K
+ C --> K
+
+ K --> L[MenusSeed]
+ H --> L
+ L --> M[SettingsSeed]
+
+ classDef independent fill:#e8f4fd
+ classDef single fill:#fff2e8
+ classDef multiple fill:#ffeaa7
+ classDef deep fill:#fab1a0
+
+ class A,B,C independent
+ class D,E,F,G,I,J single
+ class H,L multiple
+ class K,M deep
+```
+
+- **Independent** (Light Blue): No dependencies, can run first
+- **Single Dependency** (Light Orange): Depend on exactly one other seed
+- **Multiple Dependencies** (Light Yellow): Depend on 2+ seeds at same level
+- **Deep Dependencies** (Light Red): Depend on seeds that themselves have dependencies
+
+## Key Features Demonstrated
+
+### 1. Complex Multi-Dependencies
+
+The `ContentSeed` shows how a single seed can depend on multiple other seeds:
+
+```csharp
+[DependsOn(typeof(CategoriesSeed), typeof(UsersSeed),
+ typeof(ContentTemplatesSeed), typeof(ContentMetadataSeed))]
+public class ContentSeed : ISeed
+{
+ // Implementation
+}
+```
+
+### 2. Shared Dependencies
+
+`ContentTemplatesSeed` is used by both `ContentSeed` and `MenusSeed`, showing how seeds can be shared across different parts of the system.
+
+### 3. Sequential Execution
+
+Seeds are executed sequentially in dependency order. Independent seeds (those with no dependencies) like `DatabaseMigrationSeed`, `MediaFilesSeed`, and `ContentMetadataSeed` can be processed first, but the framework executes them one by one to maintain consistency.
+
+### 4. Realistic Timing
+
+Each seed includes delays to simulate actual database operations, file processing, and API calls.
+
+## Running the Demo
+
+### Prerequisites
+
+- .NET 8.0 or later
+- Visual Studio 2022 or VS Code
+
+### Quick Start
+
+```bash
+# Navigate to the demo project
+cd Neolution.Extensions.DataSeeding.Demo
+
+# Run the initialization command
+dotnet run init
+```
+
+## Code Structure
+
+### Seed Implementation Pattern
+
+Each seed follows a consistent pattern:
+
+```csharp
+[DependsOn(typeof(DependencySeed))]
+public class ExampleSeed : ISeed
+{
+ private readonly ILogger logger;
+
+ public ExampleSeed(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Starting seed operation...");
+ await Task.Delay(200); // Simulate work
+ this.logger.LogInformation("Seed operation completed");
+ }
+}
+```
+
+### Dependency Injection Setup
+
+The demo uses standard .NET dependency injection configured in `Startup.cs`:
+
+```csharp
+public void ConfigureServices(IServiceCollection services)
+{
+ services.AddDataSeeding();
+ // Additional service registrations...
+}
+```
+
+**Note**: The parameterless `AddDataSeeding()` method automatically detects seeds in the calling assembly. For cross-assembly scenarios, use `AddDataSeeding(assembly)`.
+
+## What You'll Learn
+
+By studying this demo, you'll understand:
+
+1. **Dependency Management**: How to structure seeds with complex interdependencies
+2. **Execution Order**: How the framework automatically resolves and orders seed execution
+3. **Real-World Patterns**: Practical examples of data seeding in applications
+4. **Performance**: How the framework handles execution timing
+
+## Extending the Demo
+
+You can easily add your own seeds by:
+
+1. Creating a new class implementing `ISeed`
+2. Adding `[DependsOn(typeof(...))]` attributes for dependencies
+3. Implementing the `SeedAsync()` method
+4. Running the demo to see your seed in the execution order
+
+The framework will automatically detect and include your new seeds in the dependency resolution process.
+
+---
+
+**Need Help?** Check the main library documentation or explore the seed implementations in the `Commands/Init/Seeds/` directory for detailed examples.
diff --git a/Neolution.Extensions.DataSeeding.Demo/Startup.cs b/Neolution.Extensions.DataSeeding.Demo/Startup.cs
new file mode 100644
index 0000000..d5cf1e8
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/Startup.cs
@@ -0,0 +1,20 @@
+namespace Neolution.Extensions.DataSeeding.Demo
+{
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ /// The startup class, composition root for the application.
+ ///
+ public class Startup
+ {
+ ///
+ /// Configures the services.
+ ///
+ /// The services.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("SonarLint", "S2325", Justification = "Method is part of ICompositionRoot interface contract")]
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddDataSeeding();
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Sample/appsettings.Development.json b/Neolution.Extensions.DataSeeding.Demo/appsettings.Development.json
similarity index 100%
rename from Neolution.Extensions.DataSeeding.Sample/appsettings.Development.json
rename to Neolution.Extensions.DataSeeding.Demo/appsettings.Development.json
diff --git a/Neolution.Extensions.DataSeeding.Sample/appsettings.Production.json b/Neolution.Extensions.DataSeeding.Demo/appsettings.Production.json
similarity index 100%
rename from Neolution.Extensions.DataSeeding.Sample/appsettings.Production.json
rename to Neolution.Extensions.DataSeeding.Demo/appsettings.Production.json
diff --git a/Neolution.Extensions.DataSeeding.Sample/appsettings.json b/Neolution.Extensions.DataSeeding.Demo/appsettings.json
similarity index 95%
rename from Neolution.Extensions.DataSeeding.Sample/appsettings.json
rename to Neolution.Extensions.DataSeeding.Demo/appsettings.json
index 6a320a6..834fee8 100644
--- a/Neolution.Extensions.DataSeeding.Sample/appsettings.json
+++ b/Neolution.Extensions.DataSeeding.Demo/appsettings.json
@@ -12,9 +12,6 @@
"extensions": [
{
"assembly": "Neolution.DotNet.Console"
- },
- {
- "assembly": "NLog.AWS.Logger"
}
],
"targets": {
diff --git a/Neolution.Extensions.DataSeeding.Demo/packages.lock.json b/Neolution.Extensions.DataSeeding.Demo/packages.lock.json
new file mode 100644
index 0000000..44051eb
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Demo/packages.lock.json
@@ -0,0 +1,374 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net8.0": {
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Direct",
+ "requested": "[8.*, )",
+ "resolved": "8.0.1",
+ "contentHash": "7tYqdPPpAK+3jO9d5LTuCK2VxrEdf85Ol4trUr6ds4jclBecadWZ/RyPCbNjfbN5iGTfUnD/h65TOQuqQv2c+A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Json": "8.0.1",
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
+ "Microsoft.Extensions.FileProviders.Physical": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Direct",
+ "requested": "[8.*, )",
+ "resolved": "8.0.1",
+ "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Options": "8.0.2"
+ }
+ },
+ "Neolution.CodeAnalysis": {
+ "type": "Direct",
+ "requested": "[3.2.1, )",
+ "resolved": "3.2.1",
+ "contentHash": "AQDkBJ9e6TrnhpwTtf4KrwlAPBLy77I5XkNey586nN2IFfjk1LVwBwvOcjORD1dSNtEhOf69JQjdmiMf54C90w==",
+ "dependencies": {
+ "SonarAnalyzer.CSharp": "9.20.0.85982",
+ "StyleCop.Analyzers.Unstable": "1.2.0.556"
+ }
+ },
+ "Neolution.DotNet.Console": {
+ "type": "Direct",
+ "requested": "[5.0.0, )",
+ "resolved": "5.0.0",
+ "contentHash": "4l5H0Sgxr5mJrde4MjtvXzlJHGRbHz9tWp4JMuJkE1xCYFvDY48incEARo0cynDMrm+t6y7TSQpkrJLwsUQbMQ==",
+ "dependencies": {
+ "CommandLineParser": "2.9.1",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.CommandLine": "8.0.0",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0",
+ "Microsoft.Extensions.Configuration.Json": "8.0.1",
+ "Microsoft.Extensions.Configuration.UserSecrets": "8.0.1",
+ "Microsoft.Extensions.DependencyInjection": "8.0.1",
+ "Microsoft.Extensions.Hosting": "8.0.1",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Configuration": "8.0.1",
+ "NLog": "5.3.4",
+ "NLog.Extensions.Logging": "5.3.15",
+ "Scrutor": "6.0.1"
+ }
+ },
+ "CommandLineParser": {
+ "type": "Transitive",
+ "resolved": "2.9.1",
+ "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA=="
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "8.0.2",
+ "contentHash": "7IQhGK+wjyGrNsPBjJcZwWAr+Wf6D4+TwOptUt77bWtgNkiV8tDEbhFS+dDamtQFZ2X7kWG9m71iZQRj2x3zgQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "EJzSNO9oaAXnTdtdNO6npPRsIIeZCBSNmdQ091VDO7fBiOtJAAeEq6dtrVXIi3ZyjC5XRSAtVvF8SzcneRHqKQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
+ "Microsoft.Extensions.FileProviders.Physical": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1",
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.2",
+ "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "8.0.2",
+ "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Options": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
+ "Microsoft.Extensions.FileSystemGlobbing": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "bP9EEkHBEfjgYiG8nUaXqMk/ujwJrffOkNPP7onpRMO8R+OUSESSP4xHkCAXgYZ1COP2Q9lXlU5gkMFh20gRuw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Binder": "8.0.2",
+ "Microsoft.Extensions.Configuration.CommandLine": "8.0.0",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0",
+ "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1",
+ "Microsoft.Extensions.Configuration.Json": "8.0.1",
+ "Microsoft.Extensions.Configuration.UserSecrets": "8.0.1",
+ "Microsoft.Extensions.DependencyInjection": "8.0.1",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Diagnostics": "8.0.1",
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
+ "Microsoft.Extensions.FileProviders.Physical": "8.0.0",
+ "Microsoft.Extensions.Hosting.Abstractions": "8.0.1",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging.Configuration": "8.0.1",
+ "Microsoft.Extensions.Logging.Console": "8.0.1",
+ "Microsoft.Extensions.Logging.Debug": "8.0.1",
+ "Microsoft.Extensions.Logging.EventLog": "8.0.1",
+ "Microsoft.Extensions.Logging.EventSource": "8.0.1",
+ "Microsoft.Extensions.Options": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1",
+ "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.3",
+ "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "QWwTrsgOnJMmn+XUslm8D2H1n3PkP/u/v52FODtyBc/k4W9r3i2vcXXeeX/upnzllJYRRbrzVzT0OclfNJtBJA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "8.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Binder": "8.0.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Options": "8.0.2",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "uzcg/5U2eLyn5LIKlERkdSxw6VPC1yydnOSQiRRWGBGN3kphq3iL4emORzrojScDmxRhv49gp5BI8U3Dz7y4iA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging.Configuration": "8.0.1",
+ "Microsoft.Extensions.Options": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "B8hqNuYudC2RB+L/DI33uO4rf5by41fZVdcVL2oZj0UyoAZqnwTwYHp1KafoH4nkl1/23piNeybFFASaV2HkFg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "ZD1m4GXoxcZeDJIq8qePKj+QAWeQNO/OG8skvrOG8RQfxLp9MAKRoliTc27xanoNUzeqvX5HhS/I7c0BvwAYUg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Options": "8.0.2",
+ "System.Diagnostics.EventLog": "8.0.1"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "YMXMAla6B6sEf/SnfZYTty633Ool3AH7KOw2LOaaEqwSo2piK4f7HMtzyc3CNiipDnq1fsUSuG5Oc7ZzpVy8WQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Logging": "8.0.1",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.2",
+ "Microsoft.Extensions.Options": "8.0.2",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "8.0.2",
+ "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Configuration.Binder": "8.0.0",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ },
+ "NLog": {
+ "type": "Transitive",
+ "resolved": "5.3.4",
+ "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A=="
+ },
+ "NLog.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "5.3.15",
+ "contentHash": "BSCkfWjaTesw4/k8Ujj3YdqtDzd2iCRYIOwoHume7aaJFz4/gLbemA5MLgkAwtCxcd8nGBGfaEkeIzQPGxArLg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Logging": "8.0.0",
+ "NLog": "5.3.4"
+ }
+ },
+ "Scrutor": {
+ "type": "Transitive",
+ "resolved": "6.1.0",
+ "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1",
+ "Microsoft.Extensions.DependencyModel": "8.0.2"
+ }
+ },
+ "SonarAnalyzer.CSharp": {
+ "type": "Transitive",
+ "resolved": "9.20.0.85982",
+ "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q=="
+ },
+ "StyleCop.Analyzers.Unstable": {
+ "type": "Transitive",
+ "resolved": "1.2.0.556",
+ "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "n1ZP7NM2Gkn/MgD8+eOT5MulMj6wfeQMNS2Pizvq5GHCZfjlFMXV2irQlQmJhwA2VABC57M0auudO89Iu2uRLg=="
+ },
+ "neolution.extensions.dataseeding": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "[8.*, )",
+ "Scrutor": "[6.1.0, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/MySeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/MySeed.cs
deleted file mode 100644
index 98cfa9c..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/MySeed.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds;
-
- ///
- public class MySeed : Seed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public MySeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public override async Task SeedAsync()
- {
- this.logger.LogInformation("Start Seed");
-
- await SeedAsync().ConfigureAwait(true);
- await SeedAsync().ConfigureAwait(true);
- await SeedAsync().ConfigureAwait(true);
-
- this.logger.LogInformation("Seed finished!");
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/ApplicationSettingsSeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/ApplicationSettingsSeed.cs
deleted file mode 100644
index 5b3fb91..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/ApplicationSettingsSeed.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- public class ApplicationSettingsSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public ApplicationSettingsSeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation($"Seed: {nameof(ApplicationSettingsSeed)}");
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/MasterSeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/MasterSeed.cs
deleted file mode 100644
index 64f1494..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/MasterSeed.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- public class MasterSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public MasterSeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public int Priority => 2;
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation($"Seed: {nameof(MasterSeed)}");
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/PermissionsSeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/PermissionsSeed.cs
deleted file mode 100644
index 79603d4..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/PermissionsSeed.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
-{
- using System;
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- public class PermissionsSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public PermissionsSeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public Type DependsOn => typeof(UsersSeed);
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation($"Seed: {nameof(PermissionsSeed)}");
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSeed.cs
deleted file mode 100644
index eefd778..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSeed.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- public class TenantsSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public TenantsSeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation($"Seed: {nameof(TenantsSeed)}");
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSettingsSeed.cs b/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSettingsSeed.cs
deleted file mode 100644
index a53d938..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Commands/Init/Seeds/TenantsSettingsSeed.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample.Commands.Init.Seeds
-{
- using System;
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- public class TenantsSettingsSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public TenantsSettingsSeed(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public Type DependsOn => typeof(TenantsSeed);
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation($"Seed: {nameof(TenantsSettingsSeed)}");
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/README.md b/Neolution.Extensions.DataSeeding.Sample/README.md
deleted file mode 100644
index 1ce42cc..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Introduction
-
-Please describe here the tool.
-
-## Build & Deploy
-
-Describe how to deploy into production (and staging)
diff --git a/Neolution.Extensions.DataSeeding.Sample/Startup.cs b/Neolution.Extensions.DataSeeding.Sample/Startup.cs
deleted file mode 100644
index a2af831..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/Startup.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Sample
-{
- using Microsoft.Extensions.DependencyInjection;
- using Neolution.DotNet.Console.Abstractions;
-
- ///
- public class Startup : ICompositionRoot
- {
- ///
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDataSeeding(typeof(Startup).Assembly);
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.Sample/packages.lock.json b/Neolution.Extensions.DataSeeding.Sample/packages.lock.json
deleted file mode 100644
index 33420ad..0000000
--- a/Neolution.Extensions.DataSeeding.Sample/packages.lock.json
+++ /dev/null
@@ -1,335 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- "net6.0": {
- "AWS.Logger.NLog": {
- "type": "Direct",
- "requested": "[3.1.0, )",
- "resolved": "3.1.0",
- "contentHash": "eDS/rjV7Kubyc6diXHEvhkrBti3/ItkSuKtPduNC8Ab3W23GIjMUzg1irWn1Y7KHXr3CI4d48Cfpb6ut2eelog==",
- "dependencies": {
- "AWS.Logger.Core": "3.1.0",
- "NLog": "4.5.0"
- }
- },
- "Microsoft.Extensions.Configuration.UserSecrets": {
- "type": "Direct",
- "requested": "[6.0.2, )",
- "resolved": "6.0.2",
- "contentHash": "q3zRs1RbaNRHvDaooOOdJs4KMbaVg6MJUgPD60p1DuOMDjob5LbbBYRZwdaas/TrkCUZ7vYSBn3EXrrDWY1HSA==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.1",
- "Microsoft.Extensions.Configuration.Json": "6.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "6.0.1",
- "Microsoft.Extensions.FileProviders.Physical": "6.0.1"
- }
- },
- "Microsoft.Extensions.Logging": {
- "type": "Direct",
- "requested": "[6.0.1, )",
- "resolved": "6.0.1",
- "contentHash": "k6tbYaHrqY9kq7p5FfpPbddY1OImPCpXQ/PGcED6N9s5ULRp8n1PdmMzsIwIzCnhIS5bs06G/lO9LfNVpUj8jg==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection": "6.0.2",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.Logging.Abstractions": "6.0.4",
- "Microsoft.Extensions.Options": "6.0.1",
- "System.Diagnostics.DiagnosticSource": "6.0.2"
- }
- },
- "Neolution.CodeAnalysis": {
- "type": "Direct",
- "requested": "[3.2.1, )",
- "resolved": "3.2.1",
- "contentHash": "AQDkBJ9e6TrnhpwTtf4KrwlAPBLy77I5XkNey586nN2IFfjk1LVwBwvOcjORD1dSNtEhOf69JQjdmiMf54C90w==",
- "dependencies": {
- "SonarAnalyzer.CSharp": "9.20.0.85982",
- "StyleCop.Analyzers.Unstable": "1.2.0.556"
- }
- },
- "Neolution.DotNet.Console": {
- "type": "Direct",
- "requested": "[1.1.0-beta0001, )",
- "resolved": "1.1.0-beta0001",
- "contentHash": "lmZZFVGQYNAeBu/Km3I2RhMnqKZScI5gYADXCknBs3tnMymQISh0KIsVUOqnSe/wfirKu2HH/1TelCjXrya6lQ==",
- "dependencies": {
- "CommandLineParser": "2.8.0",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
- "Microsoft.Extensions.Configuration.CommandLine": "6.0.0",
- "Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
- "Microsoft.Extensions.Configuration.Json": "6.0.0",
- "Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
- "Microsoft.Extensions.DependencyInjection": "6.0.0",
- "Microsoft.Extensions.Hosting.Abstractions": "6.0.0",
- "Microsoft.Extensions.Logging": "6.0.0",
- "Microsoft.Extensions.Logging.Configuration": "6.0.0",
- "NLog": "4.7.14",
- "NLog.Extensions.Logging": "1.7.4",
- "Scrutor": "4.1.0"
- }
- },
- "AWS.Logger.Core": {
- "type": "Transitive",
- "resolved": "3.1.0",
- "contentHash": "ruKBNASE/IBnVZhyeUy0ueuxq1CuMtDHNpU2cqGUg12SxvClkqqY/PVXyM6I4ltsMh/1tF8WdpTBj9k3Fw/PgA==",
- "dependencies": {
- "AWSSDK.CloudWatchLogs": "3.7.0.5"
- }
- },
- "AWSSDK.CloudWatchLogs": {
- "type": "Transitive",
- "resolved": "3.7.0.5",
- "contentHash": "E9mEMaCVStnkzrs2gb35AbMz8xEGCqMGSnk+yyFJbkmdu9a+w5MVsuwRMkHbqiKn1bs5FTpdQJlV/NtgSYeHbA==",
- "dependencies": {
- "AWSSDK.Core": "[3.7.0.6, 3.8.0)"
- }
- },
- "AWSSDK.Core": {
- "type": "Transitive",
- "resolved": "3.7.0.6",
- "contentHash": "2oHC7TTLUjwVIvyS3CmCGvDPpgfrm/2FrjRwB/jUPEGreHpGkvTZHgcWbiD9QkLx1BLWJMpP+jww7CXbTuZZXQ=="
- },
- "CommandLineParser": {
- "type": "Transitive",
- "resolved": "2.8.0",
- "contentHash": "eco2HlKQBY4Joz9odHigzGpVzv6pjsXnY5lziioMveQxr+i2Z7xYcIOMeZTgYiqnMtMAbXMXsVhrNfWO5vJS8Q=="
- },
- "Microsoft.Extensions.Configuration": {
- "type": "Transitive",
- "resolved": "6.0.2",
- "contentHash": "2dpWx3Lxqq1lzLa9pY4lTOl6xg7VL45z0oe3r1xfXM0nQzR9XpuuMdk54B1LrC/AsyKbOukry+pdoQ7M7e/ezg==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.1",
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.Configuration.Abstractions": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "+FhuIM7bE3CyzhhIju6K1pmcDAp4ez2PKwx8jnV4dEI/LXXBGdQbDijlaQWqMa1oC38DX390bSshQVaKXioiXA==",
- "dependencies": {
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.Configuration.Binder": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "b3ErKzND8LIC7o08QAVlKfaEIYEvLJbtmVbFZVBRXeu9YkKfSSzLZfR1SUfQPBIy9mKLhEtJgGYImkcMNaKE0A==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
- }
- },
- "Microsoft.Extensions.Configuration.CommandLine": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "3nL1qCkZ1Oxx14ZTzgo4MmlO7tso7F+TtMZAY2jUAtTLyAcDp+EDjk3RqafoKiNaePyPvvlleEcBxh3b2Hzl1g==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "6.0.0",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
- }
- },
- "Microsoft.Extensions.Configuration.EnvironmentVariables": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "pnyXV1LFOsYjGveuC07xp0YHIyGq7jRq5Ncb5zrrIieMLWVwgMyYxcOH0jTnBedDT4Gh1QinSqsjqzcieHk1og==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "6.0.0",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0"
- }
- },
- "Microsoft.Extensions.Configuration.FileExtensions": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "2jaVZJqN6BvEV9DR/gqOnZUY1jjA/NaUcyJVl7Mp3Uvb9N0YE2uSJ6bdmiKxocsiGZxpfjqBe0SRISCCKhVecw==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "6.0.2",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "6.0.1",
- "Microsoft.Extensions.FileProviders.Physical": "6.0.1",
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.Configuration.Json": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "1jgLdq/UXJ71TE0toUF5VPn7yYaeyiN9sAdX2EZ6Z5DbUiWUKcMRAS5biExfu/ChYMvlGH/7ZlJijPY0zB1Vhg==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "6.0.2",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.1",
- "Microsoft.Extensions.Configuration.FileExtensions": "6.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "6.0.1",
- "System.Text.Json": "6.0.11"
- }
- },
- "Microsoft.Extensions.DependencyInjection": {
- "type": "Transitive",
- "resolved": "6.0.2",
- "contentHash": "gWUfUZ2ZDvwiVCxsOMComAhG43xstNWWVjV2takUZYRuDSJjO9Q5/b3tfOSkl5mcVwZAL3RZviRj5ZilxHghlw==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0"
- }
- },
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.1",
- "contentHash": "fGLiCRLMYd00JYpClraLjJTNKLmMJPnqxMaiRzEBIIvevlzxz33mXy39Lkd48hu1G+N21S7QpaO5ZzKsI6FRuA=="
- },
- "Microsoft.Extensions.DependencyModel": {
- "type": "Transitive",
- "resolved": "8.0.2",
- "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==",
- "dependencies": {
- "System.Text.Encodings.Web": "8.0.0",
- "System.Text.Json": "8.0.5"
- }
- },
- "Microsoft.Extensions.FileProviders.Abstractions": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "Kr8S/Uunxsjyhh5D91avbME3pITDrVYp/NACiymemtwhnvxpe82Jmr99cnPqiVridg2nIkcNNRVKzl/iP5/00g==",
- "dependencies": {
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.FileProviders.Physical": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "k9h04ZJiQ7mr4hsLNCUc/CL4AwOAHKq0uam4JBfeZ33XSU5MHxn7l7Vpf+T5sd5HXI7us1MGrkB5awqfg5xyHw==",
- "dependencies": {
- "Microsoft.Extensions.FileProviders.Abstractions": "6.0.1",
- "Microsoft.Extensions.FileSystemGlobbing": "6.0.0",
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.FileSystemGlobbing": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "ip8jnL1aPiaPeKINCqaTEbvBFDmVx9dXQEBZ2HOBRXPD1eabGNqP/bKlsIcp7U2lGxiXd5xIhoFcmY8nM4Hdiw=="
- },
- "Microsoft.Extensions.Hosting.Abstractions": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "GcT5l2CYXL6Sa27KCSh0TixsRfADUgth+ojQSD5EkzisZxmGFh7CwzkcYuGwvmXLjr27uWRNrJ2vuuEjMhU05Q==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.FileProviders.Abstractions": "6.0.0"
- }
- },
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Transitive",
- "resolved": "6.0.4",
- "contentHash": "K14wYgwOfKVELrUh5eBqlC8Wvo9vvhS3ZhIvcswV2uS/ubkTRPSQsN557EZiYUSSoZNxizG+alN4wjtdyLdcyw=="
- },
- "Microsoft.Extensions.Logging.Configuration": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "ZDskjagmBAbv+K8rYW9VhjPplhbOE63xUD0DiuydZJwt15dRyoqicYklLd86zzeintUc7AptDkHn+YhhYkYo8A==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "6.0.0",
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
- "Microsoft.Extensions.Configuration.Binder": "6.0.0",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.Logging": "6.0.0",
- "Microsoft.Extensions.Logging.Abstractions": "6.0.0",
- "Microsoft.Extensions.Options": "6.0.0",
- "Microsoft.Extensions.Options.ConfigurationExtensions": "6.0.0"
- }
- },
- "Microsoft.Extensions.Options": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "v5rh5jRcLBOKOaLVyYCm4TY/RoJlxWsW7N2TAPkmlHe55/0cB0Syp979x4He1+MIXsaTvJl1WOc7b1D1PSsO3A==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.Primitives": "6.0.1"
- }
- },
- "Microsoft.Extensions.Options.ConfigurationExtensions": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "bXWINbTn0vC0FYc9GaQTISbxhQLAMrvtbuvD9N6JelEaIS/Pr62wUCinrq5bf1WRBGczt1v4wDhxFtVFNcMdUQ==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "6.0.0",
- "Microsoft.Extensions.Configuration.Binder": "6.0.0",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.Options": "6.0.0",
- "Microsoft.Extensions.Primitives": "6.0.0"
- }
- },
- "Microsoft.Extensions.Primitives": {
- "type": "Transitive",
- "resolved": "6.0.1",
- "contentHash": "zyJfttJduuQbGEsd/TgerUEdgzHgUn/6oFgeaE04DKUMs7bbzckV7Ci1QZxf/VyQAlG41gR/GjecEScuYoHCUg=="
- },
- "NLog": {
- "type": "Transitive",
- "resolved": "4.7.14",
- "contentHash": "R1Nn+Zi2mSiKf82zII+8ZhGTr66CmCDLlpDq/0N0Y22TDrioYeP6t3XWzJVTi3OXaHJ/jiXId/okXQu8e0g3uw=="
- },
- "NLog.Extensions.Logging": {
- "type": "Transitive",
- "resolved": "1.7.4",
- "contentHash": "/dMxI/lBPNhKe9uONCQaZ3JYSHJh9/yez4Uqc6yIo2hIGaoi8sbQg7WQqW5/1WAIquhrG/SeCEZUTGMiRSQAHw==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "5.0.0",
- "Microsoft.Extensions.Logging": "5.0.0",
- "NLog": "4.7.11"
- }
- },
- "Scrutor": {
- "type": "Transitive",
- "resolved": "6.1.0",
- "contentHash": "m4+0RdgnX+jeiaqteq9x5SwEtuCjWG0KTw1jBjCzn7V8mCanXKoeF8+59E0fcoRbAjdEq6YqHFCmxZ49Kvqp3g==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1",
- "Microsoft.Extensions.DependencyModel": "8.0.2"
- }
- },
- "SonarAnalyzer.CSharp": {
- "type": "Transitive",
- "resolved": "9.20.0.85982",
- "contentHash": "c0IYtFg4mYusTafTy0Bs5wev45vRKNShkuoWyQyPMbC6imEFHL7tY/7xbbUJd6JNLYx5vP8wyi2LgiBsMHqb2Q=="
- },
- "StyleCop.Analyzers.Unstable": {
- "type": "Transitive",
- "resolved": "1.2.0.556",
- "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
- },
- "System.Diagnostics.DiagnosticSource": {
- "type": "Transitive",
- "resolved": "6.0.2",
- "contentHash": "6tQaIexFycaotdGn23lf3XJ/eI1GOjQKIvQDRFN9N4pwoNsKnHuXccQ3lnQO6GX8KAb1ic+6ZofJmPdbUVwZag=="
- },
- "System.Runtime.CompilerServices.Unsafe": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
- },
- "System.Text.Encodings.Web": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "System.Text.Json": {
- "type": "Transitive",
- "resolved": "8.0.5",
- "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0",
- "System.Text.Encodings.Web": "8.0.0"
- }
- },
- "neolution.extensions.dataseeding": {
- "type": "Project",
- "dependencies": {
- "Microsoft.Extensions.Logging.Abstractions": "[6.0.4, )",
- "Scrutor": "[6.1.0, )"
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/AnotherSeed.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/AnotherSeed.cs
new file mode 100644
index 0000000..73189f6
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/AnotherSeed.cs
@@ -0,0 +1,20 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Another seed with no dependencies
+ ///
+ public class AnotherSeed : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the async operation.
+ public Task SeedAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyA.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyA.cs
new file mode 100644
index 0000000..1dcffe3
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyA.cs
@@ -0,0 +1,22 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Circular dependency test seed A that depends on B.
+ ///
+ [DependsOn(typeof(CircularDependencyB))]
+ public class CircularDependencyA : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ // This seed intentionally creates a circular dependency for testing
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyB.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyB.cs
new file mode 100644
index 0000000..3fbbc6e
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyB.cs
@@ -0,0 +1,22 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Circular dependency test seed B that depends on A.
+ ///
+ [DependsOn(typeof(CircularDependencyC))]
+ public class CircularDependencyB : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ // This seed intentionally creates a circular dependency for testing
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyC.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyC.cs
new file mode 100644
index 0000000..cbad10d
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyC.cs
@@ -0,0 +1,22 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Circular dependency test seed C that depends on A, forming a 3-node cycle: A → B → C → A.
+ ///
+ [DependsOn(typeof(CircularDependencyA))]
+ public class CircularDependencyC : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ // This seed intentionally creates a 3-node circular dependency for testing
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyTests.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyTests.cs
new file mode 100644
index 0000000..5c70483
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/CircularDependencyTests.cs
@@ -0,0 +1,84 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Shouldly;
+ using Xunit;
+ using Xunit.Abstractions;
+
+ ///
+ /// Tests for circular dependency detection.
+ ///
+ public class CircularDependencyTests
+ {
+ ///
+ /// The test output helper
+ ///
+ private readonly ITestOutputHelper testOutputHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The test output helper.
+ public CircularDependencyTests(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutputHelper = testOutputHelper;
+ }
+
+ ///
+ /// Tests circular dependency detection.
+ ///
+ [Fact]
+ public void CircularDependencyThrowsException()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(CircularDependencyTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+ var seeder = serviceProvider.GetRequiredService();
+
+ // Act & Assert
+ Should.Throw(() => seeder.SeedAsync().GetAwaiter().GetResult())
+ .Message.ShouldContain("Circular dependency detected");
+ }
+
+ ///
+ /// Tests that complex 3-node circular dependency (A → B → C → A) is detected.
+ ///
+ [Fact]
+ public void ThreeNodeCircularDependencyDetected()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(CircularDependencyTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+ var seeder = serviceProvider.GetRequiredService();
+
+ // Act & Assert - should detect A → B → C → A cycle
+ var exception = Should.Throw(() => seeder.SeedAsync().GetAwaiter().GetResult());
+ exception.Message.ShouldContain("Circular dependency detected");
+
+ // Verify that all three seeds are mentioned as part of the cycle
+ exception.Message.ShouldSatisfyAllConditions(
+ () => exception.Message.ShouldContain("CircularDependencyA"),
+ () => exception.Message.ShouldContain("CircularDependencyB"),
+ () => exception.Message.ShouldContain("CircularDependencyC"));
+ }
+
+ ///
+ /// Creates the service collection.
+ ///
+ /// The .
+ private IServiceCollection CreateServiceCollection()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
+
+ // Don't register any fake services for circular dependency tests
+ // The circular dependency seeds should be self-contained
+ return services;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Neolution.Extensions.DataSeeding.UnitTests.csproj b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests.csproj
similarity index 92%
rename from Neolution.Extensions.DataSeeding.UnitTests/Neolution.Extensions.DataSeeding.UnitTests.csproj
rename to Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests.csproj
index 46ee334..821cb3c 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Neolution.Extensions.DataSeeding.UnitTests.csproj
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests.csproj
@@ -1,23 +1,22 @@
- net6.0
+ net8.0
enable
-
false
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/SimpleDependencySeed.cs b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/SimpleDependencySeed.cs
new file mode 100644
index 0000000..3246a4f
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests/SimpleDependencySeed.cs
@@ -0,0 +1,22 @@
+namespace Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Example seed showing the new simplified syntax for single dependency
+ ///
+ [DependsOn(typeof(AnotherSeed))]
+ public class SimpleDependencySeed : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the async operation.
+ public Task SeedAsync()
+ {
+ // Your seeding logic here
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedService.cs
similarity index 88%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedService.cs
index 4a55076..354d341 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake scoped service implementation.
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedServiceWithDependency.cs
similarity index 95%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedServiceWithDependency.cs
index cc6c66c..9c3bbc0 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeScopedServiceWithDependency.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
using System;
using System.Threading.Tasks;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeSingletonService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeSingletonService.cs
similarity index 86%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeSingletonService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeSingletonService.cs
index 0eb323a..ef606b8 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeSingletonService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeSingletonService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake singleton service implementation.
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeTransientService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeTransientService.cs
similarity index 86%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeTransientService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeTransientService.cs
index 8b0bc7f..a3e3f7a 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/FakeTransientService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/FakeTransientService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake transient service implementation.
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedService.cs
similarity index 80%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedService.cs
index 97c9d0f..4af6bc4 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake scoped service interface.
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedServiceWithDependency.cs
similarity index 88%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedServiceWithDependency.cs
index 17e0181..7c7ed84 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeScopedServiceWithDependency.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
using System.Threading.Tasks;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeSingletonService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeSingletonService.cs
similarity index 77%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeSingletonService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeSingletonService.cs
index dab6369..c749ddb 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeSingletonService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeSingletonService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake singleton service interface.
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeTransientService.cs b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeTransientService.cs
similarity index 77%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeTransientService.cs
rename to Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeTransientService.cs
index 52cc476..a2a0c26 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/Services/IFakeTransientService.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Fakes/Services/IFakeTransientService.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services
+namespace Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services
{
///
/// Fake transient service interface.
diff --git a/Neolution.Extensions.DataSeeding.Tests.Common/Neolution.Extensions.DataSeeding.Tests.Common.csproj b/Neolution.Extensions.DataSeeding.Tests.Common/Neolution.Extensions.DataSeeding.Tests.Common.csproj
new file mode 100644
index 0000000..a50eb93
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.Common/Neolution.Extensions.DataSeeding.Tests.Common.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/BasicTests.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/BasicTests.cs
similarity index 50%
rename from Neolution.Extensions.DataSeeding.UnitTests/BasicTests.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/BasicTests.cs
index 6008215..48c7bc4 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/BasicTests.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/BasicTests.cs
@@ -1,9 +1,12 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests
{
+ using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes;
+ using Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
@@ -47,6 +50,34 @@ public void SeedsCanBeSeededWithLoggingTest()
dataInitializerRun.ShouldBeTrue();
}
+ ///
+ /// Tests that duplicate seed registration throws an InvalidOperationException with a helpful message.
+ ///
+ [Fact]
+ public void DuplicateSeedRegistrationThrowsInvalidOperationException()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+
+ services.AddDataSeeding();
+
+ // Issue: SimpleSeed is already added because AddDataSeeding() scanned the assembly for ISeed implementations
+ services.AddTransient();
+
+ services.AddTransient();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act & Assert - The exception should be thrown when the seeder is constructed (UseServiceProvider is called)
+ var exception = Should.Throw(() =>
+ {
+ serviceProvider.GetRequiredService();
+ });
+
+ exception.Message.ShouldContain("Duplicate seed type(s) detected");
+ exception.Message.ShouldContain("SimpleSeed");
+ exception.Message.ShouldContain("This usually means the same seed class was registered more than once.");
+ }
+
///
/// Creates the service collection.
///
@@ -54,15 +85,17 @@ public void SeedsCanBeSeededWithLoggingTest()
private IServiceCollection CreateServiceCollection()
{
var services = new ServiceCollection();
- services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper).SetMinimumLevel(LogLevel.Debug));
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
// Register fake services with different lifetimes to test scoped service injection
- services.AddSingleton();
- services.AddScoped();
- services.AddTransient();
+ // Use Tests.Common implementations to match what seeds expect
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddTransient();
// Register the scoped service with dependency to test UserManager-like scenarios
- services.AddScoped();
+ services.AddScoped();
+
return services;
}
}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/DataInitializerFake.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/DataInitializerFake.cs
similarity index 93%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/DataInitializerFake.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/DataInitializerFake.cs
index c10b8a4..7cfe4a1 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/DataInitializerFake.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/DataInitializerFake.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes
{
using System;
using Neolution.Extensions.DataSeeding.Abstractions;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs
similarity index 82%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs
index 1afb67c..78b34ec 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/PermissionsSeed.cs
@@ -1,11 +1,11 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
{
- using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
///
+ [DependsOn(typeof(UsersSeed))]
public class PermissionsSeed : ISeed
{
///
@@ -22,9 +22,6 @@ public PermissionsSeed(ILogger logger)
this.logger = logger;
}
- ///
- public Type DependsOn => typeof(UsersSeed);
-
///
public Task SeedAsync()
{
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs
similarity index 91%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs
index 6d6e98b..c9f693e 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/ScopedServiceWithDependencySeed.cs
@@ -1,9 +1,9 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
{
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
///
/// A seed that tests scoped service dependencies similar to UserManager scenario.
diff --git a/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/SimpleSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/SimpleSeed.cs
new file mode 100644
index 0000000..4bed025
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/SimpleSeed.cs
@@ -0,0 +1,53 @@
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
+
+ ///
+ /// A simple seed for basic testing.
+ ///
+ public class SimpleSeed : ISeed
+ {
+ ///
+ /// The logger
+ ///
+ private readonly ILogger logger;
+
+ ///
+ /// The singleton service
+ ///
+ private readonly IFakeSingletonService singletonService;
+
+ ///
+ /// The transient service
+ ///
+ private readonly IFakeTransientService transientService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ /// The singleton service.
+ /// The transient service.
+ public SimpleSeed(
+ ILogger logger,
+ IFakeSingletonService singletonService,
+ IFakeTransientService transientService)
+ {
+ this.logger = logger;
+ this.singletonService = singletonService;
+ this.transientService = transientService;
+ }
+
+ ///
+ public async Task SeedAsync()
+ {
+ this.logger.LogInformation("Executing simple seed");
+ this.logger.LogDebug("Singleton service ID: {SingletonId}", this.singletonService.ServiceId);
+ this.logger.LogDebug("Transient service ID: {TransientId}", this.transientService.ServiceId);
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs
similarity index 89%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs
index 444a820..938e146 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSeed.cs
@@ -1,4 +1,4 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
{
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs
similarity index 82%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs
index 77cefab..31f5127 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/TenantsSettingsSeed.cs
@@ -1,11 +1,11 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
{
- using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
///
+ [DependsOn(typeof(TenantsSeed))]
public class TenantsSettingsSeed : ISeed
{
///
@@ -22,9 +22,6 @@ public TenantsSettingsSeed(ILogger logger)
this.logger = logger;
}
- ///
- public Type DependsOn => typeof(TenantsSeed);
-
///
public Task SeedAsync()
{
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs
similarity index 81%
rename from Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs
index eea9fbe..bfc3743 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Fakes/MultiTenantSeeds/UsersSeed.cs
@@ -1,11 +1,11 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.Fakes.MultiTenantSeeds
{
- using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
///
+ [DependsOn(typeof(TenantsSeed))]
public class UsersSeed : ISeed
{
///
@@ -22,9 +22,6 @@ public UsersSeed(ILogger logger)
this.logger = logger;
}
- ///
- public Type DependsOn => typeof(TenantsSeed);
-
///
public Task SeedAsync()
{
diff --git a/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.csproj b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.csproj
new file mode 100644
index 0000000..a3dab4b
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net8.0
+ enable
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/ScopeDisposalTests.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopeDisposalTests.cs
similarity index 96%
rename from Neolution.Extensions.DataSeeding.UnitTests/ScopeDisposalTests.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopeDisposalTests.cs
index 4df9c95..93859cd 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/ScopeDisposalTests.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopeDisposalTests.cs
@@ -1,10 +1,10 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests
{
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/ScopedServiceLifetimeTests.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopedServiceLifetimeTests.cs
similarity index 97%
rename from Neolution.Extensions.DataSeeding.UnitTests/ScopedServiceLifetimeTests.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopedServiceLifetimeTests.cs
index 4e0020c..d3140a8 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/ScopedServiceLifetimeTests.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/ScopedServiceLifetimeTests.cs
@@ -1,11 +1,10 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests
{
- using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/SeedingWorkflowComponentTests.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/SeedingWorkflowComponentTests.cs
similarity index 97%
rename from Neolution.Extensions.DataSeeding.UnitTests/SeedingWorkflowComponentTests.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/SeedingWorkflowComponentTests.cs
index 9a980e7..787be8c 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/SeedingWorkflowComponentTests.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/SeedingWorkflowComponentTests.cs
@@ -1,12 +1,11 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests
{
- using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/WorkflowTestSeed.cs b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/WorkflowTestSeed.cs
similarity index 93%
rename from Neolution.Extensions.DataSeeding.UnitTests/WorkflowTestSeed.cs
rename to Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/WorkflowTestSeed.cs
index fb8d83a..a7c87da 100644
--- a/Neolution.Extensions.DataSeeding.UnitTests/WorkflowTestSeed.cs
+++ b/Neolution.Extensions.DataSeeding.Tests.Core.UnitTests/WorkflowTestSeed.cs
@@ -1,9 +1,9 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
+namespace Neolution.Extensions.DataSeeding.Tests.Core.UnitTests
{
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
///
/// Test seed for workflow integration testing.
diff --git a/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/ComplexSeed.cs b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/ComplexSeed.cs
new file mode 100644
index 0000000..e46e7e1
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/ComplexSeed.cs
@@ -0,0 +1,21 @@
+namespace Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// A seed with complex multi-dependencies for testing.
+ ///
+ [DependsOn(typeof(FoundationSeedA), typeof(FoundationSeedB))]
+ public class ComplexSeed : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedA.cs b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedA.cs
new file mode 100644
index 0000000..a8b4d32
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedA.cs
@@ -0,0 +1,20 @@
+namespace Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Foundation seed A for multi-dependency testing.
+ ///
+ public class FoundationSeedA : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedB.cs b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedB.cs
new file mode 100644
index 0000000..59234eb
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/FoundationSeedB.cs
@@ -0,0 +1,20 @@
+namespace Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Foundation seed B for multi-dependency testing.
+ ///
+ public class FoundationSeedB : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/MultiDependencyTests.cs b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/MultiDependencyTests.cs
new file mode 100644
index 0000000..85447f0
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/MultiDependencyTests.cs
@@ -0,0 +1,63 @@
+namespace Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Xunit;
+ using Xunit.Abstractions;
+
+ ///
+ /// Tests for the multi-dependency functionality.
+ ///
+ public class MultiDependencyTests
+ {
+ ///
+ /// The test output helper
+ ///
+ private readonly ITestOutputHelper testOutputHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The test output helper.
+ public MultiDependencyTests(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutputHelper = testOutputHelper;
+ }
+
+ ///
+ /// Tests that seeds with multiple dependencies are executed in correct order.
+ ///
+ /// A representing the asynchronous unit test.
+ [Fact]
+ public async Task SeedsWithMultipleDependenciesExecuteInCorrectOrder()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(MultiDependencyTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+ var seeder = serviceProvider.GetRequiredService();
+
+ // Act & Assert - This should not throw
+ await seeder.SeedAsync();
+
+ // If we reach this point, the seeding completed successfully
+ Assert.NotNull(seeder);
+ }
+
+ ///
+ /// Creates the service collection.
+ ///
+ /// The .
+ private IServiceCollection CreateServiceCollection()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
+
+ // Don't register any fake services for multi-dependency tests
+ // The multi-dependency seeds should be self-contained
+ return services;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests.csproj b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests.csproj
new file mode 100644
index 0000000..821cb3c
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests/Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net8.0
+ enable
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedService.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedService.cs
new file mode 100644
index 0000000..a603a01
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedService.cs
@@ -0,0 +1,19 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services
+{
+ ///
+ /// Fake scoped service implementation.
+ ///
+ public class FakeScopedService : IFakeScopedService
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FakeScopedService()
+ {
+ this.ServiceId = System.Guid.NewGuid().ToString();
+ }
+
+ ///
+ public string ServiceId { get; }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs
new file mode 100644
index 0000000..6fb93bb
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/FakeScopedServiceWithDependency.cs
@@ -0,0 +1,38 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services
+{
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ /// A scoped service that depends on other scoped services, similar to UserManager.
+ ///
+ public class FakeScopedServiceWithDependency : IFakeScopedServiceWithDependency
+ {
+ ///
+ /// The service provider.
+ ///
+ private readonly IServiceProvider serviceProvider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service provider.
+ public FakeScopedServiceWithDependency(IServiceProvider serviceProvider)
+ {
+ this.serviceProvider = serviceProvider;
+ }
+
+ ///
+ /// Performs an async operation that accesses scoped dependencies.
+ ///
+ /// A task representing the async operation.
+ public async Task PerformScopedOperationAsync()
+ {
+ // This simulates UserManager.CreateAsync accessing other scoped services
+ var scopedService = this.serviceProvider.GetRequiredService();
+ await Task.Delay(1); // Simulate async work
+ return $"Operation completed with {scopedService.ServiceId}";
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedService.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedService.cs
new file mode 100644
index 0000000..f798fcd
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedService.cs
@@ -0,0 +1,13 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services
+{
+ ///
+ /// Fake scoped service interface.
+ ///
+ public interface IFakeScopedService
+ {
+ ///
+ /// Gets the service identifier.
+ ///
+ string ServiceId { get; }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs
new file mode 100644
index 0000000..0b83784
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Fakes/Services/IFakeScopedServiceWithDependency.cs
@@ -0,0 +1,16 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Interface for a scoped service that depends on other scoped services.
+ ///
+ public interface IFakeScopedServiceWithDependency
+ {
+ ///
+ /// Performs an async operation that accesses scoped dependencies.
+ ///
+ /// A task representing the async operation.
+ Task PerformScopedOperationAsync();
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.csproj b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.csproj
new file mode 100644
index 0000000..a3dab4b
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net8.0
+ enable
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopeDisposalTests.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopeDisposalTests.cs
new file mode 100644
index 0000000..b565a69
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopeDisposalTests.cs
@@ -0,0 +1,105 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
+ using Shouldly;
+ using Xunit;
+ using Xunit.Abstractions;
+ using FakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedService;
+ using FakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedServiceWithDependency;
+ using IFakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedService;
+ using IFakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedServiceWithDependency;
+
+ ///
+ /// Tests that demonstrate the ObjectDisposedException issue and verify the fix.
+ ///
+ public class ScopeDisposalTests
+ {
+ ///
+ /// The test output helper
+ ///
+ private readonly ITestOutputHelper testOutputHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The test output helper.
+ public ScopeDisposalTests(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutputHelper = testOutputHelper;
+ }
+
+ ///
+ /// Tests that scoped services accessed after scope disposal throw ObjectDisposedException.
+ /// This demonstrates the problem we fixed.
+ ///
+ [Fact]
+ public void ScopedService_AccessedAfterScopeDisposal_ThrowsObjectDisposedException()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ object scopedService;
+
+ // Create and dispose a scope (simulating the old problematic approach)
+ using (var scope = serviceProvider.CreateScope())
+ {
+ scopedService = scope.ServiceProvider.GetRequiredService();
+ } // Scope is disposed here
+
+ // Act & Assert - This should throw because the scope is disposed
+ var exception = Should.Throw(async () =>
+ {
+ // Call the method using reflection
+ var method = typeof(IFakeScopedServiceWithDependency).GetMethod("PerformScopedOperationAsync");
+ var task = (Task)method!.Invoke(scopedService, null)!;
+ await task;
+ });
+
+ exception.ShouldNotBeNull();
+ }
+
+ ///
+ /// Tests that the current seeding implementation works correctly with scoped services.
+ /// This verifies our fix is working.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Fact]
+ public async Task CurrentSeedingImplementation_WithScopedServices_WorksCorrectly()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(ScopeDisposalTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act & Assert - This should work without throwing
+ var seeder = serviceProvider.GetRequiredService();
+ await seeder.SeedAsync();
+
+ // If we get here, the scoped services worked correctly
+ seeder.ShouldNotBeNull();
+ }
+
+ ///
+ /// Creates the service collection.
+ ///
+ /// The .
+ private IServiceCollection CreateServiceCollection()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
+
+ // Register scoped services
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddScoped();
+
+ return services;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopedServiceLifetimeTests.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopedServiceLifetimeTests.cs
new file mode 100644
index 0000000..3ea5c06
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/ScopedServiceLifetimeTests.cs
@@ -0,0 +1,120 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests
+{
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
+ using Shouldly;
+ using Xunit;
+ using Xunit.Abstractions;
+ using FakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedService;
+ using FakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedServiceWithDependency;
+ using IFakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedService;
+ using IFakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedServiceWithDependency;
+
+ ///
+ /// Tests to ensure proper service lifetime handling during seeding operations.
+ /// These tests prevent regressions related to scoped service injection.
+ ///
+ public class ScopedServiceLifetimeTests
+ {
+ ///
+ /// The test output helper.
+ ///
+ private readonly ITestOutputHelper testOutputHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The test output helper.
+ public ScopedServiceLifetimeTests(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutputHelper = testOutputHelper;
+ }
+
+ ///
+ /// Tests that seeding operations properly use scoped services within their scope.
+ /// This ensures that each seeding operation gets fresh scoped service instances.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Fact]
+ public async Task SeedingOperations_UseFreshScopedServices()
+ {
+ // Arrange - Create a service collection with data seeding configured
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(ScopedServiceLifetimeTests).Assembly);
+
+ var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true });
+ var seeder = serviceProvider.GetRequiredService();
+
+ // Act & Assert - This should not throw any scope validation errors
+ await seeder.SeedAsync();
+
+ // Additional verification: Ensure we can still create new scoped instances after seeding
+ using var scope = serviceProvider.CreateScope();
+ var scopedService = scope.ServiceProvider.GetRequiredService();
+ scopedService.ShouldNotBeNull();
+ }
+
+ ///
+ /// Tests that dependency analysis does not cache scoped service instances.
+ ///
+ [Fact]
+ public void DependencyAnalysis_DoesNotCacheScopedServiceInstances()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(ScopedServiceLifetimeTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act - Just creating the seeder should trigger dependency analysis
+ serviceProvider.GetRequiredService();
+
+ // Assert - We should still be able to create new scoped services
+ using var scope = serviceProvider.CreateScope();
+ var scopedService = scope.ServiceProvider.GetRequiredService();
+ scopedService.ShouldNotBeNull();
+ var serviceId = scopedService.ServiceId;
+ serviceId.ShouldNotBeEmpty();
+ }
+
+ ///
+ /// Tests that the root cause error (cannot resolve scoped service from root) does not occur.
+ ///
+ [Fact]
+ public void SeedRegistration_DoesNotTriggerScopedServiceRootResolutionError()
+ {
+ // Arrange & Act - This should not throw the root cause error:
+ // "Cannot resolve 'IEnumerable' from root provider because it requires scoped service"
+ var services = this.CreateServiceCollection();
+
+ Should.NotThrow(() =>
+ {
+ services.AddDataSeeding(typeof(ScopedServiceLifetimeTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+ serviceProvider.GetRequiredService();
+ });
+ }
+
+ ///
+ /// Creates the service collection with all required dependencies.
+ ///
+ /// The .
+ private IServiceCollection CreateServiceCollection()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
+
+ // Register services with different lifetimes
+ services.AddSingleton();
+ services.AddTransient();
+
+ // Register scoped services
+ services.AddScoped();
+ services.AddScoped();
+
+ return services;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/SeedingWorkflowComponentTests.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/SeedingWorkflowComponentTests.cs
new file mode 100644
index 0000000..635ddb9
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/SeedingWorkflowComponentTests.cs
@@ -0,0 +1,151 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests
+{
+ using System.Linq;
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+ using Neolution.Extensions.DataSeeding.Tests.Common.Fakes.Services;
+ using Shouldly;
+ using Xunit;
+ using Xunit.Abstractions;
+ using FakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedService;
+ using FakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.FakeScopedServiceWithDependency;
+ using IFakeScopedService = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedService;
+ using IFakeScopedServiceWithDependency = Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.Fakes.Services.IFakeScopedServiceWithDependency;
+
+ ///
+ /// Component tests that verify the complete seeding workflow with various service lifetimes.
+ /// These tests ensure the entire seeding pipeline works correctly with scoped dependencies.
+ ///
+ public class SeedingWorkflowComponentTests
+ {
+ ///
+ /// The test output helper
+ ///
+ private readonly ITestOutputHelper testOutputHelper;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The test output helper.
+ public SeedingWorkflowComponentTests(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutputHelper = testOutputHelper;
+ }
+
+ ///
+ /// Tests the complete workflow: registration → dependency analysis → execution with scoped services.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Fact]
+ public async Task CompleteWorkflow_WithScopedServices_ExecutesSuccessfully()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(SeedingWorkflowComponentTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act & Assert - This tests the complete workflow:
+ // 1. Service registration (AddDataSeeding)
+ // 2. Dependency analysis (UseServiceProvider in Seeding)
+ // 3. Seed execution (SeedAsync in Seeder)
+ var seeder = serviceProvider.GetRequiredService();
+ await seeder.SeedAsync();
+
+ // If we reach here, the entire workflow succeeded
+ seeder.ShouldNotBeNull();
+ }
+
+ ///
+ /// Tests that seeds are resolved with correct concrete types during execution.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Fact]
+ public async Task SeedExecution_ResolvesSeedsWithCorrectConcreteTypes()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(SeedingWorkflowComponentTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var seeder = serviceProvider.GetRequiredService();
+
+ // Verify that specific seeds can be resolved by their concrete types
+ var iSeedImplementations = serviceProvider.GetServices();
+ var concreteSeedTypes = iSeedImplementations.Select(s => s.GetType()).ToList();
+
+ // Assert - Look for WorkflowTestSeed
+ var workflowTestSeedType = concreteSeedTypes.FirstOrDefault(t => t.Name == "WorkflowTestSeed");
+ workflowTestSeedType.ShouldNotBeNull();
+
+ // Execute seeding to verify everything works
+ await seeder.SeedAsync();
+ }
+
+ ///
+ /// Tests that service registration includes both interface and concrete type registrations.
+ ///
+ [Fact]
+ public void ServiceRegistration_IncludesBothInterfaceAndConcreteTypes()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(SeedingWorkflowComponentTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act & Assert - Seeds should be registered as both ISeed and their concrete types
+ var workflowTestSeedType = typeof(WorkflowTestSeed);
+
+ var workflowSeedAsInterface = serviceProvider.GetServices()
+ .FirstOrDefault(s => s.GetType() == workflowTestSeedType);
+ var workflowSeedAsConcrete = serviceProvider.GetService(workflowTestSeedType!);
+
+ workflowSeedAsInterface.ShouldNotBeNull();
+ workflowSeedAsConcrete.ShouldNotBeNull();
+
+ // Seeds are transient, so instances will be different, but both registrations should work
+ workflowSeedAsInterface.GetType().ShouldBe(workflowTestSeedType);
+ workflowSeedAsConcrete.GetType().ShouldBe(workflowTestSeedType);
+ }
+
+ ///
+ /// Tests that dependency analysis can handle seeds with complex dependency chains.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Fact]
+ public async Task DependencyAnalysis_HandlesComplexDependencyChains()
+ {
+ // Arrange
+ var services = this.CreateServiceCollection();
+ services.AddDataSeeding(typeof(SeedingWorkflowComponentTests).Assembly);
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act - This should work even with seeds that have multiple scoped dependencies
+ var seeder = serviceProvider.GetRequiredService();
+ await seeder.SeedAsync();
+
+ // Assert - If we get here, dependency analysis and execution both succeeded
+ seeder.ShouldNotBeNull();
+ }
+
+ ///
+ /// Creates the service collection with comprehensive service registrations.
+ ///
+ /// The .
+ private IServiceCollection CreateServiceCollection()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
+
+ // Register services with different lifetimes
+ services.AddScoped();
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddScoped();
+
+ return services;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/WorkflowTestSeed.cs b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/WorkflowTestSeed.cs
new file mode 100644
index 0000000..37d4bb6
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests/WorkflowTestSeed.cs
@@ -0,0 +1,20 @@
+namespace Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests
+{
+ using System.Threading.Tasks;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Test seed for workflow testing.
+ ///
+ public class WorkflowTestSeed : ISeed
+ {
+ ///
+ /// Seeds the data.
+ ///
+ /// A task representing the asynchronous operation.
+ public Task SeedAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/AbstractSeedTests.cs b/Neolution.Extensions.DataSeeding.UnitTests/AbstractSeedTests.cs
deleted file mode 100644
index b0a69b9..0000000
--- a/Neolution.Extensions.DataSeeding.UnitTests/AbstractSeedTests.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests
-{
- using System.Linq;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
- using Shouldly;
- using Xunit;
- using Xunit.Abstractions;
-
- ///
- /// Tests for seeds that inherit from the abstract Seed class.
- ///
- public class AbstractSeedTests
- {
- ///
- /// The test output helper
- ///
- private readonly ITestOutputHelper testOutputHelper;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The test output helper.
- public AbstractSeedTests(ITestOutputHelper testOutputHelper)
- {
- this.testOutputHelper = testOutputHelper;
- }
-
- ///
- /// Tests that seeds inheriting from the abstract Seed class can be registered and resolved.
- ///
- [Fact]
- public void AbstractSeedCanBeRegisteredAndResolved()
- {
- // Arrange
- var services = this.CreateServiceCollection();
- services.AddDataSeeding(typeof(AbstractSeedTests).Assembly);
- services.AddTransient();
- var serviceProvider = services.BuildServiceProvider();
-
- // Act & Assert - This should not throw
- var abstractSeeds = serviceProvider.GetServices();
- abstractSeeds.ShouldNotBeNull();
- abstractSeeds.ShouldNotBeEmpty();
-
- // Verify we can find our specific abstract seed
- var abstractSeedExample = abstractSeeds.OfType().FirstOrDefault();
- abstractSeedExample.ShouldNotBeNull();
- }
-
- ///
- /// Tests that the seeder can execute seeds that inherit from the abstract Seed class.
- ///
- [Fact]
- public void SeederCanExecuteAbstractSeeds()
- {
- // Arrange
- var services = this.CreateServiceCollection();
- services.AddDataSeeding(typeof(AbstractSeedTests).Assembly);
- services.AddTransient();
- var serviceProvider = services.BuildServiceProvider();
- var dataInitializer = serviceProvider.GetRequiredService();
-
- // Act
- var result = dataInitializer.Run();
-
- // Assert
- result.ShouldBeTrue();
- }
-
- ///
- /// Tests that abstract seeds can be manually executed via .
- ///
- [Fact]
- public void AbstractSeedsCanBeManuallyExecuted()
- {
- // Arrange
- var services = this.CreateServiceCollection();
- services.AddDataSeeding(typeof(AbstractSeedTests).Assembly);
- var serviceProvider = services.BuildServiceProvider();
- var seeder = serviceProvider.GetRequiredService();
-
- // Act & Assert - This should not throw
- var seedTask = seeder.SeedAsync();
- seedTask.ShouldNotBeNull();
- }
-
- ///
- /// Creates the service collection.
- ///
- /// The .
- private IServiceCollection CreateServiceCollection()
- {
- var services = new ServiceCollection();
- services.AddLogging(builder => builder.AddXUnit(this.testOutputHelper));
-
- // Register fake services with different lifetimes to test scoped service injection
- services.AddSingleton();
- services.AddScoped();
- services.AddTransient();
-
- // Register the scoped service with dependency to test UserManager-like scenarios
- services.AddScoped();
-
- return services;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/AbstractSeedExample.cs b/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/AbstractSeedExample.cs
deleted file mode 100644
index 1fbdc17..0000000
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/AbstractSeedExample.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
-
- ///
- /// Example seed that inherits from the abstract Seed class instead of implementing ISeed directly.
- /// This tests the scenario where users prefer to use the abstract Seed base class.
- ///
- public class AbstractSeedExample : Seed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- public AbstractSeedExample(ILogger logger)
- {
- this.logger = logger;
- }
-
- ///
- public override async Task SeedAsync()
- {
- this.logger.LogInformation("AbstractSeedExample.SeedAsync() called");
-
- // Example of using the protected SeedAsync() helper method
- await SeedAsync().ConfigureAwait(false);
-
- this.logger.LogInformation("AbstractSeedExample.SeedAsync() completed");
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ApplicationSettingsSeed.cs b/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ApplicationSettingsSeed.cs
deleted file mode 100644
index 6e0e23a..0000000
--- a/Neolution.Extensions.DataSeeding.UnitTests/Fakes/MultiTenantSeeds/ApplicationSettingsSeed.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.UnitTests.Fakes.MultiTenantSeeds
-{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.UnitTests.Fakes.Services;
-
- ///
- public class ApplicationSettingsSeed : ISeed
- {
- ///
- /// The logger
- ///
- private readonly ILogger logger;
-
- ///
- /// The singleton service
- ///
- private readonly IFakeSingletonService singletonService;
-
- ///
- /// The scoped service
- ///
- private readonly IFakeScopedService scopedService;
-
- ///
- /// The transient service
- ///
- private readonly IFakeTransientService transientService;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- /// The singleton service.
- /// The scoped service.
- /// The transient service.
- public ApplicationSettingsSeed(
- ILogger logger,
- IFakeSingletonService singletonService,
- IFakeScopedService scopedService,
- IFakeTransientService transientService)
- {
- this.logger = logger;
- this.singletonService = singletonService;
- this.scopedService = scopedService;
- this.transientService = transientService;
- }
-
- ///
- public Task SeedAsync()
- {
- this.logger.LogInformation(
- "Seed() called with Singleton: {SingletonId}, Scoped: {ScopedId}, Transient: {TransientId}",
- this.singletonService.ServiceId,
- this.scopedService.ServiceId,
- this.transientService.ServiceId);
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding.sln b/Neolution.Extensions.DataSeeding.sln
index 94bb358..3f5c1f2 100644
--- a/Neolution.Extensions.DataSeeding.sln
+++ b/Neolution.Extensions.DataSeeding.sln
@@ -14,32 +14,125 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.Extensions.DataSeeding", "Neolution.Extensions.DataSeeding\Neolution.Extensions.DataSeeding.csproj", "{E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.Extensions.DataSeeding.UnitTests", "Neolution.Extensions.DataSeeding.UnitTests\Neolution.Extensions.DataSeeding.UnitTests.csproj", "{D1831BE0-7751-4E55-A248-CC8444E7D2A5}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Tests.Common", "Neolution.Extensions.DataSeeding.Tests.Common\Neolution.Extensions.DataSeeding.Tests.Common.csproj", "{AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.Extensions.DataSeeding.Sample", "Neolution.Extensions.DataSeeding.Sample\Neolution.Extensions.DataSeeding.Sample.csproj", "{750FFE55-0B29-4796-A2C3-936B8F380D59}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests", "Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests\Neolution.Extensions.DataSeeding.Tests.CircularDependency.UnitTests.csproj", "{B17509C4-0796-45FB-2CC4-04D59467875D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Tests.Core.UnitTests", "Neolution.Extensions.DataSeeding.Tests.Core.UnitTests\Neolution.Extensions.DataSeeding.Tests.Core.UnitTests.csproj", "{20C74226-61B6-97E3-F329-B446D8C5E0E2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests", "Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests\Neolution.Extensions.DataSeeding.Tests.MultiDependency.UnitTests.csproj", "{D8D831E2-3A1E-3E86-B7CD-F49273100149}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests", "Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests\Neolution.Extensions.DataSeeding.Tests.ScopedServices.UnitTests.csproj", "{E3D54B8C-9320-3BEB-402B-727F56F15820}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.Extensions.DataSeeding.Demo", "Neolution.Extensions.DataSeeding.Demo\Neolution.Extensions.DataSeeding.Demo.csproj", "{99C05D1A-0230-BCC0-35E9-F7EDEAED5970}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|x64.Build.0 = Debug|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Debug|x86.Build.0 = Debug|Any CPU
{E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|Any CPU.Build.0 = Release|Any CPU
- {D1831BE0-7751-4E55-A248-CC8444E7D2A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D1831BE0-7751-4E55-A248-CC8444E7D2A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D1831BE0-7751-4E55-A248-CC8444E7D2A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D1831BE0-7751-4E55-A248-CC8444E7D2A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {750FFE55-0B29-4796-A2C3-936B8F380D59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {750FFE55-0B29-4796-A2C3-936B8F380D59}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {750FFE55-0B29-4796-A2C3-936B8F380D59}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {750FFE55-0B29-4796-A2C3-936B8F380D59}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|x64.ActiveCfg = Release|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|x64.Build.0 = Release|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|x86.ActiveCfg = Release|Any CPU
+ {E47FF26B-4900-40A2-BA9D-2BEC5C4733E6}.Release|x86.Build.0 = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|x64.Build.0 = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Debug|x86.Build.0 = Debug|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|x64.ActiveCfg = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|x64.Build.0 = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|x86.ActiveCfg = Release|Any CPU
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF}.Release|x86.Build.0 = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|x64.Build.0 = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Debug|x86.Build.0 = Debug|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|x64.ActiveCfg = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|x64.Build.0 = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|x86.ActiveCfg = Release|Any CPU
+ {B17509C4-0796-45FB-2CC4-04D59467875D}.Release|x86.Build.0 = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|x64.Build.0 = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Debug|x86.Build.0 = Debug|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|x64.ActiveCfg = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|x64.Build.0 = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|x86.ActiveCfg = Release|Any CPU
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2}.Release|x86.Build.0 = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|x64.Build.0 = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Debug|x86.Build.0 = Debug|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|x64.ActiveCfg = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|x64.Build.0 = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|x86.ActiveCfg = Release|Any CPU
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149}.Release|x86.Build.0 = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|x64.Build.0 = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Debug|x86.Build.0 = Debug|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|x64.ActiveCfg = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|x64.Build.0 = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|x86.ActiveCfg = Release|Any CPU
+ {E3D54B8C-9320-3BEB-402B-727F56F15820}.Release|x86.Build.0 = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|x64.Build.0 = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Debug|x86.Build.0 = Debug|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|Any CPU.Build.0 = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|x64.ActiveCfg = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|x64.Build.0 = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|x86.ActiveCfg = Release|Any CPU
+ {99C05D1A-0230-BCC0-35E9-F7EDEAED5970}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {AAE4167A-DFA8-4C4F-8008-CC68F49DE7BF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {B17509C4-0796-45FB-2CC4-04D59467875D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {20C74226-61B6-97E3-F329-B446D8C5E0E2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {D8D831E2-3A1E-3E86-B7CD-F49273100149} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {E3D54B8C-9320-3BEB-402B-727F56F15820} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A2A6363-0CAE-4400-8903-B346EA76BA17}
EndGlobalSection
diff --git a/Neolution.Extensions.DataSeeding/Abstractions/DependsOnAttribute.cs b/Neolution.Extensions.DataSeeding/Abstractions/DependsOnAttribute.cs
new file mode 100644
index 0000000..9d051db
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding/Abstractions/DependsOnAttribute.cs
@@ -0,0 +1,69 @@
+namespace Neolution.Extensions.DataSeeding.Abstractions
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Specifies the seeds that this seed depends on for execution order.
+ /// The seeding framework will ensure all dependencies are executed before this seed.
+ ///
+ ///
+ ///
+ /// [DependsOn(typeof(UsersSeed), typeof(RolesSeed))]
+ /// public class UserPermissionsSeed : ISeed
+ /// {
+ /// public async Task SeedAsync()
+ /// {
+ /// // This will run after UsersSeed and RolesSeed complete
+ /// }
+ /// }
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class DependsOnAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The types of seeds that this seed depends on. All types must implement .
+ /// Thrown when is null.
+ /// Thrown when any seed type does not implement or when duplicate types are specified.
+ public DependsOnAttribute(params Type[] seedTypes)
+ {
+ if (seedTypes == null)
+ {
+ throw new ArgumentNullException(nameof(seedTypes));
+ }
+
+ // Validate all types implement ISeed
+ foreach (var seedType in seedTypes)
+ {
+ if (seedType == null)
+ {
+ throw new ArgumentException("Seed type cannot be null.", nameof(seedTypes));
+ }
+
+ if (!typeof(ISeed).IsAssignableFrom(seedType))
+ {
+ throw new ArgumentException($"Type '{seedType.FullName}' must implement ISeed interface.", nameof(seedTypes));
+ }
+ }
+
+ // Remove duplicates and store
+ this.SeedTypes = seedTypes.Distinct().ToArray();
+
+ // Warn about duplicates (for development feedback)
+ if (this.SeedTypes.Length != seedTypes.Length)
+ {
+ // In a real implementation, you might want to log this warning
+ // For now, we'll just silently deduplicate
+ }
+ }
+
+ ///
+ /// Gets the types of seeds that this seed depends on.
+ ///
+ /// An array of seed types that must implement .
+ public Type[] SeedTypes { get; }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding/Abstractions/ISeed.cs b/Neolution.Extensions.DataSeeding/Abstractions/ISeed.cs
index c2e6528..f76b53b 100644
--- a/Neolution.Extensions.DataSeeding/Abstractions/ISeed.cs
+++ b/Neolution.Extensions.DataSeeding/Abstractions/ISeed.cs
@@ -1,27 +1,38 @@
namespace Neolution.Extensions.DataSeeding.Abstractions
{
- using System;
using System.Threading.Tasks;
///
- /// A data seed component.
+ /// Interface for seeds that can be executed by the seeding framework.
+ /// Use the to specify execution dependencies.
///
+ ///
+ ///
+ /// [DependsOn(typeof(UserRolesSeed))]
+ /// public class UsersSeed : ISeed
+ /// {
+ /// private readonly ILogger<UsersSeed> logger;
+ ///
+ /// public UsersSeed(ILogger<UsersSeed> logger)
+ /// {
+ /// this.logger = logger;
+ /// }
+ ///
+ /// public async Task SeedAsync()
+ /// {
+ /// this.logger.LogInformation("Seeding users...");
+ /// // Your seeding logic here
+ /// }
+ /// }
+ ///
+ ///
public interface ISeed
{
///
- /// Gets the priority of this seed. Lower number means higher priority. Default is 1.
+ /// Performs the seeding operation asynchronously.
+ /// This method is called by the seeding framework after all dependencies have been executed.
///
- public int Priority => 1;
-
- ///
- /// Gets the seed type this seed depends on.
- ///
- public Type? DependsOn => null;
-
- ///
- /// The data to seed.
- ///
- /// The .
+ /// A task representing the asynchronous seeding operation.
Task SeedAsync();
}
}
diff --git a/Neolution.Extensions.DataSeeding/Abstractions/ISeeder.cs b/Neolution.Extensions.DataSeeding/Abstractions/ISeeder.cs
index 722a448..8f8d8b1 100644
--- a/Neolution.Extensions.DataSeeding/Abstractions/ISeeder.cs
+++ b/Neolution.Extensions.DataSeeding/Abstractions/ISeeder.cs
@@ -9,17 +9,9 @@
public interface ISeeder
{
///
- /// Seeds the specified seed component.
+ /// Seeds all registered seed components in dependency order.
///
/// The .
Task SeedAsync();
-
- ///
- /// Seeds the specified seed component.
- ///
- /// The seed type.
- /// The .
- Task SeedAsync()
- where T : Seed;
}
}
diff --git a/Neolution.Extensions.DataSeeding/Abstractions/Seed.cs b/Neolution.Extensions.DataSeeding/Abstractions/Seed.cs
deleted file mode 100644
index bb1bd72..0000000
--- a/Neolution.Extensions.DataSeeding/Abstractions/Seed.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Abstractions
-{
- using System.Linq;
- using System.Threading.Tasks;
-
- ///
- /// The seed.
- ///
- public abstract class Seed
- {
- ///
- /// Seeds the seed.
- ///
- /// A representing the asynchronous operation.
- public abstract Task SeedAsync();
-
- ///
- /// Seeds the seed component.
- ///
- /// The seed.
- /// A representing the asynchronous operation.
- protected static async Task SeedAsync()
- where T : ISeed
- {
- var seed = Seeding.Instance.Seeds.FirstOrDefault(x => x.GetType() == typeof(T));
- if (seed != null)
- {
- await seed.SeedAsync().ConfigureAwait(false);
- }
- }
- }
-}
diff --git a/Neolution.Extensions.DataSeeding/Internal/DependencyResolver.cs b/Neolution.Extensions.DataSeeding/Internal/DependencyResolver.cs
new file mode 100644
index 0000000..31b4007
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding/Internal/DependencyResolver.cs
@@ -0,0 +1,177 @@
+namespace Neolution.Extensions.DataSeeding.Internal
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Implements topological sorting for seed dependency resolution using Kahn's algorithm.
+ /// This replaces the simple tree-based approach with proper dependency graph resolution.
+ ///
+ internal static class DependencyResolver
+ {
+ ///
+ /// Resolves the execution order of seeds using topological sorting.
+ ///
+ /// The collection of seeds to sort.
+ /// The seeds in execution order.
+ /// Thrown when circular dependencies are detected.
+ public static IList ResolveDependencies(IEnumerable seeds)
+ {
+ var seedList = seeds.ToList();
+ var seedLookup = seedList.ToDictionary(s => s.GetType(), s => s);
+
+ var (graph, inDegree) = BuildDependencyGraph(seedList, seedLookup);
+ var result = ProcessTopologicalSort(seedList, graph, inDegree);
+ ValidateNoCycles(seedList, result);
+
+ return result;
+ }
+
+ ///
+ /// Gets the dependencies for a specific seed.
+ /// Reads dependency information from the .
+ ///
+ /// The seed to get dependencies for.
+ /// Array of dependency types.
+ internal static Type[] GetSeedDependencies(ISeed seed)
+ {
+ ArgumentNullException.ThrowIfNull(seed);
+
+ var seedType = seed.GetType();
+
+ // Check for the DependsOnAttribute
+ var dependsOnAttribute = seedType.GetCustomAttribute();
+ if (dependsOnAttribute != null)
+ {
+ return dependsOnAttribute.SeedTypes;
+ }
+
+ // No dependencies if no attribute is present
+ return Array.Empty();
+ }
+
+ ///
+ /// Builds the dependency graph and calculates in-degrees for all seeds.
+ ///
+ /// The list of seeds to process.
+ /// Dictionary for fast seed lookup by type.
+ /// A tuple containing the dependency graph and in-degree counts.
+ private static (SeedGraph Graph, IDictionary InDegree) BuildDependencyGraph(
+ IList seedList,
+ IDictionary seedLookup)
+ {
+ var graph = new SeedGraph();
+ var inDegree = new Dictionary();
+
+ // Initialize graph and in-degree for all seeds
+ foreach (var seed in seedList)
+ {
+ graph[seed] = new List();
+ inDegree[seed] = 0;
+ }
+
+ // Build dependency relationships
+ foreach (var seed in seedList)
+ {
+ var dependencies = GetSeedDependencies(seed);
+ AddDependenciesToGraph(seed, dependencies, seedLookup, graph, inDegree);
+ }
+
+ return (graph, inDegree);
+ }
+
+ ///
+ /// Adds dependencies to the graph and updates in-degree counts.
+ ///
+ /// The seed that has dependencies.
+ /// The types this seed depends on.
+ /// Dictionary for fast seed lookup by type.
+ /// The dependency graph to update.
+ /// The in-degree count to update.
+ private static void AddDependenciesToGraph(
+ ISeed seed,
+ Type[] dependencies,
+ IDictionary seedLookup,
+ SeedGraph graph,
+ IDictionary inDegree)
+ {
+ foreach (var dependencyType in dependencies)
+ {
+ if (seedLookup.TryGetValue(dependencyType, out var dependency))
+ {
+ // dependency -> seed (dependency must execute before seed)
+ graph[dependency].Add(seed);
+ inDegree[seed]++;
+ }
+ }
+ }
+
+ ///
+ /// Processes the topological sort using Kahn's algorithm.
+ ///
+ /// The list of seeds to process.
+ /// The dependency graph.
+ /// The in-degree count for each seed.
+ /// The seeds sorted in execution order.
+ private static List ProcessTopologicalSort(IList seedList, SeedGraph graph, IDictionary inDegree)
+ {
+ var result = new List();
+ var queue = new Queue();
+
+ // Find all seeds with no dependencies (in-degree = 0)
+ foreach (var seed in seedList)
+ {
+ if (inDegree[seed] == 0)
+ {
+ queue.Enqueue(seed);
+ }
+ }
+
+ // Process the queue using Kahn's algorithm
+ while (queue.Count > 0)
+ {
+ var currentSeed = queue.Dequeue();
+ result.Add(currentSeed);
+
+ // For each seed that depends on the current seed
+ foreach (var dependentSeed in graph[currentSeed])
+ {
+ inDegree[dependentSeed]--;
+ if (inDegree[dependentSeed] == 0)
+ {
+ queue.Enqueue(dependentSeed);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Validates that no circular dependencies exist by checking if all seeds were processed.
+ ///
+ /// The original list of seeds.
+ /// The processed seeds from topological sort.
+ /// Thrown when circular dependencies are detected.
+ private static void ValidateNoCycles(ICollection originalSeeds, ICollection sortedSeeds)
+ {
+ if (sortedSeeds.Count == originalSeeds.Count)
+ {
+ return;
+ }
+
+ var unprocessedSeeds = originalSeeds.Except(sortedSeeds).Select(s => s.GetType().Name);
+ throw new InvalidOperationException($"Circular dependency detected among seeds: {string.Join(", ", unprocessedSeeds)}");
+ }
+
+ ///
+ /// Helper class to wrap seed dependency graph to avoid nested generic types (S4017).
+ ///
+ private sealed class SeedGraph : Dictionary>
+ {
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding/Internal/SeedTypeEqualityComparer.cs b/Neolution.Extensions.DataSeeding/Internal/SeedTypeEqualityComparer.cs
new file mode 100644
index 0000000..52f9e99
--- /dev/null
+++ b/Neolution.Extensions.DataSeeding/Internal/SeedTypeEqualityComparer.cs
@@ -0,0 +1,43 @@
+namespace Neolution.Extensions.DataSeeding.Internal
+{
+ using System;
+ using System.Collections.Generic;
+ using Neolution.Extensions.DataSeeding.Abstractions;
+
+ ///
+ /// Compares seeds by their type to detect duplicates.
+ ///
+ internal sealed class SeedTypeEqualityComparer : IEqualityComparer
+ {
+ ///
+ /// Determines whether the specified seeds are equal by comparing their types.
+ ///
+ /// The first seed to compare.
+ /// The second seed to compare.
+ /// true if the specified seeds have the same type; otherwise, false.
+ public bool Equals(ISeed? x, ISeed? y)
+ {
+ if (x is null && y is null)
+ {
+ return true;
+ }
+
+ if (x is null || y is null)
+ {
+ return false;
+ }
+
+ return x.GetType() == y.GetType();
+ }
+
+ ///
+ /// Returns a hash code for the specified seed based on its type.
+ ///
+ /// The seed for which a hash code is to be returned.
+ /// A hash code for the specified seed.
+ public int GetHashCode(ISeed obj)
+ {
+ return obj?.GetType().GetHashCode() ?? 0;
+ }
+ }
+}
diff --git a/Neolution.Extensions.DataSeeding/Internal/Wrap.cs b/Neolution.Extensions.DataSeeding/Internal/Wrap.cs
deleted file mode 100644
index fcf742d..0000000
--- a/Neolution.Extensions.DataSeeding/Internal/Wrap.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace Neolution.Extensions.DataSeeding.Internal
-{
- using System;
- using System.Collections.Generic;
-
- ///
- /// The wrap that contains a seed type and maybe even another wrap.
- ///
- internal class Wrap
- {
- ///
- /// Gets or sets the type of the seed.
- ///
- public Type? SeedType { get; set; }
-
- ///
- /// Gets the wrapped seeds.
- ///
- public IList Wrapped { get; } = new List();
- }
-}
diff --git a/Neolution.Extensions.DataSeeding/Neolution.Extensions.DataSeeding.csproj b/Neolution.Extensions.DataSeeding/Neolution.Extensions.DataSeeding.csproj
index de098f9..ae556e9 100644
--- a/Neolution.Extensions.DataSeeding/Neolution.Extensions.DataSeeding.csproj
+++ b/Neolution.Extensions.DataSeeding/Neolution.Extensions.DataSeeding.csproj
@@ -1,12 +1,12 @@
- net6.0
+ net6.0;net8.0
enable
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Neolution.Extensions.DataSeeding/Seeder.cs b/Neolution.Extensions.DataSeeding/Seeder.cs
index 0e8a145..6edc8cd 100644
--- a/Neolution.Extensions.DataSeeding/Seeder.cs
+++ b/Neolution.Extensions.DataSeeding/Seeder.cs
@@ -1,12 +1,11 @@
namespace Neolution.Extensions.DataSeeding
{
using System;
- using System.Collections.Generic;
+ using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.Internal;
///
internal sealed class Seeder : ISeeder
@@ -30,30 +29,18 @@ public Seeder(IServiceProvider serviceProvider, ILogger logger)
///
public async Task SeedAsync()
{
- this.logger.LogDebug("Determine seeding dependencies and wrap the seeds up accordingly");
- var wraps = Seeding.Instance.WrapSeeds();
+ this.logger.LogDebug("Resolving seed dependencies using topological sort");
- if (this.logger.IsEnabled(LogLevel.Debug))
- {
- this.logger.LogDebug("Resolved dependency tree of the seeds:");
- for (var index = 0; index < wraps.Count; index++)
- {
- var wrap = wraps[index];
- this.LogWrapTree(wrap, index == wrap.Wrapped.Count - 1);
- }
- }
-
- this.logger.LogTrace("Create a list of sorted seeds based on the dependency tree");
- var sortedSeeds = new List();
- Seeding.Instance.RecursiveUnwrap(wraps, sortedSeeds);
+ var sortedSeeds = Internal.DependencyResolver.ResolveDependencies(Seeding.Instance.Seeds);
- if (this.logger.IsEnabled(LogLevel.Trace))
+ if (this.logger.IsEnabled(LogLevel.Debug))
{
- this.logger.LogTrace("The seeds will be seeded in the following order:");
+ this.logger.LogDebug("Dependency resolution completed. Seeds will be executed in the following order:");
for (var index = 0; index < sortedSeeds.Count; index++)
{
var seed = sortedSeeds[index];
- this.logger.LogTrace("{Index}.\t{SeedTypeName}", index + 1, seed.GetType().Name);
+ var dependencies = GetDependencyDescription(seed);
+ this.logger.LogDebug("{Index}. {SeedTypeName}{Dependencies}", index + 1, seed.GetType().Name, dependencies);
}
}
@@ -69,6 +56,7 @@ public async Task SeedAsync()
// Use the concrete type to ensure proper resolution
var seedType = seed.GetType();
var scopedSeed = (ISeed)scope.ServiceProvider.GetRequiredService(seedType);
+ this.logger.LogTrace("Executing seed: {SeedTypeName}", scopedSeed.GetType().Name);
await scopedSeed.SeedAsync().ConfigureAwait(false);
}
}
@@ -76,38 +64,21 @@ public async Task SeedAsync()
this.logger.LogDebug("All seeds have been seeded!");
}
- ///
- public async Task SeedAsync()
- where T : Seed
- {
- var seed = Seeding.Instance.FindSeed();
- await seed.SeedAsync().ConfigureAwait(false);
- }
-
///
- /// Logs the wrap tree in a pretty format.
+ /// Gets a description of the seed's dependencies for logging purposes.
///
- /// The wrap.
- /// if set to true it's the last wrap.
- /// The indent.
- private void LogWrapTree(Wrap wrap, bool last, string indent = "")
+ /// The seed to describe.
+ /// A formatted dependency description.
+ private static string GetDependencyDescription(ISeed seed)
{
- var seedTypeName = wrap.SeedType?.Name ?? string.Empty;
+ var dependencies = Internal.DependencyResolver.GetSeedDependencies(seed);
- // Shorten the seed type name if it was suffixed with "Seed"
- const string suffix = "Seed";
- if (seedTypeName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
+ if (dependencies.Length > 0)
{
- seedTypeName = seedTypeName[..^suffix.Length];
+ return $" (depends on: {string.Join(", ", dependencies.Select(t => t.Name))})";
}
- this.logger.LogDebug("{Indent}+- {SeedTypeName}", indent, seedTypeName);
- indent += last ? " " : "| ";
-
- for (var i = 0; i < wrap.Wrapped.Count; i++)
- {
- this.LogWrapTree(wrap.Wrapped[i], i == wrap.Wrapped.Count - 1, indent);
- }
+ return " (no dependencies)";
}
}
}
diff --git a/Neolution.Extensions.DataSeeding/Seeding.cs b/Neolution.Extensions.DataSeeding/Seeding.cs
index ec2ce6e..f9a5f53 100644
--- a/Neolution.Extensions.DataSeeding/Seeding.cs
+++ b/Neolution.Extensions.DataSeeding/Seeding.cs
@@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neolution.Extensions.DataSeeding.Abstractions;
- using Neolution.Extensions.DataSeeding.Internal;
///
/// The Seeding singleton.
@@ -29,11 +28,6 @@ internal sealed class Seeding
///
private IServiceProvider? serviceProvider;
- ///
- /// The seeds
- ///
- private IReadOnlyList seeds = Enumerable.Empty().ToList();
-
///
/// Prevents a default instance of the class from being created.
///
@@ -82,56 +76,37 @@ internal void UseServiceProvider(IServiceProvider serviceProvider)
logger.LogTrace("Assembly full name: '{SeedsAssemblyFullName}'", this.seedsAssembly.FullName);
}
- // Resolve all seeds that are registered (temporarily for dependency analysis)
- // Use a temporary scope to handle scoped dependencies during analysis
- using (var tempScope = serviceProvider.CreateScope())
- {
- this.Seeds = tempScope.ServiceProvider.GetServices().ToList();
- this.seeds = tempScope.ServiceProvider.GetServices().ToList();
- } // Dispose the temporary scope - seeds will be resolved fresh during execution
-
- logger.LogDebug("{SeedsCount} seeds have been found and loaded", this.Seeds.Count + this.seeds.Count);
- logger.LogTrace("{SeedCount} ISeed implementations found", this.Seeds.Count);
- logger.LogTrace("{SeedCount} Seed implementations found", this.seeds.Count);
- logger.LogDebug("Seeding instance ready");
- }
-
- ///
- /// Wraps up all the seeds that do not depend on another seed.
- ///
- /// The wrapped seeds.
- internal IList WrapSeeds()
- {
- return this.FindDependentSeeds()
- .OrderBy(seed => seed.Priority)
- .Select(seed => this.Wrap(seed.GetType()))
- .ToList();
- }
-
- ///
- /// Recursively unwraps the containing seeds and push them into a sorted list.
- ///
- /// The wraps.
- /// The list of already sorted seeds.
- internal void RecursiveUnwrap(IEnumerable wraps, List sortedSeeds)
- {
- foreach (var wrap in wraps)
+ using (var seedResolveScope = serviceProvider.CreateScope())
{
- var seed = this.Unwrap(wrap);
- sortedSeeds.Add(seed);
- this.RecursiveUnwrap(wrap.Wrapped, sortedSeeds);
+ var seeds = seedResolveScope.ServiceProvider.GetServices().ToList();
+
+ // Check for duplicate seed types immediately after collection
+ var seedTypes = seeds.Select(seed => seed.GetType()).ToList();
+ var duplicateTypes = seedTypes.GroupBy(type => type)
+ .Where(group => group.Count() > 1)
+ .Select(group => group.Key)
+ .ToList();
+
+ if (duplicateTypes.Any())
+ {
+ var duplicateTypeNames = string.Join(", ", duplicateTypes.Select(type => type.FullName));
+ var message = $"Duplicate seed type(s) detected: {duplicateTypeNames}.\n" +
+ "This usually means the same seed class was registered more than once.\n" +
+ "Possible causes:\n" +
+ "- Multiple calls to AddDataSeeding() for the same assembly\n" +
+ "- Overlapping assembly scans\n" +
+ "- Manual registration of seeds in addition to automatic scanning\n" +
+ "\nPlease review your dependency injection setup to ensure each seed type is only registered once.";
+
+ throw new InvalidOperationException(message);
+ }
+
+ this.Seeds = seeds;
}
- }
- ///
- /// Finds the seed.
- ///
- /// The type of the seed.
- /// The found .
- internal Seed FindSeed()
- where T : Seed
- {
- return this.seeds.Single(x => x.GetType() == typeof(T));
+ logger.LogDebug("{SeedsCount} seeds have been found and loaded", this.Seeds.Count);
+ logger.LogTrace("{SeedCount} seed implementations found", this.Seeds.Count);
+ logger.LogDebug("Seeding instance ready");
}
///
@@ -147,44 +122,5 @@ internal IServiceScope CreateScope()
return this.serviceProvider.CreateScope();
}
-
- ///
- /// Finds the seeds that depend on the specified seed type.
- ///
- /// Type of the seed.
- /// The dependent seeds.
- private IEnumerable FindDependentSeeds(Type? seedType = null)
- {
- return this.Seeds.Where(x => x.DependsOn == seedType).ToList();
- }
-
- ///
- /// Recursively Wraps the specified seed type.
- ///
- /// Type of the seed.
- /// The wrapped seed(s).
- private Wrap Wrap(Type? seedType)
- {
- var wrap = new Wrap { SeedType = seedType };
-
- var dependentSeeds = this.FindDependentSeeds(wrap.SeedType);
- foreach (var seed in dependentSeeds)
- {
- var wrapped = this.Wrap(seed.GetType());
- wrap.Wrapped.Add(wrapped);
- }
-
- return wrap;
- }
-
- ///
- /// Unwraps the specified wrap.
- ///
- /// The wrap.
- /// The containing seed.
- private ISeed Unwrap(Wrap wrap)
- {
- return this.Seeds.Single(x => x.GetType() == wrap.SeedType);
- }
}
}
diff --git a/Neolution.Extensions.DataSeeding/ServiceCollectionExtensions.cs b/Neolution.Extensions.DataSeeding/ServiceCollectionExtensions.cs
index fff1e9d..4983102 100644
--- a/Neolution.Extensions.DataSeeding/ServiceCollectionExtensions.cs
+++ b/Neolution.Extensions.DataSeeding/ServiceCollectionExtensions.cs
@@ -13,7 +13,17 @@ public static class ServiceCollectionExtensions
/// Adds the data seeding functionality to the service collection.
///
/// The services.
- /// The assembly.
+ public static void AddDataSeeding(this IServiceCollection services)
+ {
+ var assembly = Assembly.GetCallingAssembly();
+ AddDataSeeding(services, assembly);
+ }
+
+ ///
+ /// Adds the data seeding functionality to the service collection.
+ ///
+ /// The services.
+ /// The assembly containing the seed implementations.
public static void AddDataSeeding(this IServiceCollection services, Assembly assembly)
{
services.AddTransient();
@@ -26,14 +36,6 @@ public static void AddDataSeeding(this IServiceCollection services, Assembly ass
.AsSelf()
.AsImplementedInterfaces()
.WithTransientLifetime());
-
- // Register Seed implementations
- services.Scan(scan => scan
- .FromAssemblies(assembly)
- .AddClasses(classes => classes.AssignableTo())
- .AsSelf()
- .As()
- .WithTransientLifetime());
}
}
}
diff --git a/README.md b/README.md
index 20b0b13..2d2f6e2 100644
--- a/README.md
+++ b/README.md
@@ -1,47 +1,242 @@
# DataSeeding
-## Introduction
+## What's this about?
+
From the [database seeding](https://en.wikipedia.org/wiki/Database_seeding) article in Wikipedia:
+
> Database seeding is populating a database with an initial set of data. It's common to load seed data such as initial user accounts or dummy data upon initial setup of an application.
-The code needed to create an initial state of data for an application can get overwhelmingly large, even for smaller applications. This means that the source code file responsible for creating, generating, initializing, loading, transforming (...) your data, will eventually end up having thousands of lines of code and tons of different dependencies. When not properly organized, this could create maintainability issues for your application.
+We've all been there - your data seeding code starts simple but grows into a monster file with thousands of lines and complex dependencies. This library helps you split that mess into manageable chunks with proper dependency handling.
+
+## Key Features
+
+- **Attribute-Based Dependencies**: Clean syntax using `[DependsOn(typeof(...))]` attributes
+- **Multi-Dependency Support**: Seeds can depend on multiple other seeds
+- **Circular Dependency Detection**: Catches circular dependencies with clear error messages
+- **Scoped Service Support**: Proper handling of scoped dependencies with lifetime management
+- **Topological Sorting**: Dependency resolution using Kahn's algorithm for execution order
+- **Service Lifetime Safety**: Prevents common DI issues like ObjectDisposedException
+
+## Quick Start
+
+### Add to dependency injection
+
+For Microsoft Dependency Injection:
+
+```csharp
+services.AddDataSeeding();
+```
-This library aims to help developers to divide the whole data seeding logic of an application into small chunks of logic.
+This automatically scans the calling assembly for `ISeed` implementations and prepares them for ordered execution.
-## Getting Started
-In most use-cases there are only two relevant interfaces:
+#### Advanced Usage
-### Add the functionality to the dependency injection container
-For Microsoft Dependency Injection, there is already an extension method built in:
+If your seeds are in a different assembly than the one calling the registration, use the explicit overload:
- services.AddDataSeeding(typeof(Startup).Assembly);
+```csharp
+services.AddDataSeeding(typeof(Startup).Assembly);
+```
-This configures DataSeeding to scan the passed assembly to look for `ISeed` implementations. All found implementations will then prepared and properly ordered for the data seeding.
+**Note**: The parameterless overload is preferred for most scenarios. Only use the assembly parameter when seeds are located in a different project/assembly.
### The `ISeed` interface
-The class that contains a chunk of the data seeding logic is called **seed**. To make the library pick up your seeds, they have to implement `ISeed` interface.
-In the `SeedAsync()` method you can add your data seeding logic. The seed will be instantiated by the dependency injection container, so you can use the constructor to inject services.
+Create seeds by implementing the `ISeed` interface and using the `[DependsOn]` attribute to declare dependencies:
+
+```csharp
+[DependsOn(typeof(TenantSeed), typeof(RoleSeed))]
+public class UserSeed : ISeed
+{
+ public async Task SeedAsync()
+ {
+ // Your seeding logic here
+ }
+}
+```
+
+Seeds are instantiated by the dependency injection container, so you can inject services in the constructor, including scoped services.
+
+## Dependency Management
+
+### Defining Dependencies
+
+Use the `[DependsOn]` attribute to specify dependencies. For multiple dependencies, list them in the attribute:
+
+```csharp
+[DependsOn(typeof(TenantSeed), typeof(RoleSeed))]
+public class UserSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // Your seeding logic here
+ return Task.CompletedTask;
+ }
+}
+```
+
+#### Single Dependency
+
+For seeds with only one dependency:
+
+```csharp
+[DependsOn(typeof(TenantSeed))]
+public class UserSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // Your seeding logic here
+ return Task.CompletedTask;
+ }
+}
+```
+
+#### No Dependencies
-Sometimes, you might want a certain seed to be seeded before another one. This can be achieved by implementing the optional `DependsOn` property in the dependent seed. Make the property return the type of the seed you want to have seeded before the current seed. As soon as the `ISeeder` is resolved, the optimal order of the seeds will be determined.
+Seeds without dependencies don't need the attribute:
+
+```csharp
+public class TenantSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // This seed has no dependencies
+ return Task.CompletedTask;
+ }
+}
+```
+
+### Scoped Service Injection
+
+The library handles scoped services properly:
+
+```csharp
+public class UserSeed : ISeed
+{
+ private readonly UserManager userManager;
+ private readonly ApplicationDbContext context;
+ private readonly ILogger logger;
+
+ public UserSeed(
+ UserManager userManager,
+ ApplicationDbContext context,
+ ILogger logger)
+ {
+ this.userManager = userManager;
+ this.context = context;
+ this.logger = logger;
+ }
+
+ public async Task SeedAsync()
+ {
+ var user = new User { UserName = "admin" };
+ await this.userManager.CreateAsync(user, "Password123!");
+ }
+}
+```
+
+**Benefits:**
+
+- Each seed gets a fresh DI scope
+- Scoped services are automatically disposed after execution
+- Prevents ObjectDisposedException and scope validation errors
### The `ISeeder` interface
-The `ISeeder` interface can be resolved from the service provider. It contains the logic to find all seeds in a specified assembly and seed them in an appropriate order.
-
- public class DataInitializer
+
+Resolve `ISeeder` from the service provider to execute seeds:
+
+```csharp
+public class DataInitializer
+{
+ private readonly ISeeder seeder;
+
+ public DataInitializer(ISeeder seeder)
+ {
+ this.seeder = seeder;
+ }
+
+ public async Task RunAsync()
{
- private readonly ISeeder seeder;
+ await this.seeder.SeedAsync();
+ }
+}
+```
+
+## Features
+
+### Circular Dependency Detection
+
+The library detects circular dependencies and throws clear error messages:
- public DataInitializer(ISeeder seeder)
- {
- this.seeder = seeder;
- }
+```csharp
+// This will throw an InvalidOperationException
+[DependsOn(typeof(SeedB))]
+public class SeedA : ISeed
+{
+ public Task SeedAsync() => Task.CompletedTask;
+}
- public void Run()
- {
- this.seeder.SeedAsync();
- }
+[DependsOn(typeof(SeedA))] // Circular!
+public class SeedB : ISeed
+{
+ public Task SeedAsync() => Task.CompletedTask;
+}
+```
+
+Detects all types of cycles:
+
+- Simple cycles: A → B → A
+- Complex cycles: A → B → C → A
+- Multi-level cycles: A → B → C → D → A
+
+### Complex Dependencies
+
+Seeds can have multiple dependencies:
+
+```csharp
+[DependsOn(typeof(TenantSeed), typeof(RoleSeed), typeof(PermissionSeed))]
+public class ComplexSeed : ISeed
+{
+ // Executes only after ALL dependencies complete
+ public async Task SeedAsync() { /* ... */ }
+}
+```
+
+## Example Usage
+
+```csharp
+// Base seed with no dependencies
+public class UserRolesSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // Seed user roles
+ return Task.CompletedTask;
}
+}
+
+// Single dependency
+[DependsOn(typeof(UserRolesSeed))]
+public class UsersSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // Seed users (requires roles first)
+ return Task.CompletedTask;
+ }
+}
+
+// Multiple dependencies
+[DependsOn(typeof(UsersSeed), typeof(PermissionsSeed))]
+public class UserPermissionsSeed : ISeed
+{
+ public Task SeedAsync()
+ {
+ // Seed user permissions (requires both users and permissions)
+ return Task.CompletedTask;
+ }
+}
+```
+
+## Demo
-### Samples
-Check out the sample console application and the unit test in the repository.
\ No newline at end of file
+Check out the [demo project](./Neolution.Extensions.DataSeeding.Demo) for a complete CMS seeding example with 13 interconnected seeds.