EFCore.AutoSeed.Cli
1.0.0
dotnet tool install --global EFCore.AutoSeed.Cli --version 1.0.0
dotnet new tool-manifest
dotnet tool install --local EFCore.AutoSeed.Cli --version 1.0.0
#tool dotnet:?package=EFCore.AutoSeed.Cli&version=1.0.0
nuke :add-package EFCore.AutoSeed.Cli --version 1.0.0
EFCore.AutoSeed
Seed your database from your EF Core model. One line, full referential integrity, realistic distribution. No factories, no CSV files, no manual ordering.
[GIF: empty database to 20.000 referentially valid rows in 8 seconds]
The problem
Every EF Core seeder available today asks you to describe the model again, in CSV files, in factory classes, in attributes with manual priority numbers, in JSON config. You already declared all of it in your DbContext. Then a migration changes it and your seeder breaks.
And the data you end up with is uniform: every customer with three orders. In production one customer has 500.000. The query planner picks a different plan for each shape, so your performance test passes while lying to you.
Usage
await db.AutoSeedAsync(seed: 42, scale: 1_000);
That is the whole API for the common case. AutoSeed reads your model, works out the insertion order, resolves cycles, infers what each property means, and writes referentially valid rows.
Same seed, same data. Always.
dotnet add package EFCore.AutoSeed
One package. Nothing to configure before the first run.
What makes it different
It reads the EF Core model, not the database schema
DDL introspection sees tables and columns. The EF model also carries navigation properties, composite keys, self-references, owned types and inheritance.
AutoSeed generates data your database accepts: composite primary keys and composite foreign keys need no extra configuration, owned types (OwnsOne, including nested owned types) get their own columns populated instead of being left null, and TPH, TPT and TPC inheritance all just work, discriminator column included.
It also works before the database exists.
It works out the order itself
Foreign keys form a graph. AutoSeed topologically sorts it, detects cycles, and resolves the nullable ones with a second pass.
No priority numbers. No ordering by hand.
When a cycle is genuinely unsatisfiable (a required foreign key with no nullable link), AutoSeed names the entities involved and stops, instead of letting your database throw a constraint violation.
It generates realistic distributions, not just realistic values
Most generators aim for "the name looks like a name". AutoSeed also aims for the right shape:
Customer: 1.000 rows
Order: 3.847 rows long tail: mean 3.8, max 512, one customer holds 13%
OrderItem: 19.203 rows
Most customers have one order. A few have hundreds, and the mean and the outliers stay the same across runs with the same seed.
Weekday/business-hour clustering, null rate, the query filter bias and locale are all configurable through an optional AutoSeedOptions:
await db.AutoSeedAsync(seed: 42, scale: 1_000, options: new AutoSeedOptions(NullRate: 0.2, Locale: "pt_BR"));
It explains itself before it writes anything
var plan = await db.AutoSeedExplainAsync(seed: 42, scale: 1_000);
Console.WriteLine(plan.ToReport());
Prints the insertion order, the row count per entity type, which cycles got deferred to a second pass, and which entity types were skipped and why. Nothing is written to the database.
The same thing is available from the command line:
dotnet tool install -g EFCore.AutoSeed.Cli
autoseed explain --context MyApp.AppDbContext --assembly bin/Release/net10.0/publish/MyApp.dll
Coverage mode
The opposite of bulk. The smallest dataset that exercises everything:
await db.AutoSeedCoverageAsync();
Every enum value. Every nullable property in both states. Every relationship at zero, one and many. Every string at empty, one character and maximum length. No seed or scale to configure: the row counts are structural, not scaled.
Usually under 50 rows. The dataset unit tests want and nobody assembles by hand without forgetting half of it.
Fast mode
await db.AutoSeedFastAsync(seed: 42, scale: 1_000);
Same generated data as AutoSeedAsync for the same seed, an equivalence test proves it, but written with SqlBulkCopy (SQL Server) or a binary COPY (PostgreSQL) instead of SaveChanges. See Benchmarks below for actual numbers.
Only entity types simple enough to make bypassing EF Core safe are supported: an inherited entity type or a foreign-key cycle fails loudly and points back at AutoSeedAsync, instead of risking silently wrong data. Owned types and int/long/Guid identity primary keys are supported.
Production shape
ShapeCapture shape = await productionReplicaDb.CaptureShapeAsync();
await db.AutoSeedFromShapeAsync(seed: 42, shape, scale: 1_000);
CaptureShapeAsync reads a row count for every table straight from the database engine's own maintained statistics (sys.dm_db_partition_stats on SQL Server, pg_class.reltuples on PostgreSQL): never a query against an actual row. AutoSeedFromShapeAsync then seeds a table present in the shape at a scale proportional to its captured row count relative to the largest one, so a local database's relative table sizes resemble where the shape came from, instead of every independent table getting the same flat scale.
The same thing from the command line:
autoseed capture --context MyApp.AppDbContext --assembly bin/Release/net10.0/publish/MyApp.dll --output shape.json
autoseed apply --context MyApp.AppDbContext --assembly bin/Release/net10.0/publish/MyApp.dll --shape shape.json
Supported frameworks
| Target | Status |
|---|---|
net10.0 |
supported |
net8.0 |
supported |
netstandard2.0 |
not yet, open an issue if you need it |
net45 and older |
not possible: EF Core does not exist there |
EF Core itself requires .NET 8 or later from version 8 onwards, so those are the targets that matter. .NET Framework 2.0 through 4.5 is Entity Framework 6 territory, which has a different model API entirely.
Supported EF Core versions: the two most recent majors.
What it does not do
- It does not anonymise production data. Not a masking tool. Production-shape capture (
autoseed capture) reads row counts only, from the database's own statistics, never a data row. - It is not a service. No cloud, no account, no server to keep running.
- EF Core only. Not Dapper, not raw ADO.NET, not other ORMs.
- SQL Server and PostgreSQL only.
- It refuses models it cannot satisfy, loudly and by name.
Validated
Property-based tests assert that for any model and any seed, every foreign key points at an existing row and no constraint is violated. They run on every commit.
Also tested against 4 real, public schemas: Northwind, Chinook, Contoso University and a lite AdventureWorks OLTP subset. Composite keys, self-references, shared-primary-key one-to-ones and many-to-many join tables included.
Benchmarks
A single local run, containerized SQL Server, three-table schema (Customer → Order → OrderItem), AutoSeedAsync (fidelity mode) against AutoSeedFastAsync (bulk insert):
| Root rows | Fidelity mode | Fast mode | Speedup |
|---|---|---|---|
| 1,000 | 6.0 s | 0.6 s | ~10x |
| 5,000 | 25.9 s | 3.0 s | ~9x |
Fast mode also allocates about 60% less managed memory at both scales. These are directional numbers from one machine, one schema, three iterations each, not a rigorous multi-environment study: the point is the order of magnitude, not the second decimal place. Reproduce them yourself, or run your own shape, with dotnet run -c Release in tests/AutoSeed.Benchmarks.
Compared to
| Package | Approach | Last release |
|---|---|---|
| Bogus | generates values for objects. AutoSeed is built on it and does not replace it | active |
| EFCore.Seeder | you write CSV files, embed them as resources, implement IEquatable |
2020 |
| EntityFrameworkCore.Seeder | you write a factory class with rules per entity | 1.0.1 |
| Ef.Seeder | you set a numeric priority per entity to get the order right | 1.0.2 |
| BulkDataSeeder.EfCore | CSV files plus JSON configuration | 1.0.4 |
| AutoFixture | fills objects by reflection, breaks on foreign keys | active |
EF Core HasData |
you type each row | built in |
They are all seeders. They are all manual. That is the gap this fills.
Outside .NET, SynthDB and Seedfast take a similar approach for PostgreSQL, reading DDL rather than an ORM model.
Roadmap
Shipped: the model reader, cycle resolution, ~20 property inference rules including a Discount/AmountDue correlation alongside Total/Quantity, long-tail cardinality for related rows, composite keys, owned types (fidelity and fast mode), TPH/TPT/TPC inheritance, global query filter bias, weekday/business-hour temporal clustering, a null rate for nullable columns, optional dirty-data noise (casing, whitespace, diacritics) for free-text values, all four of those configurable through AutoSeedOptions, bulk insert (SqlBulkCopy, PostgreSQL binary COPY) with int/long/Guid identity keys and an equivalence test against AutoSeedAsync, production row-count capture and apply (autoseed capture/autoseed apply, CaptureShapeAsync/AutoSeedFromShapeAsync), AutoSeedAsync/AutoSeedExplainAsync/AutoSeedCoverageAsync/AutoSeedFastAsync, and autoseed explain.
Not shipped yet:
- Bulk insert coverage:
AutoSeedFastAsyncstill rejects TPH/TPT/TPC inheritance and foreign-key cycles, falling back toAutoSeedAsyncfor those. - Per-column shape statistics: a captured shape holds row counts only today; null fraction, distinct count and value histograms are not captured, so
AutoSeedFromShapeAsyncshapes relative table sizes, not value distributions.AutoSeedFromShapeAsyncis also fidelity-mode only, no fast-mode equivalent yet.
Details and rationale in ARCHITECTURE.md.
Documentation
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 29 | 8/4/2026 |