agentsclimarketplace

Audit trail

Skill FaysilAlshareef/dotnet-ai-kit/skills/data/audit-trail

Use when adding automatic audit trail (CreatedAt, UpdatedBy) via EF Core interceptors.From its SKILL.md

Install
npx -y skills add FaysilAlshareef/dotnet-ai-kit --skill audit-trail

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

3.9 KB, as published. Nobody here has run it

Audit Trail

IAuditable Interface

namespace {Company}.{Domain}.Domain.Interfaces;

public interface IAuditable
{
    DateTime CreatedAt { get; set; }
    DateTime? UpdatedAt { get; set; }
    string? CreatedBy { get; set; }
    string? UpdatedBy { get; set; }
}

Entity Implementation

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; }
}

SaveChanges Interceptor

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);
    }
}

Soft Delete

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;

Global Query Filter for Soft Delete

// In DbContext.OnModelCreating
modelBuilder.Entity<Order>().HasQueryFilter(o => !o.IsDeleted);

Registration

services.AddDbContext<ApplicationDbContext>((provider, options) =>
{
    options.UseSqlServer(connectionString)
           .AddInterceptors(provider.GetRequiredService<AuditableEntityInterceptor>());
});

services.AddScoped<AuditableEntityInterceptor>();

Anti-Patterns

Anti-PatternCorrect Approach
Setting audit fields manually in handlersUse interceptor
Using DateTime.UtcNow directlyInject TimeProvider
Hard-deleting audited entitiesUse soft delete
Audit fields with public set on domain entitiesAcceptable — IAuditable needs setters for interceptor

Detect Existing Patterns

grep -r "IAuditable\|CreatedAt\|UpdatedAt" --include="*.cs"
grep -r "SaveChangesInterceptor\|SavingChangesAsync" --include="*.cs"

Adding to Existing Project

  1. Create IAuditable interface in Domain
  2. Implement on entities that need auditing
  3. Create interceptor in Infrastructure
  4. Register interceptor in DbContext setup

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.