Generating initial data for your Backend.Fx application on boot.
All implementations of the abstract DataSeeder are auto wired and executed on application boot. When the target
seeding level of the seeder matches the application's seeding level, the seeder is executed.
Every seeder is executed in a transaction. If any seeder fails, the application will be considered as not booted successfully and won't be functional.
During seeding, the application is switched to SingleUserMode, so that no online transaction processing can be done
until the seeding completes.
Dependent seeders are executed in the correct order using the topological sort algorithm.
- Add a reference to the
Backend.Fx.DataSeedingpackage in your domain assembly - Start implementing your seeders by deriving from the
DataSeederclass. Note that seeders run on every application boot, so they should be idempotent. - Add a reference to the
Backend.Fx.DataSeeding.Featurepackage in your composition assembly (that's where yourBackendFxApplicationclass lives) - Enable the feature in the
BackendFxApplicationclass
public class MyCoolApplication
{
public MyCoolApplication() : base(new SimpleInjectorCompositionRoot(), new ExceptionLogger(), GetAssemblies())
{
CompositionRoot.RegisterModules( ... );
EnableFeature(new DataSeedingFeature(DataSeedingLevel.Production));
}
}public class DepartmentSeeder : DataSeeder
{
private readonly IDepartmentRepository _departmenRepository;
public DepartmentSeeder(
IOrganizationalUnitRepository organizationalUnitRepository,
IDepartmentRepository departmenRepository)
{
// we can use injection here
_organizationalUnitRepository = organizationalUnitRepository;
_departmenRepository = departmenRepository;
// add a dependency to a seeded that needs to run before this one
AddDependency<OrganizationalUnitSeeder>();
}
// this seeder should only run for development applictions
public override DataSeedingLevel Level => DataSeedingLevel.Development;
protected override async Task SeedDataAsync(CancellationToken cancellationToken)
{
// here we can do whatever is needed to generate data. Feel free to be creative.
var organizationalUnits = await _organizationalUnitRepository.GetAllAsync(cancellationToken);
foreach (var organizationalUnit in organizationalUnits)
{
for (var i = 0; i < Random.Shared.Next(10); i++)
{
var department = new Department($"D{i:000}", organizationalUnit.Id);
await _departmenRepository.AddAsync(costCenter, cancellationToken);
}
}
}
protected override async Task<bool> ShouldRun(CancellationToken cancellationToken)
{
// every seeder needs to be idempotent. So we check if any data is already there.
bool hasAny = await _departmenRepository.HasAnyAsync(cancellationToken);
return hasAny == false;
}
}Data seeding cannot be executed in parallel. Therefore, the feature by default uses a SemaphoreSlim to ensure that
seeding is only executed once. Consequently, this works only in a single process environment.
When running in a distributed environment, you might want to provide a distributed mutex implementation when enabling the feature.
EnableFeature(new DataSeedingFeature(DataSeedingLevel.Production, new MyDistributedSeedingMutex()));An excellent approach backed by different database management systems can be found in the DistributedLock package.