원클릭으로
db-transactions
Use when managing database transactions, isolation levels, or cross-context coordination.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when managing database transactions, isolation levels, or cross-context coordination.
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 | db-transactions |
| description | Use when managing database transactions, isolation levels, or cross-context coordination. |
| metadata | {"category":"data","agent":"ef-specialist","when-to-use":"When managing database transactions, isolation levels, or cross-context coordination"} |
SaveChanges is already a transaction — one call saves atomically// SaveChangesAsync wraps all tracked changes in a single transaction
internal sealed class CreateOrderHandler(
IOrderRepository repository,
IUnitOfWork unitOfWork)
: IRequestHandler<CreateOrderCommand, Result<Guid>>
{
public async Task<Result<Guid>> Handle(
CreateOrderCommand request, CancellationToken ct)
{
var order = Order.Create(request.CustomerName);
repository.Add(order);
// All changes saved atomically — no explicit transaction needed
await unitOfWork.SaveChangesAsync(ct);
return Result<Guid>.Success(order.Id);
}
}
internal sealed class TransferOrderHandler(AppDbContext db)
: IRequestHandler<TransferOrderCommand, Result>
{
public async Task<Result> Handle(
TransferOrderCommand request, CancellationToken ct)
{
await using var transaction =
await db.Database.BeginTransactionAsync(ct);
try
{
// Operation 1: debit source
var source = await db.Accounts.FindAsync(
[request.SourceId], ct);
source!.Debit(request.Amount);
await db.SaveChangesAsync(ct);
// Operation 2: credit destination
var destination = await db.Accounts.FindAsync(
[request.DestinationId], ct);
destination!.Credit(request.Amount);
await db.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
return Result.Success();
}
catch
{
await transaction.RollbackAsync(ct);
throw;
}
}
}
// Marker interface for commands needing transactions
public interface ITransactionalRequest { }
public sealed record TransferOrderCommand(
Guid SourceId, Guid DestinationId, decimal Amount)
: IRequest<Result>, ITransactionalRequest;
// Pipeline behavior
public sealed class TransactionBehavior<TRequest, TResponse>(
AppDbContext db,
ILogger<TransactionBehavior<TRequest, TResponse>> logger)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : ITransactionalRequest
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
var typeName = typeof(TRequest).Name;
logger.LogInformation(
"Begin transaction for {RequestName}", typeName);
await using var transaction =
await db.Database.BeginTransactionAsync(ct);
try
{
var response = await next();
await transaction.CommitAsync(ct);
logger.LogInformation(
"Committed transaction for {RequestName}", typeName);
return response;
}
catch (Exception ex)
{
await transaction.RollbackAsync(ct);
logger.LogError(ex,
"Rolled back transaction for {RequestName}", typeName);
throw;
}
}
}
// Registration
services.AddMediatR(cfg =>
{
cfg.AddBehavior(typeof(IPipelineBehavior<,>),
typeof(TransactionBehavior<,>));
});
// Read Committed (default) — good for most scenarios
await using var transaction = await db.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted, ct);
// Serializable — strongest consistency, lowest concurrency
await using var transaction = await db.Database
.BeginTransactionAsync(IsolationLevel.Serializable, ct);
// Snapshot — optimistic, no blocking reads (SQL Server)
await using var transaction = await db.Database
.BeginTransactionAsync(IsolationLevel.Snapshot, ct);
// Entity with row version
public sealed class Order
{
public Guid Id { get; set; }
public byte[] RowVersion { get; set; } = default!;
}
// Configuration
builder.Property(o => o.RowVersion).IsRowVersion();
// Handling concurrency conflict
try
{
await db.SaveChangesAsync(ct);
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbValues = await entry.GetDatabaseValuesAsync(ct);
if (dbValues is null)
return Result.Failure(
Error.Conflict("Order.Deleted",
"Order was deleted by another user"));
// Client wins: force overwrite
entry.OriginalValues.SetValues(dbValues);
await db.SaveChangesAsync(ct);
// Or: Database wins: reload and return conflict
// return Result.Failure(Error.Conflict(...));
}
// When using EnableRetryOnFailure, manual transactions need
// an execution strategy wrapper
var strategy = db.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var transaction =
await db.Database.BeginTransactionAsync(ct);
// Operations...
await db.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
});
// Share a connection for transactions across DbContexts
// (same database, different schemas / modules)
var connection = db1.Database.GetDbConnection();
await connection.OpenAsync(ct);
await using var transaction = await connection.BeginTransactionAsync(ct);
db1.Database.UseTransaction(transaction as DbTransaction);
db2.Database.UseTransaction(transaction as DbTransaction);
await db1.SaveChangesAsync(ct);
await db2.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
| Level | Dirty Reads | Non-Repeatable | Phantoms | Use Case |
|---|---|---|---|---|
| Read Uncommitted | Yes | Yes | Yes | Reporting only |
| Read Committed | No | Yes | Yes | Default, general use |
| Repeatable Read | No | No | Yes | Account balances |
| Snapshot | No | No | No | Read-heavy with consistency |
| Serializable | No | No | No | Financial transactions |
DbUpdateConcurrencyExceptionBeginTransactionAsync usageITransactionalRequest or similar marker interfacesDbUpdateConcurrencyException handlingIsRowVersion() in entity configurationsCreateExecutionStrategy() usageDbUpdateConcurrencyException with retry or conflict response