Repository navigation
Releases: hlib-zinchenko/Hlibz.EntityFrameworkCore.ModelRules
Release list
v1.0.0: First stable release
The first stable release of Hlibz.EntityFrameworkCore.ModelRules: declarative rules that check a finished EF Core model against your team's conventions. A model that breaks one fails to build, at startup or in a unit test, and the error lists every violation at once.
dotnet add package Hlibz.EntityFrameworkCore.ModelRulesRules
| ID | Rule | Checks |
|---|---|---|
| MR001 | NoShadowProperties() |
No shadow properties or foreign keys invented by convention |
| MR002 | NamesFollow(style, scope) |
Tables, columns, keys, FKs, indexes, check constraints, sequences and functions follow snake_case (or your style) |
| MR003 | DecimalsHavePrecision() |
Every decimal column has a precision |
| MR004 | StringsHaveMaxLength() |
Every string column has a max length |
| MR005 | NullabilityMatchesClr() |
C# nullability matches column nullability |
| MR006 | EnumsStoredAsStrings() |
Enums aren't stored as numbers, including in collections and JSON |
| MR007 | SingleSchema(schema?) |
One schema per DbContext |
| MR008 | NoCascadeDeleteAcrossAggregates(isRoot) |
Aggregate roots never cascade-delete each other |
| MR009 | MaxIdentifierLength(max, scope) |
No identifier exceeds your database's limit |
| MR010 | EntitiesHaveQueryFilter<TMarker>() |
Soft-deletable / tenant-owned entities have a query filter |
| MR011 | NoClientSideDeleteBehaviors() |
No ClientSetNull / ClientCascade |
| MR012 | NoNavigationsAcrossAggregates(isRoot) |
Aggregate roots refer to each other by key only |
| MR013 | AggregateRootsHaveConcurrencyToken(isRoot) |
Every aggregate root has a concurrency token |
| MR014 | NoRedundantIndexes() |
No index is a leading prefix of another index or key |
Highlights
- Two ways to run:
UseModelRules(...)inConfigureConventionschecks the model as EF builds it;ModelRuleVerifier.Verify(context)/VerifyAll(...)checks one or several contexts from a test. - Exclusions by entity, property, name or predicate, per rule or globally.
- Custom rules: derive from
ModelRuleand work againstIReadOnlyModel. ConfigureClientSetNullAs(DeleteBehavior.SetNull)fixes every unconfigured optional relationship at once.
Compatibility
- EF Core 8, 9 and 10, with one build per major (
net8.0,net9.0,net10.0). - Provider-neutral. The same violations are verified on PostgreSQL (Npgsql), SQL Server, SQLite and MySQL (Oracle and Pomelo). Integration tests run against real PostgreSQL, SQL Server and MySQL.
Upgrading from the previews
No code changes since 1.0.0-preview.4. If you're upgrading from 1.0.0-preview.1, the static entry point was renamed in preview.2: replace ModelRules.Verify(...) / ModelRules.Validate(...) with ModelRuleVerifier.Verify(...) / ModelRuleVerifier.Validate(...).
Versioning
Semantic versioning, and rule IDs are permanent. A minor or patch release may make a rule catch cases it missed before. Only a major release changes what a rule requires. New rules are always opt-in. See Versioning.
v1.0.0-preview.4
Breaking changes
NamesFollow(...)andMaxIdentifierLength(...)throwArgumentOutOfRangeExceptionwhen
scopeisNamingScope.None, which used to register a rule that checked nothing.SingleSchema(" ")throwsArgumentException; a blank schema used to be accepted.
Rules report more
Per the versioning policy, a rule may now find violations it missed before:
- MR002, MR007, MR009 and MR014 check every table an entity type is split into with
SplitToTable: its name, schema, columns, keys, indexes and the foreign key linking it to the
main table. - MR006 checks collections of enums (
List<Status>,Status[]), which EF Core stores as
numbers even withProperties<Enum>().HaveConversion<string>(). Convert their elements with
PrimitiveCollection(x => x.Statuses).ElementType().HasConversion<string>(). - MR006 checks enums inside JSON documents (owned and complex types mapped with
ToJson()),
which EF Core writes as numbers unless converted. - Calling
UseModelRulesmore than once reports every rule set's violations at once, instead of
stopping at the first rule set that fails.
Fixed
- MR003 and MR004 no longer report a foreign key column whose principal key sets the precision,
max length or column type: EF Core gives the foreign key column the same facets. - MR004 no longer reports columns whose value converter implies a length, such as a
Guidor
intconverted to a string (36 and 64 characters). - MR006 no longer reports enums the provider maps to a database enum type, such as PostgreSQL
enums mapped with Npgsql'sMapEnum. The exception is a single enum inside a JSON owned type,
which Npgsql writes as a number, so it is still reported. - MR008 no longer reports the cascading foreign keys EF Core adds between one entity's tables:
from a TPT-derived table to its base table, and from aSplitToTabletable to the main one.
New
- Exclusions reach owned types.
Entity<Order>()covers the typesOrderowns,
Property<Order>(x => x.ShippingAddress.Street)matches a property of an owned type, and
Property<Order>(x => x.Lines)matches an owned type's own violations, such as its table name. - MR007 explains a table with no schema: it suggests
modelBuilder.HasDefaultSchema(...), which
SingleSchema("dbo")needs on SQL Server.
Docs
- Every rule's message is shown in the README, along with how the rules work with compiled
models and how to verify contexts resolved from DI. - The Compatibility section explains the minimum EF Core patch each build requires, and what to
do about anNU1605restore error.
v1.0.0-preview.3
New
ModelRuleVerifier.VerifyAll(params Func<DbContext>[])checks several contexts in one test.
Each factory creates its context, so constructors that take services besides the options work.
Every context is checked before it throws, and the singleModelRuleViolationExceptionlists
the violations grouped by context.ModelRuleVerifier.VerifyAll(params DbContextOptions[])does the same for contexts whose
constructor takes only their options.
v1.0.0-preview.2
Breaking changes
ModelRulesis renamed toModelRuleVerifier. The old name clashed with any consumer
namespace that has aModelRulessegment (e.g.Acme.ModelRules.Tests), which caused CS0234.
ReplaceModelRules.Verify(...)/ModelRules.Validate(...)with
ModelRuleVerifier.Verify(...)/ModelRuleVerifier.Validate(...).
New
ModelRuleVerifier.Verify(DbContextOptions),Verify(DbContextOptions, rules)and
Validate(DbContextOptions, rules)create the context from its options, so a context normally
built by DI can be verified in a test without building it yourself. The README shows a theory
over several contexts.configurationBuilder.ConfigureClientSetNullAs(DeleteBehavior.SetNull)replaces EF Core's
ClientSetNulldefault on every optional relationship you haven't configured, which fixes MR011
in one line. ExplicitOnDelete(...)configuration is left alone. It runs before the rules
whatever order you call them in. UseRestricton SQL Server whereSetNullwould create
multiple cascade paths.
Docs
- The Install section explains installing a prerelease, including with central package
management.