Analyzer rule
LC052: Model data uses a value that changes on every run
LC052 flags HasData and HasDefaultValue with DateTime.Now or Guid.NewGuid(), which change the EF Core model every run and make EF Core 9 Migrate() throw.
- Default severity
- Warning
- Code fix
- Manual only
- Category
- Reliability
- Domain
- Schema & Modeling
Change the severity in .editorconfig:
[*.cs]
dotnet_diagnostic.LC052.severity = error
LC052: Model data uses a value that changes on every run
In Plain Terms
You write “today’s date” on a form that is supposed to be a master copy. Every time someone checks the copy against the original, they differ, so everyone thinks the form was changed.
Goal
Detect DateTime.Now, DateTime.UtcNow, DateTime.Today, DateTimeOffset.Now, DateTimeOffset.UtcNow, Guid.NewGuid(), and Guid.CreateVersion7() inside HasData(...) seed data and HasDefaultValue(...).
The Problem
Seed data and column defaults are part of the EF Core model. The migration snapshot stores their values. When a value comes from the clock or a new GUID, every run builds a different model than the last migration recorded:
// Violation: a new timestamp and key on every run.
modelBuilder.Entity<Blog>().HasData(new Blog { Id = 1, Key = Guid.NewGuid(), CreatedAt = DateTime.UtcNow });
// Violation: the column default is frozen at whenever the model was built.
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).HasDefaultValue(DateTime.UtcNow);
dotnet ef migrations addproduces a new migration that updates the seed rows each time, even with no real change.- Since EF Core 9,
Migrate()anddotnet ef database updatethrowPendingModelChangesWarningbecause the model never matches the snapshot. The EF Core 9 breaking changes name these calls as the usual cause. - A
HasDefaultValue(DateTime.UtcNow)default is not “now” at insert time. It is the time the migration was generated.
The Fix
Seed fixed literals, and let the database supply “now” for column defaults:
modelBuilder.Entity<Blog>().HasData(new Blog
{
Id = 1,
Key = new Guid("8c1d2f6e-7a55-4c43-9a2a-1f8b3e6d9c01"),
CreatedAt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc)
});
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); // SQL Server
For data that really must be generated at runtime, use UseSeeding / UseAsyncSeeding (EF Core 9+) instead of HasData.
Analyzer Logic
ID: LC052
Category: Reliability
Severity: Warning
- Find calls to EF Core’s
HasData(entity and owned-type builders) andHasDefaultValue. - Look through every argument, including object initializers, anonymous objects, and arrays.
- Report each clock or new-GUID value found, directly or through up to three locals or
static readonlyfields whose only value contains one.
When it stays quiet (non-goals)
- Fixed values:
new DateTime(...),new Guid("..."), literals, and constants. HasDefaultValueSql(...)andHasComputedColumnSql(...), which run in the database.- Locals that are written again after their declaration, instance fields, and mutable static fields. The rule cannot prove what they hold when the model is built.
- Values returned from helper methods.
HasDatamethods that are not EF Core’s.
Why There Is No Code Fix
No automatic fix. A seed value needs a fixed literal that only the author can choose, and a database default needs provider-specific SQL (GETUTCDATE() on SQL Server, now() on PostgreSQL, CURRENT_TIMESTAMP on SQLite).
Test Cases
Violations
modelBuilder.Entity<Blog>().HasData(new Blog { Id = 1, CreatedAt = DateTime.Now });
modelBuilder.Entity<Blog>().HasData(new { Id = 1, Key = Guid.NewGuid() });
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).HasDefaultValue(DateTime.UtcNow);
Valid
modelBuilder.Entity<Blog>().HasData(new Blog { Id = 1, CreatedAt = new DateTime(2024, 1, 1) });
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");