Skip to content

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:

  1. Understand what the generator was trying to protect you from.
  2. Decide whether the message is blocking correctness or only warning about cost.
  3. 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.

SeverityWhat it meansWhat to do
ErrorThe generator cannot produce a safe or coherent modelFix it before trusting generated output
WarningGeneration can continue, but the shape may be inefficient or ambiguousReview the design and either simplify it or accept the tradeoff consciously
InfoThe generator is telling you about a behavior choiceRead it once so the generated result does not surprise you later

Before looking at the individual code:

  1. Confirm the type is partial when using Pragmatic attributes.
  2. Confirm navigation properties are declared through [Relation.*], not manually mixed with inferred relationships.
  3. Confirm the DTO really matches the entity graph you expect to load.
  4. Confirm warnings about depth or many navigations are acceptable for the query volume you expect in production.
CodeSeverityMeaningFirst fix to try
PRAG0600ErrorAn attributed entity type is not partialAdd partial to the class declaration
PRAG0601ErrorEntity type referenced in attribute was not foundVerify the type exists and is accessible
PRAG0602ErrorA [Database] DbContext type is not partialAdd partial to the DbContext declaration
PRAG0603Error[ReadAccess] entity belongs to a different database — cross-database join is not possibleMove the entity to the same database or use an application-level join
PRAG0604Error[ReadAccess] owner module is not included — no EntityMetadata availableAdd [Include<TModule>] for the owner module
PRAG0610ErrorA collection navigation exists without a matching [Relation.*] attributeReplace the manual navigation with [Relation.OneToMany<T>] or [Relation.ManyToMany<T>]
PRAG0611ErrorA reference navigation exists without a matching [Relation.*] attributeReplace the manual navigation with [Relation.ManyToOne<T>] or [Relation.OneToOne<T>]
PRAG0612ErrorMultiple relations target the same entity type and are ambiguousAdd .WithNavigation("...") to make each relation distinct
PRAG0613ErrorThe inverse navigation name cannot be found on the other sideFix the inverse name to match the generated navigation
PRAG0614ErrorThe two sides of a relation do not agree on their shapeAlign One-to-Many, Many-to-One, or One-to-One properly
PRAG0615ErrorTwo generated navigations would end up with the same nameRename one relation with .WithNavigation("...")
PRAG0616WarningA same-boundary parent/child relation was modeled from the child sidePrefer declaring the parent-side One-to-Many for readability
PRAG0617InfoA relation crosses boundaries, so only the foreign key is generatedExpect FK-only behavior, not in-memory navigation
PRAG0650WarningA navigation has no obvious foreign-key propertyAdd an FK property like CustomerId or configure manually
PRAG0651WarningA property type may need an EF Core value converterAdd a converter or map to a provider-friendly type
PRAG0705WarningA required navigation points to another soft-deletable entityConsider making the navigation optional
PRAG0710WarningA DTO looks like it references a navigation, but the name does not match the entity graphAlign the DTO property name or load the data explicitly
PRAG0711Warning[LoadWith] asks for include depth greater than 3Reduce depth or move to projection
PRAG0716WarningA DTO loads many navigation pathsConsider projection or split queries

This is the most common onboarding error.

Why it exists:

  • Source generators add members in separate .g.cs files.
  • 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.

Why the warning exists:

  • EF Core can infer some relationships, but inference becomes fragile once the model grows.
  • A named FK property like CustomerId makes 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:

  1. Use a provider-native primitive if the type has no strong domain value.
  2. Add a ValueConverter in entity configuration.
  3. 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:

  • OrderLine has a required navigation to Order
  • both entities use [SoftDelete]
  • EF Core uses an INNER JOIN for 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:

  1. Make the navigation optional if the child must still be queryable independently.
  2. Query the child root directly instead of relying on a required join through the parent.
  3. 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:

  1. Rename the DTO property to match the entity navigation when the intent is one-to-one mapping.
  2. Use projection and map the DTO explicitly.
  3. 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.

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:

  1. Reduce MaxDepth to the smallest value that satisfies the screen or endpoint.
  2. Move to QueryStrategy.Projection and select only the fields you need.
  3. 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

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:

  1. Prefer projection for dashboard-style or report-style DTOs.
  2. Split “header data” and “detail collections” into separate queries when they have different lifecycles.
  3. Keep [LoadWith] for entity-shaped views, not for giant read models.

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.