skills/microservice/query/query-entity/SKILL.md
Use when designing query-side entities with private setters and event-based state updates.
npx skillsauth add faysilalshareef/dotnet-ai-kit query-entityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
{ get; private set; } — state changes only through constructors and behavior methodsEvent<TData> for entity creation, sets Sequence = @event.Sequence(TData data, int sequence) and update state + Sequenceint Sequence { get; private set; } tracks last applied event for idempotencyList<T> initialized with = []Every event is wrapped in a generic envelope used by both entities and handlers:
namespace {Company}.{Domain}.Query.Domain.Events;
public class Event<T> : IRequest<bool>
{
public Guid AggregateId { get; set; }
public int Sequence { get; set; }
public Guid? UserId { get; set; }
public required string Type { get; set; }
public required T Data { get; set; }
public DateTime DateTime { get; set; }
public int Version { get; set; }
}
namespace {Company}.{Domain}.Query.Domain.Entities;
public class Order
{
// Creation constructor — from event envelope
public Order(Event<OrderCreatedData> @event)
{
Id = @event.AggregateId;
CustomerName = @event.Data.CustomerName;
Email = @event.Data.Email;
Total = @event.Data.Total;
Status = OrderStatus.Pending;
Sequence = @event.Sequence;
CreatedAt = @event.DateTime;
}
// EF Core materialization constructor — private, all parameters
private Order(
Guid id,
string customerName,
string email,
decimal total,
OrderStatus status,
int sequence,
DateTime createdAt)
{
Id = id;
CustomerName = customerName;
Email = email;
Total = total;
Status = status;
Sequence = sequence;
CreatedAt = createdAt;
}
// All properties: { get; private set; }
public Guid Id { get; private set; }
public string CustomerName { get; private set; }
public string Email { get; private set; }
public decimal Total { get; private set; }
public OrderStatus Status { get; private set; }
public int Sequence { get; private set; }
public DateTime CreatedAt { get; private set; }
// Computed property — no setter
public bool IsActive => Status != OrderStatus.Cancelled;
// Navigation collections
public List<OrderItem> Items { get; private set; } = [];
// Behavior method: (TData data, int sequence) signature
public void UpdateDetails(OrderUpdatedData data, int sequence)
{
CustomerName = data.CustomerName;
Email = data.Email;
Total = data.Total;
Sequence = sequence;
}
// Behavior method: data + sequence, updates state
public void ChangeStatus(OrderStatusChangedData data, int sequence)
{
Status = data.NewStatus;
Sequence = sequence;
}
// Behavior method: sequence-only when data is minimal
public void Cancel(int sequence)
{
Status = OrderStatus.Cancelled;
Sequence = sequence;
}
// Some entities use SetSequence for simple tracking
public void SetSequence(int sequence)
=> Sequence = sequence;
}
Some entities use a static Create method instead of a public constructor:
public class Product
{
// Private constructor for EF Core and factory
private Product(
int sequence,
Guid id,
string name,
decimal price,
ProductType type)
{
Sequence = sequence;
Id = id;
Name = name;
Price = price;
Type = type;
}
public Guid Id { get; private set; }
public int Sequence { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public ProductType Type { get; private set; }
public List<ProductVariant> Variants { get; private set; } = [];
// Static factory from event
public static Product Create(Event<ProductCreatedData> @event)
=> new(
1,
@event.AggregateId,
@event.Data.Name,
@event.Data.Price,
@event.Data.ProductType
);
// Update from full event envelope
public void Update(Event<ProductUpdatedData> @event)
{
Name = @event.Data.Name;
Price = @event.Data.Price;
Type = @event.Data.ProductType;
}
public void IncrementSequence() => Sequence++;
}
namespace {Company}.{Domain}.Query.Domain.Entities;
public class OrderItem
{
public OrderItem(OrderItemData item, Guid orderId)
{
OrderId = orderId;
ProductId = item.ProductId;
ProductName = item.ProductName;
Quantity = item.Quantity;
UnitPrice = item.UnitPrice;
}
public Guid OrderId { get; private set; }
public Guid ProductId { get; private set; }
public string ProductName { get; private set; }
public int Quantity { get; private set; }
public decimal UnitPrice { get; private set; }
}
| Style | When Used | Example |
|---|---|---|
| Public Event<T> constructor | Simple entities, direct mapping | new Order(@event) |
| Static Create factory | Entities needing computed initial values | Product.Create(@event) |
| Signature | When Used | Example |
|---|---|---|
| (TData data, int sequence) | Handler passes data and sequence separately | order.UpdateDetails(data, sequence) |
| (Event<TData> @event) | Handler passes full event envelope | product.Update(@event) |
Both styles always update Sequence at the end.
| Anti-Pattern | Correct Approach |
|---|---|
| Public setters on properties | { get; private set; } on every property |
| Parameterless private constructor | Private constructor with ALL parameters for EF Core |
| Missing Sequence property | Every entity needs int Sequence { get; private set; } |
| Apply methods accepting full Event | Behavior methods accept (TData, int sequence) or full event |
| sealed class modifier | Entities are plain public class, not sealed |
| byte[] RowVersion property | Not used — sequence tracking handles concurrency |
# Find entities with private setters
grep -r "private set" --include="*.cs" Domain/Entities/
# Find event-based constructors
grep -r "public.*\(Event<" --include="*.cs" Domain/Entities/
# Find behavior methods with sequence parameter
grep -r "int sequence)" --include="*.cs" Domain/Entities/
# Find Sequence property
grep -r "public int Sequence" --include="*.cs" Domain/Entities/
{ get; private set; } — no exceptionsEvent<TData> for creation, sets all fields from @event.Data(TData data, int sequence) — always update Sequence last= [] syntaxDomain/Entities/ directorydata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.