139 lines
4.1 KiB
C#
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;
|
|
}
|
|
}
|
|
|