Troubleshooting
Practical problem/solution guide for Pragmatic.Patch. Each section covers a common issue, the likely causes, and the fix.
PRAG1900: Type Must Be Partial
Section titled “PRAG1900: Type Must Be Partial”Your patch type fails to compile with diagnostic PRAG1900.
Add the partial keyword:
// Wrong[GeneratePatch<Guest>]public record UpdateGuestPatch;
// Right[GeneratePatch<Guest>]public partial record UpdateGuestPatch;The source generator emits properties, methods, and a JSON converter into a partial class. Without partial, the compiler cannot merge the generated code with your declaration.
PRAG1901: Entity Type Could Not Be Resolved
Section titled “PRAG1901: Entity Type Could Not Be Resolved”The generator reports that the entity type in [GeneratePatch<TEntity>] could not be resolved.
Checklist
Section titled “Checklist”-
Is the entity type accessible? The entity class must be in a referenced assembly or the same project. Check that the project reference exists in your
.csproj. -
Is the entity type a class?
[GeneratePatch]requires a class. Records, structs, and interfaces are not supported as entity types. -
Is the source generator analyzer referenced? Verify the Pragmatic.SourceGenerator is referenced with
OutputItemType="Analyzer":<ProjectReference Include="..\Pragmatic.SourceGenerator\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" />
PRAG1902: No Settable Properties
Section titled “PRAG1902: No Settable Properties”The generator warns that the entity has no properties suitable for patching.
Checklist
Section titled “Checklist”-
Does the entity have writable properties? The generator looks for properties with either a public setter or a
Set*()method:// No settable properties -- PRAG1902public class Immutable { public string Name { get; } }// Has settable properties -- workspublic class Mutable { public string Name { get; set; } }// Also works -- Set* methodpublic class Encapsulated{public string Name { get; private set; }internal void SetName(string value) => Name = value;} -
Are all properties in the excluded list? If the entity only has
Id, auditing, and soft delete properties, all of them are excluded and nothing is left to patch.
All Optional Properties Are Undefined After Deserialization
Section titled “All Optional Properties Are Undefined After Deserialization”You send a PATCH request with values, but all Optional<T> properties remain Undefined in the handler.
Checklist
Section titled “Checklist”-
Is the patch type SG-generated? Only types annotated with
[GeneratePatch<TEntity>]get a generated JSON converter. If you created a manual DTO withOptional<T>properties, register the fallback converter:builder.Services.ConfigureHttpJsonOptions(options =>options.SerializerOptions.Converters.Add(new OptionalConverterFactory())); -
Is
Content-Typeset toapplication/json? Without the correct content type, ASP.NET Core may not invoke the JSON deserializer. -
Are property names matching? The generated converter respects
PropertyNamingPolicy. If your API uses camelCase but you send PascalCase (or vice versa), the converter handles both. But check for typos in property names. -
Is the request body valid JSON? Malformed JSON may fail silently. Check the response status code — a 400 indicates a deserialization error.
ApplyTo Does Nothing
Section titled “ApplyTo Does Nothing”You call patch.ApplyTo(entity) but no properties change on the entity.
Checklist
Section titled “Checklist”-
Did the client send any fields? Check
patch.ModifiedProperties— if it is empty, the JSON body was{}or all fields were absent. -
Are the property names correct in the JSON? If the JSON uses
"first_name"but the property isFirstName, the converter won’t match (unless you have a custom naming policy configured). -
Is the entity the correct type?
ApplyTo()is strongly typed. Verify you are passing the entity type specified in[GeneratePatch<TEntity>]. -
Are the properties excluded? Check if the properties you expect to patch are in the excluded list (Id, auditing, soft delete, collections, entity references). See Concepts: Excluded Properties.
Null Sent in JSON But Property Stays Undefined
Section titled “Null Sent in JSON But Property Stays Undefined”You send { "price": null } for a decimal property, but Price remains Undefined instead of Null.
decimal is a non-nullable value type. It cannot represent null, so the converter treats null as “not applicable” and leaves the property as Undefined.
If you need to support null values for a property, make the entity property nullable:
public class Product{ // Non-nullable: null in JSON becomes Undefined public decimal Price { get; set; }
// Nullable: null in JSON becomes Optional<decimal?>.Null public decimal? DiscountRate { get; set; }}For decimal Price, the only way to set it is to send an actual value: { "price": 0 } produces Optional.Of(0m).
ModifiedProperties Contains Unexpected Entries
Section titled “ModifiedProperties Contains Unexpected Entries”ModifiedProperties includes a field that the client did not send.
Checklist
Section titled “Checklist”-
Check the raw JSON. The generated converter tracks keys by their presence in the JSON object, not by their value. Even
{ "name": null }adds"Name"toModifiedProperties. -
Check for default serialization behavior. Some JSON serializers include properties with default values. Ensure the client is configured to omit undefined fields.
-
Check the naming policy. The converter normalizes property names through the configured
PropertyNamingPolicy. Both"name"and"Name"resolve to the same property.
Generated Code Not Appearing in IDE
Section titled “Generated Code Not Appearing in IDE”IntelliSense does not show the generated properties or ApplyTo() method.
Checklist
Section titled “Checklist”-
Restart the IDE. Source generators sometimes require a restart for the language server to pick up new outputs.
-
Build the project. Run
dotnet buildto ensure the generator executes. Check the build output for errors. -
Check the analyzer reference. Ensure the Pragmatic.SourceGenerator is referenced correctly:
<ProjectReference Include="..\Pragmatic.SourceGenerator\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" /> -
Check generated files. Look in
obj/Debug/net10.0/generated/for the generated.g.csfiles. They should include{TypeName}.Patch.g.csand{TypeName}.JsonConverter.g.cs.
Can I use Optional<T> outside of generated patch DTOs?
Section titled “Can I use Optional<T> outside of generated patch DTOs?”Yes. Optional<T> is a public struct in the Pragmatic.Patch namespace. You can use it in any DTO, but you must register the OptionalConverterFactory for JSON serialization:
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.Converters.Add(new OptionalConverterFactory()));Note: The factory uses MakeGenericType and is not AOT-safe. For NativeAOT, register typed converters directly.
Can I customize which properties are excluded?
Section titled “Can I customize which properties are excluded?”Not currently. The exclusion list is hardcoded in the generator (identity, auditing, soft delete, concurrency, navigation, entity references). If you need to exclude additional properties, use a separate entity type that omits them, or apply the patch selectively:
if (patch.SensitiveField.HasValue) return new ValidationError("SensitiveField cannot be patched");
patch.ApplyTo(entity);Can I use [GeneratePatch] with records?
Section titled “Can I use [GeneratePatch] with records?”Yes. Both partial record and partial class are supported. Records are recommended because they provide value equality and with expressions out of the box.
How does this work with Pragmatic.Endpoints?
Section titled “How does this work with Pragmatic.Endpoints?”Declare the patch as a [FromBody] property on the endpoint:
[Endpoint(HttpVerb.Patch, "/api/products/{productId}")]public partial class PatchProductEndpoint : Endpoint<ProductDto>{ [FromRoute] public required Guid ProductId { get; init; } [FromBody] public required PatchProduct Patch { get; init; }
public override async Task<Result<ProductDto, NotFoundError>> HandleAsync(CancellationToken ct) { var product = await _products.GetByIdAsync(ProductId, ct); if (product is null) return new NotFoundError("Product", ProductId.ToString());
Patch.ApplyTo(product); await _uow.SaveChangesAsync(ct); return ProductDto.FromEntity(product); }}Getting Help
Section titled “Getting Help”- Check the concepts guide for architecture overview
- Check the getting started guide for setup instructions
- Check the tri-state semantics guide for
Optional<T>details - Open an issue on the Pragmatic.Design repository with:
- Your entity class definition
- The
[GeneratePatch]declaration - The JSON body you are sending
- The expected vs actual behavior
- .NET version