Implementation details for EF Core model building. Use when changing ConventionSet, ModelBuilder, IConvention implementations, ModelRuntimeInitializer, RuntimeModel, or related classes.
65
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Covers model construction (conventions, fluent API, metadata hierarchy) and model initialization (runtime annotation propagation, compiled model filtering).
ConventionSet (src/EFCore/Metadata/Conventions/ConventionSet.cs) holds List<I*Convention> for every metadata event. Key conventions in src/EFCore/Metadata/Conventions/:
DbSetFindingConvention — discovers entities from DbSet<T>PropertyDiscoveryConvention — discovers properties from CLR typesKeyDiscoveryConvention — finds PKs (Id, TypeId)RelationshipDiscoveryConvention — infers FKs from navigationsRuntimeModelConvention — creates optimized RuntimeModel from mutable modelOverride ConfigureConventions(ModelConfigurationBuilder) to add/remove conventions.
Convention fixes must preserve dispatcher behavior and ambiguity information. For inverse-navigation attributes, do not solve recursion by permanently ignoring an ambiguous navigation: removing one candidate can make another ambiguity appear unique and configure the wrong relationship. InversePropertyAttributeConvention records inverse candidates and uses AddInverseNavigation, TryRemoveIfAmbiguous, and FindAmbiguousInverse while still allowing convention processing to continue.
Test the state transitions, not only the initially ambiguous model: first verify that multiple interacting candidates configure no relationship; then genuinely remove ambiguity both by ignoring/removing one competing navigation and by removing a competing entity type, and verify that the remaining inverse relationship is configured. These cases distinguish candidate bookkeeping from a permanent-ignore workaround.
IReadOnly* → IMutable* → IConvention* → IRuntime*
Applies to: Model, EntityType, Property, Key, ForeignKey, Navigation, Index, etc. Builders follow: *Builder → IConvention*Builder.
Follow the nearest existing facet end to end rather than stopping at the public interface:
ConventionSet add/replace/remove path, and immediate plus delayed dispatcher scopes and nodes.RuntimeModelConvention, runtime constructors/builders, and CSharpRuntimeModelCodeGenerator so compiled models preserve it.CSharpSnapshotGenerator; keep the old absence equivalent to the historical default.ModelBuilder during OnModelCreating, made read-only by FinalizeModel()Model that also contains design-time-only annotations used in migrationsRuntimeModelConvention.ProcessModelFinalized(), does not contain design-time-only annotationsModelRuntimeInitializer.Initialize() (called by DbContextServices.CreateModel()):
Initialize(model, designTime, validationLogger)
├─ FinalizeModel() if mutable
├─ Set ModelDependencies, InitializeModel
└─ RuntimeModelConvention creates RuntimeModel, copies/filters annotationsWhen processing properties in conventions or validation, remember that complex types can contain their own declared properties. Use GetFlattenedProperties() to iterate all properties (including on nested non-collection complex types) or manually recurse through GetDeclaredComplexProperties() → complexProperty.ComplexType.
CoreAnnotationNames and its AllNamesRuntimeModelConvention.ProcessModelAnnotations if it's a design-time-only annotation (only used in migration operations)
CSharpRuntimeAnnotationCodeGenerator.Generate if it can be computed lazily at runtime (e.g. based on other annotations)RelationalAnnotationProvider if used in up-migrations or the relational model and IMigrationsAnnotationProvider if used in down-migrationsRelationalModel (src/EFCore.Relational/Metadata/Internal/RelationalModel.cs) is a database-centric view of the EF model, mapping entity types to physical database objects: Tables, Views, Functions, Queries, and DefaultTables. DefaultTables are pseudo-table objects only used for FromSql queries.
Created lazily by RelationalModelRuntimeInitializer, accessed via model.GetRelationalModel(). Used by migrations (MigrationsModelDiffer), update and query pipelines.
RelationalAnnotationProvider populates annotations on relational model elements. Provider subclasses (e.g., SqlServerAnnotationProvider) add provider-specific annotations. IMigrationsAnnotationProvider controls annotations used in down-migration operations.
ModelValidator (src/EFCore/Infrastructure/ModelValidator.cs) and RelationalModelValidator (src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs) run after model finalization, during ModelRuntimeInitializer.Initialize() between the pre- and post-validation InitializeModel calls.
Model-building changes can trigger spurious migrations for users who upgrade. Two causes:
MigrationsModelDiffer sees a diff. Fix: ensure absence of the annotation in an old snapshot is treated as the old default.Inspect CSharpSnapshotGenerator (what gets written) and MigrationsModelDiffer (how absence is handled). Add a snapshot round-trip test in test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs.
| Area | Location |
|---|---|
| Convention unit tests | test/EFCore.Tests/Metadata/Conventions/ |
| Metadata unit tests | test/EFCore.Tests/Metadata/Internal/ |
| Model builder API tests | test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest*.cs |
| Relationship discovery tests | test/EFCore.Specification.Tests/ModelBuilding101*.cs |
| Model validation tests | test/EFCore.Tests/Infrastructure/ModelValidatorTest*.cs |
| Compiled model tests | test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs |
InvalidOperationException during finalizationEF_TEST_REWRITE_BASELINES=1ToString() on metadata objects shows concise contents without throwing exceptionsdotnet/efcore
c0f183b
Also appears in
last in sync Jul 24, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.