Diagnostics Guide
Diagnostics are part of the product experience, not just compiler noise.
For a novice user, the most useful way to read a Pragmatic diagnostic is:
- Understand what the generator was trying to protect you from.
- Decide whether the message is blocking correctness or only warning about cost.
- Apply the smallest fix that restores a clear model.
This page focuses on the persistence diagnostics that users are most likely to hit while adopting the stack for the first time.
How to read a diagnostic
Section titled “How to read a diagnostic”| Severity | What it means | What to do |
|---|---|---|
| Error | The generator cannot produce a safe or coherent model | Fix it before trusting generated output |
| Warning | Generation can continue, but the shape may be inefficient or ambiguous | Review the design and either simplify it or accept the tradeoff consciously |
| Info | The generator is telling you about a behavior choice | Read it once so the generated result does not surprise you later |
Fast triage checklist
Section titled “Fast triage checklist”Before looking at the individual code:
- Confirm the type is
partialwhen using Pragmatic attributes. - Confirm navigation properties are declared through
[Relation.*], not manually mixed with inferred relationships. - Confirm the DTO really matches the entity graph you expect to load.
- Confirm warnings about depth or many navigations are acceptable for the query volume you expect in production.
Common diagnostics
Section titled “Common diagnostics”| Code | Severity | Meaning | First fix to try |
|---|---|---|---|
PRAG0600 | Error | An attributed entity type is not partial | Add partial to the class declaration |
PRAG0601 | Error | Entity type referenced in attribute was not found | Verify the type exists and is accessible |
PRAG0602 | Error | A [Database] DbContext type is not partial | Add partial to the DbContext declaration |
PRAG0603 | Error | [ReadAccess] entity belongs to a different database — cross-database join is not possible | Move the entity to the same database or use an application-level join |
PRAG0604 | Error | [ReadAccess] owner module is not included — no EntityMetadata available | Add [Include<TModule>] for the owner module |
PRAG0610 | Error | A collection navigation exists without a matching [Relation.*] attribute | Replace the manual navigation with [Relation.OneToMany<T>] or [Relation.ManyToMany<T>] |
PRAG0611 | Error | A reference navigation exists without a matching [Relation.*] attribute | Replace the manual navigation with [Relation.ManyToOne<T>] or [Relation.OneToOne<T>] |
PRAG0612 | Error | Multiple relations target the same entity type and are ambiguous | Add .WithNavigation("...") to make each relation distinct |
PRAG0613 | Error | The inverse navigation name cannot be found on the other side | Fix the inverse name to match the generated navigation |
PRAG0614 | Error | The two sides of a relation do not agree on their shape | Align One-to-Many, Many-to-One, or One-to-One properly |
PRAG0615 | Error | Two generated navigations would end up with the same name | Rename one relation with .WithNavigation("...") |
PRAG0616 | Warning | A same-boundary parent/child relation was modeled from the child side | Prefer declaring the parent-side One-to-Many for readability |
PRAG0617 | Info | A relation crosses boundaries, so only the foreign key is generated | Expect FK-only behavior, not in-memory navigation |
PRAG0650 | Warning | A navigation has no obvious foreign-key property | Add an FK property like CustomerId or configure manually |
PRAG0651 | Warning | A property type may need an EF Core value converter | Add a converter or map to a provider-friendly type |
PRAG0705 | Warning | A required navigation points to another soft-deletable entity | Consider making the navigation optional |
PRAG0710 | Warning | A DTO looks like it references a navigation, but the name does not match the entity graph | Align the DTO property name or load the data explicitly |
PRAG0711 | Warning | [LoadWith] asks for include depth greater than 3 | Reduce depth or move to projection |
PRAG0716 | Warning | A DTO loads many navigation paths | Consider projection or split queries |
PRAG0600: type must be partial
Section titled “PRAG0600: type must be partial”This is the most common onboarding error.
Why it exists:
- Source generators add members in separate
.g.csfiles. - Without
partial, C# cannot merge your declaration with the generated one.
Broken:
[Entity<Guid>]public class Order{ public decimal Total { get; private set; }}Fixed:
[Entity<Guid>]public partial class Order{ public decimal Total { get; private set; }}The same rule applies to generated DbContexts, which is what PRAG0602 enforces.
PRAG0610 and PRAG0611: relation attributes are the source of truth
Section titled “PRAG0610 and PRAG0611: relation attributes are the source of truth”These errors exist because Pragmatic wants one unambiguous relationship model.
If you write a manual entity navigation but do not declare the relation with [Relation.*], the generator cannot know whether that navigation is:
- intentional and owned by the persistence model
- a leftover property from an older manual mapping
- incomplete and missing its inverse side
For novice users, the safest rule is:
- use
[Relation.*]to describe persistence relationships - let the generator own the navigation shape
- avoid mixing hand-authored navigation properties with generated ones unless you know exactly why
See Entity System and Repository for the broader model.
PRAG0650: navigation without foreign key
Section titled “PRAG0650: navigation without foreign key”Why the warning exists:
- EF Core can infer some relationships, but inference becomes fragile once the model grows.
- A named FK property like
CustomerIdmakes queries, migrations, and debugging much clearer.
Typical fix:
[Entity<Guid>][Relation.ManyToOne<Customer>]public partial class Order{ public Guid CustomerId { get; private set; }}If you intentionally want a custom FK name or manual configuration, document it in the DbContext configuration so the next person does not have to rediscover the rule.
PRAG0651: property may need value converter
Section titled “PRAG0651: property may need value converter”This warning is not saying the property is invalid. It is saying:
“EF Core may not know how to persist this property type cleanly on every provider.”
Common examples:
- custom value objects
- opaque identifier wrappers
- JSON-heavy types
- strongly typed IDs not mapped by a converter
Mitigations:
- Use a provider-native primitive if the type has no strong domain value.
- Add a
ValueConverterin entity configuration. - Keep the complex type in the domain model, but persist a simpler representation.
PRAG0705: required navigation to a soft-deletable parent
Section titled “PRAG0705: required navigation to a soft-deletable parent”This warning protects against a subtle data-loss illusion in queries.
Scenario:
OrderLinehas a required navigation toOrder- both entities use
[SoftDelete] - EF Core uses an
INNER JOINfor the required navigation - when the parent row is soft-deleted, the child may disappear from query results even if the child itself is not deleted
Why novices get trapped here:
- the model looks correct in C#
- the bug appears only after some real data is soft-deleted
- the missing children look like “query randomness”
Mitigations:
- Make the navigation optional if the child must still be queryable independently.
- Query the child root directly instead of relying on a required join through the parent.
- Accept the behavior only if “hidden when parent is soft-deleted” is the intended business rule.
If your use case is “restore deleted aggregates”, also review Query Filters.
PRAG0710: DTO references navigation without Include
Section titled “PRAG0710: DTO references navigation without Include”This warning exists to catch a mismatch between:
- what your DTO shape implies
- what the generator can actually infer from the entity graph
Typical example:
[LoadWith<Order>(MaxDepth = 2)]public sealed class OrderDto{ public CustomerSummaryDto Buyer { get; init; } = default!;}If the entity navigation is named Customer rather than Buyer, the generator cannot assume that Buyer should be auto-included.
Mitigations:
- Rename the DTO property to match the entity navigation when the intent is one-to-one mapping.
- Use projection and map the DTO explicitly.
- Keep the custom DTO property name, but stop expecting
[LoadWith]to infer it automatically.
For novice teams, the simplest rule is:
- if you rely on auto-loading, keep DTO navigation names aligned with entity navigation names
See Data Sources & Loading for the loading profile model.
PRAG0711: include depth greater than 3
Section titled “PRAG0711: include depth greater than 3”This is one of the most important DX warnings because the code often “works” while still being a bad default.
Why it exists:
- each extra include level increases query complexity
- deeper joins usually pull many more columns than the caller needs
- SQL becomes harder to tune and easier to break with provider differences
Depth 4 is not forbidden. The warning means:
“You are entering a zone where projection or split loading is usually easier to reason about.”
Mitigations:
- Reduce
MaxDepthto the smallest value that satisfies the screen or endpoint. - Move to
QueryStrategy.Projectionand select only the fields you need. - Split one deep graph into two targeted queries.
Good instinct for novice users:
- if a UI needs a summary, do not load the full object graph just because it is convenient
PRAG0716: DTO with many navigation paths
Section titled “PRAG0716: DTO with many navigation paths”This warning is about breadth rather than depth.
Even when each path is shallow, many independent paths can still create:
- large SQL
- duplicate data due to joins
- confusing performance regressions when the DTO evolves
Mitigations:
- Prefer projection for dashboard-style or report-style DTOs.
- Split “header data” and “detail collections” into separate queries when they have different lifecycles.
- Keep
[LoadWith]for entity-shaped views, not for giant read models.
When a warning is acceptable
Section titled “When a warning is acceptable”Not every warning must be removed.
A warning is usually acceptable when:
- the query runs rarely
- the dataset is small and bounded
- the behavior is understood and covered by tests
- the team has documented why the tradeoff is intentional
What you want to avoid is silent acceptance. A novice-friendly codebase makes tradeoffs explicit.