name: "party-tph-catalog" description: "Use when adding a small polymorphic catalog dimension (Gender, BlogTag, BlogPostStatus, statuses, types, categories-as-lookup) instead of scaffolding a full entity. Covers the Party base (TPH, int key, PartyType discriminator), PartyRelation for party-to-party links, DTO inheritance from Info/Create/UpdatePartyDto, BasePartyService with SetPolicies and SearchByCriteriaByPartyTypeAsync, and discriminator registration. Domain: Persistence, Domain Modeling. Level: Intermediate. Tags: party, tph, discriminator, catalog, lookup." allowed-tools: [Read, Grep, Glob]
Activation
ACTIVATION:
triggers:
- "party pattern"
- "catalog dimension"
- "lookup table"
- "TPH"
- "new tag/status/type entity"
context: decision-point | implementation
anti-triggers:
- "Entities with their own rich schema — use scaffold-template-entity"
- "Anything needing its own table or non-int key"
Detail
Party (TPH) Catalog Pattern
Problem
Small catalog dimensions (Gender, BlogTag, BlogPostStatus, BlogPostVisibility…) each need audit fields, state, soft
delete, permissions and CRUD — but giving each its own table + migration + full scaffold is overkill for rows that are
basically (id, description).
Solution
All catalog dimensions share one table (Parties) via TPH with a PartyType string discriminator.
- Inherit
Party(which already implementsIEntity<int>,IAuditableEntity,IStateEntity;Descriptionserves as the display name). New properties must be nullable/defaulted so they don't interfere with sibling types:
public class BlogTag : Party
{
public string Slug { get; set; } = string.Empty;
public string TagName => Description ?? string.Empty; // computed alias for clarity
}
public class Gender : Party { }
- Register the discriminator in
PartyConfiguration(.HasValue<Gender>(ResourceIdentifiers.Gender)), addDbSet<Gender> GenderstoAppDbSetter, and theResourceIdentifiersconstant. No new table/migration for the type itself (only if you added columns). - DTOs inherit the Party DTOs in one file:
InfoGenderDto : InfoPartyDto,CreateGenderDto : CreatePartyDto,UpdateGenderDto : UpdatePartyDto(reuseDeletePartyDto). - Service inherits
BasePartyService<TParty, TInfo, TCreate, TUpdate, TDelete>(int key, extendsPayloadService<>): callSetPolicies(_cachePolicies)in the constructor;CreateAsyncstampscreateDto.PartyType = typeof(TParty).Nameautomatically; type-scoped reads viaSearchByCriteriaByPartyTypeAsync<TDerived>(model)/ExportByPartyTypeAsync<TDerived>(...)which filter by discriminator through_repository.AsQueryableOfType<TDerived>(false). - Party-to-party links use the
PartyRelationtable (PartyId,RelatedPartyId,RelationshipType,StartDate/EndDate, notes; auditable) — don't invent join tables between dimensions. - Endpoints/permissions follow the standard recipe (
GenderEndpoints : IEndpointDefinition, attribute triad).
Applicability
| Condition | Apply? |
|---|---|
| Small lookup shared-shape dimension (id + description + state) | ✅ Yes |
| Dimension with 1-3 extra nullable columns | ✅ Yes |
| Entity with rich schema, relations, own lifecycle | ❌ No — full scaffold |
| Read-only projection | ❌ No — QueryPayloadService<> |
Gotchas
- Key is
int(Party owns it) — not theGuiddefault of full entities. - New properties on a derived type live on the shared table — keep them nullable or defaulted, or sibling inserts break.
- Forgetting the discriminator registration makes EF treat rows as base
Party— type-scoped queries return empty. SearchByCriteriaByPartyTypeAsynccaches under the first policy (falls back toCacheRoutes.Parties) — callSetPoliciesor you share the generic Parties cache.- Root SKILL.md declares this pattern as its anti-trigger — one entry point per flow: full entity → root skill, catalog dimension → this one.
Related Skills
scaffold-template-entity,permission-model,cache-policy-eviction
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.