data-seeder-generator
ABP Data Seeder Contributor Generator
Generates production-ready data seeder contributors following this project's conventions and ABP Framework best practices. This skill creates classes that implement IDataSeedContributor for seeding initial, reference, or lookup data into the database.
Key feature: Auto-detects project structure, existing entities, namespaces, and seeder patterns before generating — no manual configuration needed.
When to Use
- Adding initial lookup/reference data (e.g., categories, types, enumerations)
- Seeding default configuration or system data
- Setting up multi-tenant default data
- Creating data migrations with seed content
- Populating test/development data
- Any data that should exist when the application first runs or when a new tenant is provisioned
Step 0 — Auto-Detect Project Structure
Do this automatically before asking the user anything. Never ask what you can detect.
0.1 Find Solution and Project Name
# Find solution file
find . -name "*.sln" | head -3
# Extract project/solution name
basename $(find . -name "*.sln" | head -1) .sln
# Find all .csproj files and classify layers
find . -name "*.csproj" | sort
# *.Domain.csproj → Domain layer
# *.Domain.Shared.csproj → Domain Shared (constants, enums)
# *.Application.csproj → Application layer
# *.EntityFrameworkCore.csproj → EF Core layer
# *.DbMigrator.csproj → DbMigrator (tenant-aware seeders go here)
# *.HttpApi.Host.csproj → Host layer
0.2 Detect Root Namespace
# Extract namespace from any existing AppService or entity
grep -m1 "^namespace " $(find . -name "*AppService.cs" ! -path "*/bin/*" | head -1) 2>/dev/null
# Fallback: read from .csproj RootNamespace
grep "RootNamespace" $(find . -name "*.Domain.csproj" | head -1) 2>/dev/null
0.3 Detect Existing Seeder Patterns
# Find all existing seeders — most important step
find . -name "*DataSeed*" -o -name "*SeedContributor*" -o -name "*SeederContributor*" \
| grep "\.cs$" ! -path "*/bin/*" ! -path "*/obj/*" | head -10
# Read EVERY existing seeder found — learn the exact pattern used
# view each file found above
From existing seeders, extract:
- Constructor style (primary constructor vs traditional)
- Logging pattern (
_logger.LogInformationmessages format) - Existence check pattern (
AnyAsyncvsFirstOrDefaultAsyncvs count) - Insert pattern (
InsertAsyncvsInsertManyAsync) - UoW pattern (
[UnitOfWork]vs explicitIUnitOfWorkManager) - Whether
IClientSeedingServiceis used - Whether feature flags are used
- Namespace convention
0.4 Detect Existing Entities
# Find all domain entities
find . -name "*.cs" -path "*/Domain/*" ! -path "*/bin/*" ! -path "*/obj/*" \
! -path "*/Tests/*" ! -path "*/Data/*" | head -30
# Detect which use AggregateRoot vs Entity base class
grep -rl "AggregateRoot\|FullAuditedAggregateRoot\|Entity<" \
--include="*.cs" . | grep -v "bin\|obj\|Tests" | head -20
# Read each entity to extract:
# - Property names and types
# - Required vs optional fields
# - Any SystemName / unique identifier property
# - Enum properties (need Domain.Shared scan too)
# Example:
view("src/{Project}.Domain/Entities/Customers/Customer.cs")
0.5 Detect Entity Constants (Domain.Shared)
# Find constants files in Domain.Shared
find . -name "*Consts*" -path "*/Domain.Shared/*" ! -path "*/bin/*" | head -10
# Read to find Items collections
grep -rn "public static class Items" --include="*.cs" . | head -10
# Read each consts file found
# view each consts file
0.6 Detect Enums
# Find all enum definitions
grep -rn "public enum" --include="*.cs" . \
! -path "*/bin/*" ! -path "*/obj/*" | head -20
# Read enum files for first/default values
find . -name "*.cs" -path "*/Domain.Shared/*" | xargs grep -l "public enum" 2>/dev/null | head -5
# view each found
0.7 Detect Permission Constants
# Find permission files
find . -name "*Permissions*.cs" ! -path "*/bin/*" | head -5
# Extract all permission constants
grep -rn "public const string" --include="*Permissions*.cs" . | head -30
# Find PermissionDefinitionProvider
find . -name "*PermissionDefinitionProvider.cs" | head -3
# view it
0.8 Detect Multi-Tenancy
# Check if multi-tenancy is enabled
grep -rn "IsMultiTenant\|MultiTenancy\|ITenantRepository\|ITenantManager" \
--include="*.cs" . | head -5
# Check if IClientSeedingService exists (project-specific)
grep -rn "IClientSeedingService" --include="*.cs" . | head -5
# Find tenant constants
find . -name "*TenantConsts*" ! -path "*/bin/*" | head -3
# view if found
0.9 Detect Seeder File Locations
# Check all 3 possible seeder locations
ls src/*.Domain/Entities/ 2>/dev/null
ls src/*.Domain/Data/ 2>/dev/null
ls src/*.DbMigrator/ 2>/dev/null
# Determine which is used by existing seeders
find . -name "*DataSeed*" -o -name "*SeedContributor*" | grep "\.cs$" \
! -path "*/bin/*" | head -5
0.10 Build Project Profile
After scanning, compile:
{
"projectName": "Amnil.AccessControlManagementSystem",
"rootNamespace": "Amnil.AccessControlManagementSystem",
"domainProject": "src/Amnil.AccessControlManagementSystem.Domain",
"domainSharedProject": "src/Amnil.AccessControlManagementSystem.Domain.Shared",
"dbMigratorProject": "src/Amnil.AccessControlManagementSystem.DbMigrator",
"existingEntities": [
{
"name": "Customer",
"path": "src/.../Domain/Entities/Customers/Customer.cs",
"properties": ["Name", "Email", "Phone", "IsActive"],
"hasSystemName": true,
"constsFile": "CustomerConsts.cs"
}
],
"existingSeederPattern": {
"constructorStyle": "primary",
"existenceCheck": "AnyAsync",
"insertPattern": "InsertAsync",
"uowPattern": "explicit",
"usesClientSeedingService": true,
"usesFeatureFlags": false,
"loggingFormat": "Starting {EntityName} data seeding..."
},
"isMultiTenant": true,
"permissionPrefix": "ACMSPermissions",
"seederLocations": {
"domainEntities": "src/.../Domain/Entities/{EntityPlural}/",
"domainData": "src/.../Domain/Data/",
"dbMigrator": "src/.../DbMigrator/{Category}/"
}
}
Use this profile for all code generation below.
What Gets Generated
A complete IDataSeedContributor implementation following this project's patterns.
File Structure
Data seeder contributors can be placed in different locations depending on scope:
For Domain/Entity-specific seeders:
src/{Project}.Domain/Entities/{EntityPlural}/{EntityName}DataSeederContributor.cs
For DbMigrator-specific seeders (tenant-aware, blob storage, file-based):
src/{Project}.DbMigrator/{Category}/{EntityName}DataSeederContributor.cs
For System-wide seeders:
src/{Project}.Domain/Data/{EntityName}DataSeedContributor.cs
Location is auto-selected based on seeder type:
- Tenant-aware or blob storage → DbMigrator
- Entity-specific reference data → Domain/Entities/{EntityPlural}
- System-wide → Domain/Data
Generated Code Structure
using {RequiredNamespaces};
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace {TargetNamespace};
public class {EntityName}DataSeederContributor(
{Dependencies}
) : IDataSeedContributor, ITransientDependency
{
private readonly ILogger<{EntityName}DataSeederContributor> _logger;
{OtherPrivateFields}
public async Task SeedAsync(DataSeedContext context)
{
_logger.LogInformation("Starting {EntityName} data seeding...");
try
{
{SeedLogic}
_logger.LogInformation("{EntityName} data seeding completed successfully.");
}
catch (Exception ex)
{
_logger.LogError(ex, "{EntityName} data seeding failed.");
throw;
}
}
{HelperMethods}
}
Pattern Guidelines (From Codebase Analysis)
Always use the pattern detected in Step 0.3 from existing seeders. The patterns below are fallbacks if no existing seeder is found.
1. Basic Repository-Based Seeder (Simple)
Used for: Simple entities with just property data, no complex dependencies
Example Pattern:
public async Task SeedAsync(DataSeedContext context)
{
var items = new List<(string SystemName, string DisplayName, string? Description, bool IsActive)>
{
(EntityConsts.Items.Value1, "Display Name 1", "Description", true),
(EntityConsts.Items.Value2, "Display Name 2", "Description", true),
};
await SeedItems(items);
}
private async Task SeedItems(List<(string SystemName, string DisplayName, string? Description, bool IsActive)> items)
{
foreach (var (SystemName, DisplayName, Description, IsActive) in items)
{
await AddItem(SystemName, DisplayName, Description, IsActive);
}
}
private async Task AddItem(string systemName, string displayName, string? description, bool isActive)
{
var exists = await _repository.AnyAsync(x => x.SystemName == systemName);
if (!exists)
{
await _repository.InsertAsync(new Entity
{
SystemName = systemName,
DisplayName = displayName,
Description = description,
IsActive = isActive
});
}
}
Key Points:
- Always check existence using
AnyAsyncorFirstOrDefaultAsyncbefore insert - Use tuple collections for clean data definition
- Separate seeding logic into private helper methods
- No
autoSave: truein multi-step operations (rely on UoW)
2. Repository-Based Seeder with autoSave (Simple, Single Insert)
Used for: Simple bulk inserts where each item is independent
await _repository.InsertManyAsync(items, autoSave: true);
Example: NotificationDataSeedContributor, ConfigurationDataSeederContributor
3. Tenant-Aware Seeder
Used for: Seeders in DbMigrator project that need multi-tenant logic
public async Task SeedAsync(DataSeedContext context)
{
if (!_clientSeedingService.IsDevelopment)
{
_logger.LogWarning("Tenant data seed will only take place in the Development environment.");
return;
}
var tenantNames = new List<string>
{
// Auto-detected from TenantConsts if found, else use placeholders
ACMSTenantConsts.Items.MBL,
ACMSTenantConsts.Items.NMB,
ACMSTenantConsts.Items.NCELL
};
await SeedTenants(tenantNames);
_logger.LogInformation("Tenant seeding completed. Summary: {TotalTenants} tenants processed, {AddedTenants} added, {SkippedTenants} skipped.",
totalTenants, addedTenants, skippedTenants);
}
Key Points:
- Use
IClientSeedingService.IsDevelopmentto check environment - Use
IClientSeedingService.ValidFor([...])to filter by tenant - Track added/skipped counts for summary logging
4. Feature-Flagged Seeder
Used for: Seeders that should only run when specific features are enabled
if (_featureManagementService.IsEnabled("FeatureName"))
{
// Seed feature-specific data
}
Example: WorkflowStageDataSeederContributor uses this for conditional sub-stage seeding
5. User/Role Seeder (Complex, with UserManager)
Used for: Seeders that need identity operations (password hashing, role assignment)
private readonly IdentityUserManager _userManager;
private readonly IGuidGenerator _guidGenerator;
private readonly IConfiguration _configuration;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IRepository<IdentityRole, Guid> _roleRepository;
public async Task SeedAsync(DataSeedContext context)
{
var defaultPassword = _configuration.GetValue<string?>("User:DefaultPassword")
?? IdentityDataSeedContributor.AdminPasswordDefaultValue;
var users = new List<(string UserName, string Email, string Name, string Surname, string Password, List<string> Roles, bool IsActive)>
{
("system", "system.acms@yopmail.com", "System", "Administrator", defaultPassword, new List<string> { "admin" }, true),
};
await SeedUsers(users);
}
private async Task SeedUsers(List<...> users)
{
using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
{
foreach (var userData in users)
{
await AddUser(...);
}
await uow.CompleteAsync();
}
}
private async Task AddUser(...)
{
var existingUser = await _userManager.FindByNameAsync(userName)
?? await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
_logger.LogWarning("User {UserName} already exists. Skipping creation.", userName);
return;
}
var userId = _guidGenerator.Create();
var user = new IdentityUser(userId, userName, email) { ... };
var result = await _userManager.CreateAsync(user);
result.CheckErrors();
var addPasswordResult = await _userManager.AddPasswordAsync(user, password);
addPasswordResult.CheckErrors();
(await _userManager.SetLockoutEnabledAsync(user, true)).CheckErrors();
if (roles != null && roles.Any())
{
await _userManager.SetRolesAsync(user, validRoles);
}
}
Key Points:
- Use
IdentityUserManagerfor user operations (not repository) - Use
IGuidGenerator.Create()for GUIDs - Always use
CheckErrors()on IdentityResult - Wrap in explicit UoW if not using
[UnitOfWork]attribute - Check both user name and email for existing users
6. Blob Storage Seeder
Used for: Seeding files into blob storage
private readonly IBlobContainer<{ContainerName}> _fileContainer;
public async Task SeedAsync(DataSeedContext context)
{
var templatePath = Path.Combine(baseDirectory, "Templates");
var templateFileNames = new[]
{
BulkImportFileConsts.TemplateFileNames.Application,
BulkImportFileConsts.TemplateFileNames.PMTransfer,
};
foreach (var fileName in templateFileNames)
{
var fullPath = Path.Combine(templatePath, fileName);
await TrySeedFileAsync(fullPath, fileName);
}
}
private async Task TrySeedFileAsync(string filePath, string fileName)
{
if (!File.Exists(filePath))
{
_logger.LogWarning("File not found: {Path}", filePath);
return;
}
try
{
await using var fileStream = File.OpenRead(filePath);
await _fileContainer.SaveAsync(fileName, fileStream, true);
_logger.LogInformation("Seeded file: {FileName}", fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to seed file: {FileName}", fileName);
throw;
}
}
Key Points:
- Use
IBlobContainer<T>for blob operations - Handle file-not-found gracefully (log warning, return)
- Use
truefor overwrite parameter inSaveAsync
Common Dependencies and Their Uses
| Dependency | Purpose | Where Used | Auto-Detected? |
|---|---|---|---|
IRepository<Entity, Guid> |
Basic CRUD operations | All entity seeders | ✅ From entity scan |
ILogger<T> |
Structured logging | All seeders | ✅ Always added |
IdentityUserManager |
User creation/password/roles | User seeder | ✅ If user seeder requested |
ITenantManager |
Tenant creation | Tenant seeder | ✅ If multi-tenant detected |
IClientSeedingService |
Development/tier check | DbMigrator seeders | ✅ If found in codebase |
IFeatureManagementService |
Feature flag checks | Feature-gated seeders | ✅ If found in codebase |
IGuidGenerator |
Generate GUIDs | When creating entities manually | ✅ If user seeder |
IUnitOfWorkManager |
Manual UoW control | Complex multi-repo operations | ✅ From existing pattern |
IBlobContainer<T> |
File/blob storage | Template/file seeders | ❌ User must request |
IConfiguration |
Read config values | Password, API keys | ✅ If user seeder |
Interface Contract
All data seeder contributors must:
- Implement
IDataSeedContributorfromVolo.Abp.Data - Implement
ITransientDependency(enables DI as transient) - Have a single public method:
Task SeedAsync(DataSeedContext context) - Accept
DataSeedContextparameter (provides tenant ID, properties) - Use
[UnitOfWork]attribute OR explicitIUnitOfWorkManagerfor transactions
Logging Best Practices
_logger.LogInformation("Starting {EntityName} data seeding..."); // At start
_logger.LogDebug("Seeding item: {ItemKey}", key); // Per-item (verbose)
_logger.LogInformation("Created {Count} items"); // Summary with counts
_logger.LogWarning("Item already exists: {Key}"); // For skipped items
_logger.LogInformation("{EntityName} data seeding completed successfully."); // At end
// In catch:
_logger.LogError(ex, "{EntityName} data seeding failed.");
throw; // Re-throw to abort seeding
DataSeedContext Properties
The DataSeedContext provides:
context.TenantId— current tenant ID (null for host)- Custom properties via
WithProperty()— used by ABP built-in seeders
Typically only needed for multi-tenant-aware logic.
Usage Instructions
1. Auto-Detection First (NEW)
The skill runs Step 0 automatically before asking anything. It will:
- Detect your project name, namespace, and layer paths
- Find all existing entities and their properties
- Find all existing seeders and learn their pattern
- Detect multi-tenancy, permissions, and constants
- Pre-fill most answers so you confirm rather than type
2. Confirm or Override Detected Values
After auto-detection the skill will show:
Auto-Detected Project Profile:
Project: Amnil.AccessControlManagementSystem
Namespace: Amnil.AccessControlManagementSystem
Entities: Customer, Operation, WorkflowStage, Notification
Pattern: Primary constructor, AnyAsync check, InsertAsync
Multi-tenant: Yes (IClientSeedingService detected)
Seeder style: Matches NotificationDataSeedContributor.cs
Entities available for seeding:
1. Customer (Name, Email, Phone, IsActive, SystemName)
2. Operation (SystemName, DisplayName, Description, IsActive)
3. WorkflowStage (SystemName, DisplayName, Order, IsActive)
Which entity do you want to seed? (or "all" for all entities)
What type of seeder? (basic / tenant-aware / user-role / blob / feature-flagged)
3. Prepare Entity and Constants
Ensure your entity:
- Has a
SystemNameproperty (string, unique) for programmatic identification - Has appropriate constructors
- Has a constants class with
Itemscollection (recommended):
// Auto-generated if not found:
public static class {EntityName}Consts
{
public static class Items
{
public const string Value1 = "VALUE1";
public const string Value2 = "Value2";
}
}
4. Generate and Integrate
The skill will generate:
- Complete C# class file with correct namespace (auto-detected)
- All required using statements (auto-detected from entity + pattern)
- Constructor with dependency injection (matches detected constructor style)
- SeedAsync implementation with try-catch and logging
- Private helper methods
- EntityConsts class if not already present
After generation:
- File is placed in the correct auto-detected location
- Seeder is automatically discovered by
IDataSeederon migration/startup - Test with:
dotnet runin the DbMigrator project
Print Summary After Generation
═══════════════════════════════════════════════════════════════════
Data Seeder Generator — Complete
═══════════════════════════════════════════════════════════════════
Auto-Detected:
✓ Project: Amnil.AccessControlManagementSystem
✓ Namespace: Amnil.AccessControlManagementSystem
✓ Entities Found: 4 (Customer, Operation, WorkflowStage, Notification)
✓ Pattern From: NotificationDataSeedContributor.cs
✓ Multi-Tenant: Yes — IClientSeedingService detected
✓ Constructor: Primary constructor style
✓ Existence Check: AnyAsync
✓ Insert Pattern: InsertAsync (no autoSave)
Files Generated:
✓ CustomerDataSeederContributor.cs
→ src/.../Domain/Entities/Customers/
→ Pattern: Basic (AnyAsync + InsertAsync)
→ 2 sample records seeded
Next Steps:
1. Review generated seed values
2. Update {EntityName}Consts.Items if needed
3. Run: dotnet run --project src/{Project}.DbMigrator
4. Verify data in database
═══════════════════════════════════════════════════════════════════
Examples from This Project
Simple Entity Seeder
NotificationDataSeedContributor.cs— Notification mediums and recipient typesConfigurationDataSeederContributor.cs— System configuration valuesOperationDataSeederContributor.cs— Operation definitions with tenant filtering
Complex Seeder
UserDataSeederContributor.cs— User creation with passwords and rolesWorkflowStageDataSeederContributor.cs— Multi-entity (stages + sub-stages) with relationships and feature flags
DbMigrator-Specific Seeder
TenantDataSeederContributor.cs— Tenant creation with statisticsBulkImportTemplateDataSeederContributor.cs— File seeding to blob storage
Important Notes
- Seeding is Idempotent: Always check existence before insert (
AnyAsync,FirstOrDefaultAsync, or count checks) - No Hard-Coded IDs: Use
Guid.NewGuid()or repository-generated IDs - Context Matters: Seeders in
Domainlayer run for all tenants;DbMigratorseeders have access to tenant management services - Order Not Guaranteed: If ordering needed (FK dependencies), seed in sequence within same contributor
- Development vs Production: Use
IClientSeedingService.IsDevelopmentto skip certain seeds in non-development environments - Performance: Batch inserts with
InsertManyAsyncwhen possible, but be careful withautoSave: falsein loops (wrap in UoW)
Related Skills
crud-generator— Generate CRUD AppServices for entitiesapi-spec-to-service— Generate services from API specifications
Troubleshooting
| Issue | Solution |
|---|---|
| Seeder not running | Check that the class implements both IDataSeedContributor and ITransientDependency |
| Duplicate data | Ensure idempotency checks (existence checks) are in place |
| Guid issues | Use Guid.NewGuid() or IGuidGenerator.Create() |
| Missing dependencies | Add required services to constructor; ensure they're registered in DI |
| Order dependency | Add explicit ordering logic within SeedAsync or seed related entities in same contributor |
| Tenant data not seeding | Use DbMigrator project for tenant operations; ensure multi-tenancy is enabled |
| Auto-detection failed | Skill falls back to asking user — provide entity name and namespace manually |
More from thapaliyabikendra/ai-artifacts
abp-infrastructure-patterns
ABP Framework cross-cutting patterns including authorization, background jobs, distributed events, multi-tenancy, and module configuration. Use when: (1) defining permissions, (2) creating background jobs, (3) publishing/handling distributed events, (4) configuring modules.
59clean-code-dotnet
Clean Code principles adapted for C#/.NET including naming, variables, functions, SOLID, error handling, and async patterns. Use when: (1) reviewing C# code, (2) refactoring for clarity, (3) writing new code, (4) code review feedback.
52abp-entity-patterns
ABP Framework domain layer patterns including entities, aggregates, repositories, domain services, and data seeding. Use when: (1) creating entities with proper base classes, (2) implementing custom repositories, (3) writing domain services, (4) seeding data.
51abp-api-implementation
Implement REST APIs in ABP Framework with AppServices, DTOs, pagination, filtering, and authorization. Use when building API endpoints for ABP applications.
46abp-service-patterns
ABP Framework application layer patterns including AppServices, DTOs, Mapperly mapping, Unit of Work, and common patterns like Filter DTOs and ResponseModel. Use when: (1) creating AppServices, (2) mapping DTOs with Mapperly, (3) implementing list filtering, (4) wrapping API responses.
44abp-contract-scaffolding
Generate ABP Application.Contracts layer scaffolding (interfaces, DTOs, permissions) from technical design. Enables parallel development by abp-developer and qa-engineer. Use when: (1) backend-architect needs to generate contracts, (2) preparing for parallel implementation workflow, (3) creating API contracts before implementation.
36