Troubleshooting
Practical problem/solution guide for Pragmatic.Persistence. Each section covers a common issue, the likely causes, and the fix.
Entity Not Appearing in DbContext
Section titled “Entity Not Appearing in DbContext”Your entity compiles, has [Entity<Guid>], but the DbContext does not include a DbSet for it.
Checklist
Section titled “Checklist”-
Does the entity have
[BelongsTo<TBoundary>]? Without a boundary assignment, the SG does not know which DbContext should include the entity. Add the attribute. -
Does the host project declare a
[Database]class? The DbContext is configured in the host, not in the module. Verify a class with[Database<Provider>]and either[PersistAll]or[Persist<TBoundary>]exists. -
Does the
[Persist]attribute include your entity’s boundary? If you use[Persist<BillingBoundary>]but your entity belongs toSalesBoundary, it is excluded. Use[PersistAll]to include all boundaries, or add the correct[Persist<TBoundary>]. -
Is the
[Database]classpartial? The SG generates the DbContext into a partial class. Withoutpartial, diagnosticPRAG0602fires. -
Is the SG analyzer referenced? In your
.csproj, verify the Pragmatic.SourceGenerator is referenced withOutputItemType="Analyzer":<ProjectReference Include="..\Pragmatic.SourceGenerator\...\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" /> -
Is
Pragmatic.Persistence.EFCorereferenced? The repository and entity configuration are only generated when the EF Core runtime package is referenced. Without it, you get basic entity members but no DbContext integration.
Repository Not Resolving from DI
Section titled “Repository Not Resolving from DI”You inject IRepository<Order, Guid> but get InvalidOperationException at runtime: “No service for type has been registered.”
Checklist
Section titled “Checklist”-
Did you call the generated repository registration? The SG generates an extension method like
AddBillingRepositories()orAddMyAppRepositories(). Verify it is called inConfigureServices. -
Did you call the DbContext registration? The repository depends on the DbContext. Call
AddBillingDbContext(options => ...)before the repository registration. -
Is the entity in a boundary? Repositories are only generated for entities that have
[BelongsTo<TBoundary>]. -
Are you injecting the right type? The repository is registered as
IRepository<Order, Guid>, not asOrder.Repository. If you need the concrete type, register it explicitly or resolve viaIServiceProvider. -
Check the SG output. In Visual Studio, expand Dependencies > Analyzers > Pragmatic.SourceGenerator to see generated files. Look for
Order.Repository.g.csand_Infra.Persistence.Registration.g.cs.
If Using Composition
Section titled “If Using Composition”When you use Pragmatic.Composition with PragmaticApp.RunAsync(), repository and DbContext registration is automatic — the SG-generated host calls the registration methods. If repositories still do not resolve, verify the Composition package is referenced and PragmaticApp.RunAsync() is called.
Soft Delete Not Filtering
Section titled “Soft Delete Not Filtering”You added [SoftDelete] to an entity, but soft-deleted rows still appear in query results.
Checklist
Section titled “Checklist”-
Did you register generated query filters? Call
AddMyAppQueryFilters()orAddGeneratedQueryFilters()in your service registration. Without this, theSoftDeleteFilterclass exists but is never registered in DI. -
Is
IQueryFilterToggleaccidentally inRawmode? Check if any code in the request pipeline callsfilterToggle.UseMode(FilterMode.Raw)orfilterToggle.DisableAll(). These disable all filters including soft-delete. -
Are you using raw
DbContextinstead of the repository? The filter pipeline runs throughIQueryFilterProvider, which is called by the generated repository. If you querydbContext.Set<Order>()directly, you bypass the Pragmatic filter pipeline. EF Core global query filters (HasQueryFilter) serve as a fallback but must be configured in the entity configuration. -
Check the generated SoftDeleteFilter file. Look for
Order.SoftDeleteFilter.g.csin the SG output. Verify it exists and implementsIQueryFilter<Order>. -
Is the entity actually soft-deletable? Confirm
[SoftDelete]is on the entity class declaration, not just on a related entity.
Audit Fields Not Populating
Section titled “Audit Fields Not Populating”[Auditable] is on the entity, but CreatedAt, CreatedBy, UpdatedAt, UpdatedBy remain default values after save.
Checklist
Section titled “Checklist”-
Is
Pragmatic.Persistence.EFCorereferenced? TheAuditingInterceptorlives in the EF Core package. Without it, audit fields are generated but never populated. -
Is the interceptor registered? If using Composition, this is automatic. Without Composition, verify the DbContext options include the auditing interceptor.
-
Is
ICurrentUserregistered in DI? The*Byfields (CreatedBy,UpdatedBy) are populated fromICurrentUser. If it is not registered, these fields remainnull. The*Atfields (CreatedAt,UpdatedAt) still populate because they use the clock. -
Are you using
SaveChangesAsyncthrough the correct path? The interceptor hooks into EF Core’sSaveChangesAsync. If you bypass it (e.g., raw SQL, bulk operations without interceptor), audit fields are not set. -
For background jobs: If no HTTP request context exists,
ICurrentUsermay not be available. The*Byfields will benull, which is expected. The*Atfields still populate.
Migration Errors
Section titled “Migration Errors”dotnet ef migrations add fails or the migration produces unexpected results.
Possible Causes
Section titled “Possible Causes”Wrong migration context. Each boundary has its own migration context. Use the correct one:
dotnet ef migrations add InitBilling --context BillingMigrationDbContextIf you use the wrong context, entities from other boundaries may be included or missing.
Generated entity configuration not applied. The migration context must include the SG-generated IEntityTypeConfiguration<T>. Verify the generated OnModelCreating calls ApplyConfigurationsFromAssembly.
Cross-boundary FK without matching entity. If an entity references another entity in a different boundary, the FK column is generated but the referenced entity is not in the same DbContext. This can cause “entity type not found” warnings during migration. This is expected — the FK column is a raw Guid/int column without a foreign key constraint across boundaries.
Pending model changes. If you add an attribute (like [Auditable]) and rebuild, the SG generates new properties. Run dotnet ef migrations add to capture these changes. If you forget, the database schema diverges from the model.
Query Filter Not Applying
Section titled “Query Filter Not Applying”A custom IQueryFilter or IPermissionBasedFilter is registered but does not affect query results.
Checklist
Section titled “Checklist”-
Is the filter registered with the correct DI lifetime? Query filters should be
Scoped:services.AddScoped<IQueryFilter, TeamOrdersFilter>();If registered as
Singleton, the filter cannot access scoped services likeICurrentUser. -
Does the filter return the correct entity type?
IQueryFilter<Order>only applies toOrderqueries. Check the generic parameter matches your entity. -
Is
FilterModeoverriding your filter? InAdminmode, permission-based filters are skipped. InBackgroundmode, tenant and permission filters are skipped. InRawmode, everything is skipped. -
For
IPermissionBasedFilter: IsICurrentUseravailable? If no authenticated user exists in the current scope (background job, migration), permission-based filters are automatically skipped. This is by design — to prevent crashes in non-HTTP contexts. -
Does the user have the bypass permission?
IPermissionBasedFilterhas aBypassPermissionproperty. If the current user has that permission, the filter is skipped for them.
GridFilter / Query Returning Wrong Results
Section titled “GridFilter / Query Returning Wrong Results”The [Filter] or [Filterable] properties produce unexpected SQL or return the wrong rows.
Possible Causes
Section titled “Possible Causes”Property name does not match entity property. The SG derives the entity property from the filter property name. CustomerNameSort maps to CustomerName (removes “Sort” suffix). If the entity property is Name but your filter is CustomerName, use MapTo:
[Filter(MapTo = "Customer.Name")]public string? CustomerName { get; init; }Wrong operator for the data type. FilterOperator.Contains on a non-string property (e.g., int) produces a compile error or unexpected behavior. Use Equals for exact match on non-string types.
Required filter property is null. If a filter property is required but the caller sends null, the filter is always applied with the default value (empty string, zero), potentially returning no results.
ComplexFilter JSON not parsing. [ComplexFilter] properties expect JSON in the query string. Verify the JSON is URL-encoded and matches the [FilterDto<T>] shape.
Sort priority confusion. When multiple [Sort] properties have values, the SG applies them in Priority order (lower first). If Priority is not set, the order is declaration order.
Concurrency Conflict
Section titled “Concurrency Conflict”DbUpdateConcurrencyException is thrown when saving an entity.
Checklist
Section titled “Checklist”-
Is
[ConcurrencyAware]on the entity? This generates aRowVersionproperty used as a concurrency token. The exception is expected behavior when two concurrent writes target the same row. -
Are you loading stale data? If you load an entity, hold it for a long time (e.g., across an HTTP request-response cycle), and then save, another request may have modified it. Reload the entity before saving, or implement retry logic.
-
Handling the exception:
try{await uow.SaveChangesAsync(ct);}catch (DbUpdateConcurrencyException){// Reload entity and retry, or return ConflictError to the callerreturn new ConflictError("Order", "The order was modified by another user");} -
Bulk operations and concurrency. Bulk insert/upsert operations may bypass EF Core’s concurrency token checking. Use
UpsertOptions.ConcurrencyCheckfor bulk upserts if you need conflict detection.
Multi-Tenant Data Leaking
Section titled “Multi-Tenant Data Leaking”Data from other tenants appears in query results.
Checklist
Section titled “Checklist”-
Does the entity implement
ITenantEntity? The SG generates a tenant filter only for entities with aTenantIdproperty that implementsITenantEntity. -
Is
IMultiTenancyContextregistered and populated? The tenant filter readsTenantIdfromIMultiTenancyContext. If this service is not registered or returnsnull, the filter has nothing to compare against. -
Is
FilterModeset toBackgroundorRaw? Both modes skip tenant filtering. If a background job uses these modes, it sees all tenants’ data. -
Are you querying the DbContext directly? The Pragmatic tenant filter runs through
IQueryFilterProvider. DirectdbContext.Set<T>()queries bypass it. Ensure EF Core global query filters are configured as a safety net. -
Check the generated TenantFilter file. Look for
{Entity}.TenantFilter.g.csin the SG output.
Diagnostics Reference
Section titled “Diagnostics Reference”Persistence Diagnostics (PRAG06xx)
Section titled “Persistence Diagnostics (PRAG06xx)”| ID | Severity | Cause | Fix |
|---|---|---|---|
| PRAG0600 | Error | Entity type is not partial | Add partial to the class declaration |
| PRAG0601 | Error | Entity type referenced in attribute not found | Verify the type exists and is accessible |
| PRAG0602 | Error | [Database] DbContext type is not partial | Add partial to the DbContext declaration |
| PRAG0603 | Error | [ReadAccess] entity in different database | Move entity to same database or use app-level join |
| PRAG0604 | Error | [ReadAccess] owner module not included | Add [Include<TModule>] for the owner |
| PRAG0610 | Error | Collection navigation without [Relation.*] | Replace manual navigation with [Relation.OneToMany<T>] |
| PRAG0611 | Error | Reference navigation without [Relation.*] | Replace manual navigation with [Relation.ManyToOne<T>] |
| PRAG0612 | Error | Multiple relations to same entity are ambiguous | Add .WithNavigation("...") to disambiguate |
| PRAG0613 | Error | Inverse navigation name not found | Fix inverse name to match the generated navigation |
| PRAG0614 | Error | Relation sides disagree on shape | Align One-to-Many / Many-to-One properly |
| PRAG0615 | Error | Duplicate navigation name | Rename one relation with .WithNavigation("...") |
| PRAG0616 | Warning | Child-side relation within same boundary | Prefer declaring from parent side for readability |
| PRAG0617 | Info | Cross-boundary relation — FK only | Expect FK-only behavior, no navigation property |
| PRAG0650 | Warning | Navigation without FK property | Add FK property like CustomerId |
| PRAG0651 | Warning | Property type may need value converter | Add EF Core value converter or use provider-native type |
| PRAG0705 | Warning | Required navigation to soft-deletable entity | Consider making navigation optional |
| PRAG0710 | Warning | DTO navigation name mismatch | Align DTO property name with entity navigation |
| PRAG0711 | Warning | Include depth greater than 3 | Reduce depth or use projection |
| PRAG0716 | Warning | DTO with many navigation paths | Use projection or split queries |
Reading Diagnostics
Section titled “Reading Diagnostics”| Severity | What It Means | What to Do |
|---|---|---|
| Error | The SG cannot produce a safe model | Fix before trusting generated output |
| Warning | Generation continues but shape may be inefficient | Review and either simplify or document the tradeoff |
| Info | The SG is informing you about a behavior choice | Read once so the result does not surprise you later |
Can I use multiple boundaries for the same database?
Section titled “Can I use multiple boundaries for the same database?”Yes. Multiple boundaries can share the same connection string — they just have separate DbContexts. This gives you logical isolation (separate DbSets, separate migration history) while sharing the physical database.
How do I see the generated SQL?
Section titled “How do I see the generated SQL?”Enable EF Core logging:
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);Or in appsettings.Development.json:
{ "Logging": { "LogLevel": { "Microsoft.EntityFrameworkCore.Database.Command": "Information" } }}Why does my entity get PersistenceId instead of just Id?
Section titled “Why does my entity get PersistenceId instead of just Id?”PersistenceId is the technical identifier used for database persistence. Id is a convenience alias (Guid Id => PersistenceId). The distinction keeps the naming clear: PersistenceId is the column in the database, Id is what your code uses. Both point to the same value.
Can I override the generated EF Core configuration?
Section titled “Can I override the generated EF Core configuration?”Yes. Create a custom IEntityTypeConfiguration<T> in your project. EF Core’s ApplyConfigurationsFromAssembly will pick it up. If you need to override specific generated configuration, apply your overrides after the generated configuration runs.
How do I restore a soft-deleted entity?
Section titled “How do I restore a soft-deleted entity?”Use MutationMode.Restore:
[Mutation(Mode = MutationMode.Restore)][Endpoint(HttpVerb.Post, "api/v1/orders/{id}/restore")]public partial class RestoreOrderMutation : Mutation<Order>{ public required Guid Id { get; init; }}This bypasses all filters (including soft-delete), loads the entity, and resets IsDeleted, DeletedAt, and DeletedBy.
Why does my repository return stale data?
Section titled “Why does my repository return stale data?”If you use AsNoTracking() (the default for queries), the data is a snapshot at query time. For mutations, ensure the entity is loaded within the same scope as the save. If you cache entity references across scopes, they may become stale.
How do I debug generated code?
Section titled “How do I debug generated code?”All generated files live under obj/Debug/net10.0/generated/. In Visual Studio, expand Dependencies > Analyzers > Pragmatic.SourceGenerator in Solution Explorer. You can set breakpoints in generated .g.cs files.
Getting Help
Section titled “Getting Help”- GitHub Issues: github.com/nicola-pragmatic/Pragmatic.Design/issues
- Showcase Examples: See the
Showcase.Catalog,Showcase.Booking, andShowcase.Billingprojects for working entity, repository, query, and mutation implementations. - Diagnostics Guide: See diagnostics.md for detailed explanation of every PRAG06xx diagnostic.
- Query Pipeline: See query-pipeline.md for the full query execution flow.
- Mutation Pipeline: See mutation-pipeline.md for the full mutation execution flow.