I've been using C# Bogus (aka Faker) to generate data with EF Core, and it's been absolutely invaluable for development.
I have this set up in my context class, in the OnModelCreating
method. This seems necessary to get the data into the database, since it uses the entity HasData
method:
long accountId = 1;
var accountData = new Faker<AccountModel>()
.RuleFor(m => m.Id, f => accountId++)
.RuleFor(m => m.Name, f => f.Company.CompanyName())
.Generate(6);
builder.Entity<AccountModel>().HasData(accountData);
However, the entire data generation script runs on each request made to the API.
Where can I put the Bogus scripts that will allow it to seed the database with dotnet ef migrations add init
, but without running on every request?
Full reproduceable examples
If you're not familiar, here are some examples of the setup, including Bogus' own repo,
Note that some examples show limiting this with a conditional for ASPNETCORE_ENVIRONMENT, which is handy if you're in production, but still makes this rough with a local setup spinning up large, complex data sets.
Update: I've been living with this every time I use Bogus in a project, as it gets removed after the early dev/test stages anyhow. However, it's annoying in larger projects to be creating possibly tens of thousands of rows of data in memory. It's become noticeable in response times with our current project.
There must be some sort of conditional to wrap this in, or alternative place for it to exist that doesn't impact each and every network request. Our current solution is "comment it out between database migrations"
As Guru Stron has pointed out, OnModelCreating shouldn't be running on every request, which makes this problem a symptom and not the disease.
The only references to the base context are in the Program.cs file:
builder.Services.AddDbContext<DbContext>(
options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("DbConnectionString"),
providerOptions =>
{
providerOptions.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds);
providerOptions.EnableRetryOnFailure();
providerOptions.UseNetTopologySuite();
}
);
options.EnableSensitiveDataLogging();
});
And in our services, like so:
public AccountService(
DbContext context,
...
) {
_context = context;
}
So one or both of these must be implemented incorrectly.
Not sure what is meant by "each request" and I was not able to reproduce it. Though there is at least one problem with setup you have shown in the example - it does not use fixed seeds so every time you will add migration you will have new data generated potentially leading to a lot of "garbage" in those migrations. I recommend to use the fixed seed (if you are not using it already). Either by setting up a global one:
Randomizer.Seed = new Random(42);
Or on per instance base:
var accountData = new Faker<AccountModel>()
.UseSeed(42)
.RuleFor(m => m.Id, f => accountId++)
.RuleFor(m => m.Name, f => f.Company.CompanyName())
.Generate(6);
As for the issue. Again - I was not able to reproduce it (OnModelCreating
is called only a single time per app instance lifetime), but if you have some code you want to be invoked only during migrations you can leverage the EF.IsDesignTime
property (available since 7th version):
if (EF.IsDesignTime)
{
long accountId = 1;
var accountData = new Faker<AccountModel>()
.UseSeed(42)
.RuleFor(m => m.Id, f => accountId++)
.RuleFor(m => m.Name, f => f.Company.CompanyName())
.Generate(6);
builder.Entity<AccountModel>().HasData(accountData);
}