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