//
// Copyright (c) PlaceholderCompany. All rights reserved.
//
namespace Jellyfin.Database.Providers.Postgres;
using System;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
///
/// Helper class for managing transactions during bulk operations to reduce dead tuple generation.
///
public sealed class BulkOperationTransaction : IAsyncDisposable, IDisposable
{
private readonly JellyfinDbContext _context;
private IDbContextTransaction? _transaction;
private bool _disposed;
///
/// Initializes a new instance of the class.
///
/// The database context.
public BulkOperationTransaction(JellyfinDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
///
/// Gets a value indicating whether the transaction has been committed.
///
public bool IsCommitted { get; private set; }
///
/// Begins a new transaction with isolation level optimized for bulk operations.
///
/// Cancellation token.
/// A task representing the asynchronous operation.
public async Task BeginAsync(CancellationToken cancellationToken = default)
{
if (_transaction is not null)
{
throw new InvalidOperationException("Transaction is already active. Call Dispose or RollbackAsync first.");
}
// Use READ COMMITTED isolation for bulk operations
// This provides a good balance between consistency and concurrency
_transaction = await _context.Database.BeginTransactionAsync(
System.Data.IsolationLevel.ReadCommitted,
cancellationToken).ConfigureAwait(false);
}
///
/// Commits the transaction.
///
/// Cancellation token.
/// A task representing the asynchronous operation.
public async Task CommitAsync(CancellationToken cancellationToken = default)
{
if (_transaction is null)
{
throw new InvalidOperationException("No active transaction. Call BeginAsync first.");
}
try
{
await _transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
IsCommitted = true;
}
finally
{
_transaction.Dispose();
_transaction = null;
}
}
///
/// Rolls back the transaction.
///
/// Cancellation token.
/// A task representing the asynchronous operation.
public async Task RollbackAsync(CancellationToken cancellationToken = default)
{
if (_transaction is null)
{
return;
}
try
{
await _transaction.RollbackAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_transaction.Dispose();
_transaction = null;
}
}
///
/// Disposes the transaction if not already committed.
///
public void Dispose()
{
if (_disposed)
{
return;
}
_transaction?.Dispose();
_transaction = null;
_disposed = true;
}
///
/// Disposes the transaction asynchronously if not already committed.
///
/// A task representing the asynchronous operation.
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
if (_transaction is not null)
{
await _transaction.DisposeAsync().ConfigureAwait(false);
}
_transaction = null;
_disposed = true;
}
}