Files
pgsql-jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres/BulkOperationTransaction.cs
T

139 lines
4.1 KiB
C#

// <copyright file="BulkOperationTransaction.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
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;
/// <summary>
/// Helper class for managing transactions during bulk operations to reduce dead tuple generation.
/// </summary>
public sealed class BulkOperationTransaction : IAsyncDisposable, IDisposable
{
private readonly JellyfinDbContext _context;
private IDbContextTransaction? _transaction;
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="BulkOperationTransaction"/> class.
/// </summary>
/// <param name="context">The database context.</param>
public BulkOperationTransaction(JellyfinDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
/// <summary>
/// Gets a value indicating whether the transaction has been committed.
/// </summary>
public bool IsCommitted { get; private set; }
/// <summary>
/// Begins a new transaction with isolation level optimized for bulk operations.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
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);
}
/// <summary>
/// Commits the transaction.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
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;
}
}
/// <summary>
/// Rolls back the transaction.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task RollbackAsync(CancellationToken cancellationToken = default)
{
if (_transaction is null)
{
return;
}
try
{
await _transaction.RollbackAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_transaction.Dispose();
_transaction = null;
}
}
/// <summary>
/// Disposes the transaction if not already committed.
/// </summary>
public void Dispose()
{
if (_disposed)
{
return;
}
_transaction?.Dispose();
_transaction = null;
_disposed = true;
}
/// <summary>
/// Disposes the transaction asynchronously if not already committed.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
if (_transaction is not null)
{
await _transaction.DisposeAsync().ConfigureAwait(false);
}
_transaction = null;
_disposed = true;
}
}