Table of Contents

Class KickRequestService

Namespace
FishMMO.Database.Npgsql.Services
Assembly
FishMMO-DB.dll

Base class for database services that execute EF Core operations with consistent execution behavior (transactional and read-only), retry behavior for transient failures, and standardized error mapping into DatabaseResult.

public sealed class KickRequestService : BaseService<KickRequestEntity>, IKickRequestService
Inheritance
KickRequestService
Implements
Inherited Members

Remarks

This base type provides three primary execution paths:

  • ExecuteTransactionAsync(Func<Task>, CancellationToken) and ExecuteTransactionAsync<TResult>(Func<Task<TResult>>,string,CancellationToken) create a fresh NpgsqlDbContext, begin an explicit transaction, execute the delegate, then call SaveChangesAsync(CancellationToken) and commit.
  • ExecuteWriteAsync(Func<Task>,string,CancellationToken) and ExecuteWriteAsync<TResult>(Func<Task<TResult>>,string,CancellationToken) create a fresh NpgsqlDbContext, execute the delegate, then call SaveChangesAsync(CancellationToken) without starting an explicit transaction.
  • ExecuteReadAsync(Func<Task>,string,CancellationToken) and ExecuteReadAsync<TResult>(Func<Task<TResult>>,string,CancellationToken) create a fresh NpgsqlDbContext but do not start an explicit transaction and do not call SaveChanges.

Standalone Execution (no ambient scope): A new context is created per attempt to avoid EF change-tracker state leaking across retries. Transient database failures are retried with exponential backoff. Optimistic concurrency conflicts (Version-based authority) and StaleStateException are never retried; they are returned as non-transient failures so the caller can re-read and decide how to proceed.

Ambient Scope Execution (inside an existing Unit of Work): When an operation detects an active FishMMO.Database.Npgsql.Services.DatabaseExecutionScope, it reuses the ambient NpgsqlDbContext and does NOT retry on transient failures. This is by design for the following reasons:

  1. Transaction State Corruption: PostgreSQL aborts the entire transaction on most transient failures. The connection and transaction become unusable, making retry with the same context impossible.
  2. Context State Pollution: The DbContext's change tracker accumulates state. Retrying with a polluted change tracker can cause duplicate key violations or incorrect updates.
  3. Semantic Correctness: If operation A succeeded and operation B failed transiently, retrying B alone without re-evaluating A's preconditions could violate business invariants.

When a transient failure occurs inside an ambient scope, the failure is returned immediately so the caller can restart the entire unit of work with fresh state. Savepoints are used for nested atomicity but cannot recover from connection-level failures.

Constructors

KickRequestService(INpgsqlDbContextFactory)

Initializes a new instance of KickRequestService.

public KickRequestService(INpgsqlDbContextFactory dbContextFactory)

Parameters

dbContextFactory INpgsqlDbContextFactory

DbContext factory for creating contexts.

Exceptions

ArgumentNullException

Thrown when dbContextFactory is null.

Methods

DeleteAsync(string, CancellationToken)

Deletes all kick requests for the specified account.

public Task<DatabaseResult<int>> DeleteAsync(string accountName, CancellationToken cancellationToken = default)

Parameters

accountName string

Account name whose kick requests will be deleted.

cancellationToken CancellationToken

Cancellation token.

Returns

Task<DatabaseResult<int>>

A DatabaseResult<T> containing the number of kick requests deleted on success, or a DatabaseException on failure.

Remarks

This is an idempotent cleanup operation. Unlike entity delete methods, this method does NOT throw DatabaseEntityNotFoundException when no records exist. Instead, it returns 0 rows deleted. This design supports safe concurrent cleanup where multiple callers may attempt to delete the same records.

Uses ExecuteSqlRawAsync with execution strategy wrapping to ensure transient database failures are automatically retried.

FetchAsync(DateTime, long, int, CancellationToken)

Fetches paginated kick requests based on timestamp and position.

public Task<DatabaseResult<List<KickRequestData>>> FetchAsync(DateTime lastFetch, long lastPosition, int amount, CancellationToken cancellationToken = default)

Parameters

lastFetch DateTime

Timestamp to compare requests against.

lastPosition long

Last request ID fetched (for pagination).

amount int

Maximum number of requests to fetch.

cancellationToken CancellationToken

Cancellation token.

Returns

Task<DatabaseResult<List<KickRequestData>>>

A DatabaseResult<T> containing the list of kick request data on success, or a DatabaseException on failure.

Remarks

This method uses LINQ (ToListAsync with AsNoTracking) and automatically benefits from the retry policy configured on the DbContext without requiring explicit execution strategy wrapping. Uses pagination pattern with timestamp and ID for reliable cursor-based pagination. Returns empty list for invalid amount.

HasPendingAsync(string, CancellationToken)

Checks whether a pending kick request exists for the specified account.

public Task<DatabaseResult<bool>> HasPendingAsync(string accountName, CancellationToken cancellationToken = default)

Parameters

accountName string

Account name to check.

cancellationToken CancellationToken

Cancellation token.

Returns

Task<DatabaseResult<bool>>

A DatabaseResult<T> containing true if a pending kick request exists, false otherwise, or a DatabaseException on failure.

PersistAsync(string, CancellationToken)

Persists a kick request for the specified account.

public Task<DatabaseResult> PersistAsync(string accountName, CancellationToken cancellationToken = default)

Parameters

accountName string

Account name to kick.

cancellationToken CancellationToken

Cancellation token.

Returns

Task<DatabaseResult>

A DatabaseResult indicating success or containing a DatabaseException on failure.

Remarks

Uses SaveChangesAsync with execution strategy wrapping to ensure transient database failures are automatically retried. Creates new kick request with current UTC timestamp.