ワンクリックで
audit-trail
Use when adding automatic audit trail (CreatedAt, UpdatedBy) via EF Core interceptors.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when adding automatic audit trail (CreatedAt, UpdatedBy) via EF Core interceptors.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when adding caching to .NET APIs or optimizing response times with distributed cache, output cache, or ETags.
Use when configuring API response formats, custom formatters, or Accept header handling.
Use when building controller-based REST APIs with action results, model binding, or MediatR integration.
Use when creating RESTful API controllers with MediatR dispatch and ProblemDetails error responses.
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when building minimal API endpoints with route groups, filters, or TypedResults.
| name | audit-trail |
| description | Use when adding automatic audit trail (CreatedAt, UpdatedBy) via EF Core interceptors. |
| metadata | {"category":"data","agent":"ef-specialist","when-to-use":"When implementing audit trail with IAuditable interface or EF Core interceptor"} |
namespace {Company}.{Domain}.Domain.Interfaces;
public interface IAuditable
{
DateTime CreatedAt { get; set; }
DateTime? UpdatedAt { get; set; }
string? CreatedBy { get; set; }
string? UpdatedBy { get; set; }
}
namespace {Company}.{Domain}.Domain.Entities;
public class Order : IAuditable
{
public Guid Id { get; private set; }
public string CustomerName { get; private set; } = default!;
public decimal Total { get; private set; }
// IAuditable
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
}
namespace {Company}.{Domain}.Infrastructure.Interceptors;
public sealed class AuditableEntityInterceptor(
IHttpContextAccessor httpContextAccessor,
TimeProvider timeProvider)
: SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken ct = default)
{
if (eventData.Context is null) return base.SavingChangesAsync(eventData, result, ct);
var now = timeProvider.GetUtcNow().UtcDateTime;
var userId = httpContextAccessor.HttpContext?.User
.FindFirstValue(ClaimTypes.NameIdentifier);
foreach (var entry in eventData.Context.ChangeTracker
.Entries<IAuditable>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedAt = now;
entry.Entity.CreatedBy = userId;
break;
case EntityState.Modified:
entry.Entity.UpdatedAt = now;
entry.Entity.UpdatedBy = userId;
break;
}
}
return base.SavingChangesAsync(eventData, result, ct);
}
}
public interface ISoftDeletable
{
bool IsDeleted { get; set; }
DateTime? DeletedAt { get; set; }
string? DeletedBy { get; set; }
}
// In interceptor, handle EntityState.Deleted:
case EntityState.Deleted when entry.Entity is ISoftDeletable softDeletable:
entry.State = EntityState.Modified;
softDeletable.IsDeleted = true;
softDeletable.DeletedAt = now;
softDeletable.DeletedBy = userId;
break;
// In DbContext.OnModelCreating
modelBuilder.Entity<Order>().HasQueryFilter(o => !o.IsDeleted);
services.AddDbContext<ApplicationDbContext>((provider, options) =>
{
options.UseSqlServer(connectionString)
.AddInterceptors(provider.GetRequiredService<AuditableEntityInterceptor>());
});
services.AddScoped<AuditableEntityInterceptor>();
| Anti-Pattern | Correct Approach |
|---|---|
| Setting audit fields manually in handlers | Use interceptor |
Using DateTime.UtcNow directly | Inject TimeProvider |
| Hard-deleting audited entities | Use soft delete |
Audit fields with public set on domain entities | Acceptable — IAuditable needs setters for interceptor |
grep -r "IAuditable\|CreatedAt\|UpdatedAt" --include="*.cs"
grep -r "SaveChangesInterceptor\|SavingChangesAsync" --include="*.cs"
IAuditable interface in Domain