Claude Code Plugins

Community-maintained marketplace

Feedback

entity-development

@duc01226/EasyPlatform
2
0

Use when creating or modifying domain entities with static expressions, computed properties, field tracking, and validation methods.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name entity-development
description Use when creating or modifying domain entities with static expressions, computed properties, field tracking, and validation methods.
allowed-tools Read, Write, Edit, Grep, Glob, Bash

Entity Development Workflow

When to Use This Skill

  • Creating new domain entities
  • Adding computed properties to entities
  • Creating static expression methods (filtering, queries)
  • Implementing entity validation
  • Setting up field change tracking

Pre-Flight Checklist

  • Identify the correct service/domain
  • Check for similar entities: grep "class {EntityName}" --include="*.cs"
  • Identify if audited or non-audited entity needed
  • Check existing expression patterns in domain

File Location

{Service}.Domain/
└── Entities/
    └── {EntityName}.cs

Entity Type Decision

Non-Audited Entity (Basic)

public class Employee : RootEntity<Employee, string>
{
    // No CreatedBy, UpdatedBy, CreatedDate, etc.
}

Audited Entity (With Audit Trail)

public class AuditedEmployee : RootAuditedEntity<AuditedEmployee, string, string>
{
    // Includes: CreatedBy, UpdatedBy, CreatedDate, UpdatedDate
}

Implementation Pattern

Basic Entity Structure

[TrackFieldUpdatedDomainEvent]  // Optional: Track all field changes
public sealed class Entity : RootEntity<Entity, string>
{
    // ═══════════════════════════════════════════════════════════════════════════
    // CORE PROPERTIES
    // ═══════════════════════════════════════════════════════════════════════════

    [TrackFieldUpdatedDomainEvent]  // Track specific field changes
    public string Name { get; set; } = "";

    public string Code { get; set; } = "";
    public string CompanyId { get; set; } = "";
    public EntityStatus Status { get; set; }
    public DateTime? EffectiveDate { get; set; }

    // ═══════════════════════════════════════════════════════════════════════════
    // NAVIGATION PROPERTIES
    // ═══════════════════════════════════════════════════════════════════════════

    [JsonIgnore]
    public Company? Company { get; set; }

    [JsonIgnore]
    public List<EntityChild>? Children { get; set; }

    // ═══════════════════════════════════════════════════════════════════════════
    // COMPUTED PROPERTIES (MUST have empty set { })
    // ═══════════════════════════════════════════════════════════════════════════

    [ComputedEntityProperty]
    public bool IsActive
    {
        get => Status == EntityStatus.Active && !IsDeleted;
        set { }  // Required empty setter for EF Core
    }

    [ComputedEntityProperty]
    public string DisplayName
    {
        get => $"{Code} - {Name}".Trim();
        set { }  // Required empty setter
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // STATIC EXPRESSIONS (For Repository Queries)
    // ═══════════════════════════════════════════════════════════════════════════

    // Simple filter expression
    public static Expression<Func<Entity, bool>> OfCompanyExpr(string companyId)
        => e => e.CompanyId == companyId;

    // Unique constraint expression
    public static Expression<Func<Entity, bool>> UniqueExpr(string companyId, string code)
        => e => e.CompanyId == companyId && e.Code == code;

    // Filter by status list
    public static Expression<Func<Entity, bool>> FilterByStatusExpr(List<EntityStatus> statuses)
    {
        var statusSet = statuses.ToHashSet();
        return e => e.Status.HasValue && statusSet.Contains(e.Status.Value);
    }

    // Composite expression with conditional
    public static Expression<Func<Entity, bool>> ActiveInCompanyExpr(string companyId, bool includeInactive = false)
        => OfCompanyExpr(companyId).AndAlsoIf(!includeInactive, () => e => e.IsActive);

    // Full-text search columns
    public static Expression<Func<Entity, object?>>[] DefaultFullTextSearchColumns()
        => [e => e.Name, e => e.Code, e => e.Description];

    // ═══════════════════════════════════════════════════════════════════════════
    // ASYNC EXPRESSIONS (When External Dependencies Needed)
    // ═══════════════════════════════════════════════════════════════════════════

    public static async Task<Expression<Func<Entity, bool>>> FilterWithLicenseExprAsync(
        IRepository<License> licenseRepo,
        string companyId,
        CancellationToken ct = default)
    {
        var hasLicense = await licenseRepo.HasLicenseAsync(companyId, ct);
        return hasLicense ? PremiumFilterExpr() : StandardFilterExpr();
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // VALIDATION METHODS
    // ═══════════════════════════════════════════════════════════════════════════
    // Naming Convention:
    // - Validate[Context]() → Returns PlatformValidationResult, never throws
    // - Ensure[Context]Valid() → Returns void/T, throws PlatformValidationException
    // - At call site: Use Validate...().EnsureValid() instead of wrapper Ensure methods

    public PlatformValidationResult ValidateCanBeUpdated()
    {
        return PlatformValidationResult.Valid()
            .And(() => !IsDeleted, "Entity is deleted")
            .And(() => Status != EntityStatus.Locked, "Entity is locked");
    }

    public async Task<PlatformValidationResult> ValidateAsync(
        IRepository<Entity> repository,
        CancellationToken ct = default)
    {
        return await PlatformValidationResult.Valid()
            .And(() => Name.IsNotNullOrEmpty(), "Name is required")
            .And(() => Code.IsNotNullOrEmpty(), "Code is required")
            .AndNotAsync(async () => await repository.AnyAsync(
                e => e.Id != Id && e.CompanyId == CompanyId && e.Code == Code, ct),
                "Code already exists");
    }

    // ═══════════════════════════════════════════════════════════════════════════
    // INSTANCE METHODS
    // ═══════════════════════════════════════════════════════════════════════════

    public void Activate() => Status = EntityStatus.Active;
    public void Deactivate() => Status = EntityStatus.Inactive;
    public void Reset() { /* reset logic */ }
}

Expression Composition Patterns

Pattern Usage Example
AndAlso Combine two expressions with AND expr1.AndAlso(expr2)
OrElse Combine two expressions with OR expr1.OrElse(expr2)
AndAlsoIf Conditional AND .AndAlsoIf(condition, () => expr)
OrElseIf Conditional OR .OrElseIf(condition, () => expr)
// Composing multiple expressions
var expr = Entity.OfCompanyExpr(companyId)
    .AndAlso(Entity.FilterByStatusExpr(statuses))
    .AndAlsoIf(deptIds.Any(), () => Entity.FilterByDeptExpr(deptIds))
    .AndAlsoIf(searchText.IsNotNullOrEmpty(), () => Entity.SearchExpr(searchText));

Computed Property Rules

:white_check_mark: MUST have empty setter set { }

[ComputedEntityProperty]
public bool IsRoot
{
    get => Id == RootId;
    set { }  // Required for EF Core mapping
}

:x: Wrong - No setter

[ComputedEntityProperty]
public bool IsRoot => Id == RootId;  // Will cause EF Core issues

Anti-Patterns to AVOID

:x: Business logic in properties

// WRONG - calling services in property
public bool IsValid => validationService.Validate(this);

:x: Missing [JsonIgnore] on navigation

// WRONG - circular reference issues
public Company Company { get; set; }

:x: Complex logic in getters

// WRONG - should be a method
public List<Item> ActiveItems => Items.Where(x => x.IsActive).ToList();

Verification Checklist

  • Entity inherits from correct base class (RootEntity or RootAuditedEntity)
  • Computed properties have [ComputedEntityProperty] and empty set { }
  • Navigation properties have [JsonIgnore]
  • Static expressions follow naming convention: {Purpose}Expr
  • Full-text search columns defined if searchable
  • Validation methods return PlatformValidationResult
  • [TrackFieldUpdatedDomainEvent] on tracked fields