FluentSeeding.AspNetCore
0.2.1
dotnet add package FluentSeeding.AspNetCore --version 0.2.1
NuGet\Install-Package FluentSeeding.AspNetCore -Version 0.2.1
<PackageReference Include="FluentSeeding.AspNetCore" Version="0.2.1" />
<PackageVersion Include="FluentSeeding.AspNetCore" Version="0.2.1" />
<PackageReference Include="FluentSeeding.AspNetCore" />
paket add FluentSeeding.AspNetCore --version 0.2.1
#r "nuget: FluentSeeding.AspNetCore, 0.2.1"
#:package FluentSeeding.AspNetCore@0.2.1
#addin nuget:?package=FluentSeeding.AspNetCore&version=0.2.1
#tool nuget:?package=FluentSeeding.AspNetCore&version=0.2.1
FluentSeeding
A fluent, composable data seeding library for .NET. Define how your entities are generated using a clean, chainable API, then wire them up to your database with first-class Entity Framework Core and ASP.NET Core support.
Packages
| Package | Description |
|---|---|
FluentSeeding |
Core library with builders, rules, idempotency |
FluentSeeding.EntityFrameworkCore |
EF Core persistence layer |
FluentSeeding.AspNetCore |
Microsoft DI and hosted application integration |
Quick Start
1. Define a seeder
public sealed class UserSeeder : EntitySeeder<User>
{
protected override void Configure(SeedBuilder<User> builder)
{
builder.Count(10)
.RuleFor(u => u.Id).UseFactory(Guid.NewGuid)
.RuleFor(u => u.Name).UseValue("Test User")
.RuleFor(u => u.Email).UseFactory(i => $"user{i}@example.com");
}
}
2. Register with DI
builder.Services.AddFluentSeeding(seederConfig =>
seederConfig
.AddSeeder<UserSeeder>()
.AddSeeder<ProductSeeder>()
.AddSeeder<PurchaseSeeder>());
// Register an EF Core persistence layer
builder.Services.AddFluentSeedingEntityFrameworkCore<SampleDbContext>(options =>
options.ConflictBehavior = ConflictBehavior.Skip);
Alternatively, register all seeders within an assembly:
builder.Services.AddFluentSeeding(seeders => { seeders.AddSeedersFromAssemblyContaining<OrderSeeder>(); })
3. Run seeders
var app = builder.Build();
await app.RunSeedersAsync();
await app.RunAsync();
Core Concepts
SeedBuilder<T>
The fluent builder that describes how to generate instances of T.
var builder = new SeedBuilder<Product>();
builder.Count(5)
.RuleFor(p => p.Id).UseFactory(Guid.NewGuid)
.RuleFor(p => p.Name).UseFrom("Widget", "Gadget", "Doohickey")
.RuleFor(p => p.Price).UseFactory(() => Math.Round(Random.Shared.NextDouble() * 100, 2));
IEnumerable<Product> products = builder.Build();
Call Build() to materialize the entities. Rules execute in dependency order (see Dependencies).
RuleFor
RuleFor(selector) opens a rule for a property. Chain a terminal to set its value source:
| Terminal | Description |
|---|---|
UseValue(value) |
Same constant value for every entity |
UseFactory(Func<TProperty>) |
Invoked once per entity |
UseFactory(Func<int, TProperty>) |
Index-aware factory that receives the entity's zero-based position |
UseFrom(params TProperty[]) |
Random selection from a fixed pool |
UseFrom(IEnumerable<TProperty>) |
Random selection from a sequence |
After a terminal, you are back on SeedBuilder<T> and can continue chaining.
Modifiers
Add modifiers before the terminal to control behaviour:
builder.RuleFor(u => u.Email)
.Unique() // enforce uniqueness across all generated entities
.When(u => u.Role == "admin") // only apply this rule when predicate is true
.DependsOn(u => u.Role) // declare execution order explicitly
.UseFactory(i => $"admin{i}@corp.com");
Rules by default are executed in the order they are declared, using DependsOn is more of a safety net or more control over it.
Dependencies
DependsOn() causes Build() to topologically sort rules so that a rule always runs after the properties it depends on have already been set. Circular dependencies are detected and throw InvalidOperationException.
builder.RuleFor(u => u.Role).UseValue("admin");
builder.RuleFor(u => u.Permissions)
.DependsOn(u => u.Role)
.When(u => u.Role == "admin")
.UseValue(Permission.All);
Nested Objects
HasOne: a single nested instance per parent
builder.HasOne(u => u.Profile, profile =>
profile.RuleFor(p => p.Bio).UseValue("Hello!"));
HasMany: a collection per parent
builder.Count(3)
.HasMany(u => u.Purchases, purchases =>
purchases.Count(5)
.RuleFor(p => p.Id).UseFactory(Guid.NewGuid)
.RuleFor(p => p.Quantity).UseFactory(() => Random.Shared.Next(1, 10)));
// Creates 3 users, each with their own list of 5 purchases
Idempotency
When you need the same data every run, no matter the occasion, use the Idempotent helpers. Values are derived deterministically from the entity type, entity index, and an optional seed string using UUID v5 (RFC 4122).
Idempotent.Guid<User>(index: 0); // always the same GUID for User #0
Idempotent.Int<Product>(index: 1); // deterministic int for Product #1
Idempotent.Long<Order>(index: 2); // deterministic long for Order #2
Idempotent.Slug<Category>(index: 0, "cat"); // "cat-0"
The idempotent terminals are available directly on SeedRule<T, TProperty>:
builder.Count(5)
.RuleFor(u => u.Id).UseIdempotentGuid()
.RuleFor(u => u.ExternalRef).UseIdempotentSlug("user");
// Produces "user-0", "user-1", ... every single time
EntitySeeder<T>
EntitySeeder<T> is an abstract base class for reusable, injectable seeders. The generated data is cached after the first call to Data, making it safe to reference from other seeders.
public sealed class PurchaseSeeder : EntitySeeder<Purchase>
{
private readonly UserSeeder _users;
private readonly ProductSeeder _products;
public PurchaseSeeder(UserSeeder users, ProductSeeder products)
{
_users = users;
_products = products;
}
protected override void Configure(SeedBuilder<Purchase> builder)
{
builder.Count(50)
.RuleFor(p => p.Id).UseFactory(Guid.NewGuid)
.RuleFor(p => p.UserId).UseFrom(_users.Data.Select(u => u.Id))
.RuleFor(p => p.ProductId).UseFrom(_products.Data.Select(p => p.Id))
.RuleFor(p => p.Quantity).UseFactory(() => Random.Shared.Next(1, 100));
}
}
Persistence
IPersistenceLayer
Implement IPersistenceLayer to back seeding with any storage:
public interface IPersistenceLayer
{
void Persist<T>(IEnumerable<T> entities);
Task PersistAsync<T>(IEnumerable<T> entities, CancellationToken cancellationToken = default);
void Flush();
Task FlushAsync(CancellationToken cancellationToken = default);
}
Entity Framework Core
EntityFrameworkCorePersistenceLayer stages entities and commits with SaveChanges(). Configure how to handle pre-existing records via ConflictBehavior:
| Behavior | Description |
|---|---|
ConflictBehavior.Insert |
Always insert (default). Throws on key conflict |
ConflictBehavior.Skip |
Skip entities whose primary key already exists |
ConflictBehavior.Update |
Update existing, insert new |
services.AddScoped<IPersistenceLayer>(sp =>
new EntityFrameworkCorePersistenceLayer(
sp.GetRequiredService<AppDbContext>(),
ConflictBehavior.Skip));
ASP.NET Core Integration
SeederRunner
SeederRunner orchestrates multiple seeders in registration order. All entities are staged first; then a single FlushAsync() commits everything atomically.
// Manual usage (without DI)
var runner = new SeederRunner(persistenceLayer, new IEntitySeeder[] { userSeeder, productSeeder });
await runner.RunAsync();
When using AddFluentSeeding, SeederRunner is registered automatically and resolved by RunSeedersAsync().
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FluentSeeding (>= 0.2.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.