# Repository Async Conversion Checklist Use this checklist when converting a repository class from synchronous to asynchronous operations. ## 📋 Pre-Conversion Checklist - [ ] **Identify all public methods** in the repository interface - [ ] **Check for method dependencies** - what calls these methods? - [ ] **Review current tests** - ensure you understand expected behavior - [ ] **Create a feature branch** for the conversion - [ ] **Document breaking changes** for consumers ## 🔄 Conversion Steps ### Step 1: Update Interface Definition **File**: `MediaBrowser.Controller\Persistence\I[RepositoryName].cs` - [ ] Add `using System.Threading.Tasks;` - [ ] Add `CancellationToken` parameter to all methods - [ ] Change return types: - `void` → `Task` - `T` → `Task` - `IEnumerable` → `IAsyncEnumerable` (for streaming) or `Task>` - [ ] Rename methods to include `Async` suffix - [ ] Add XML documentation for cancellation token **Example**: ```csharp // BEFORE void SaveUser(User user); User? GetUser(Guid id); IEnumerable GetAllUsers(); // AFTER Task SaveUserAsync(User user, CancellationToken cancellationToken = default); Task GetUserAsync(Guid id, CancellationToken cancellationToken = default); Task> GetAllUsersAsync(CancellationToken cancellationToken = default); // OR for streaming: IAsyncEnumerable GetAllUsersAsync(CancellationToken cancellationToken = default); ``` ### Step 2: Update Implementation **File**: `[Project]\[RepositoryName].cs` #### 2.1 Method Signatures - [ ] Add `async` modifier to method declarations - [ ] Change return type to `Task` or `Task` - [ ] Add `CancellationToken` parameter #### 2.2 Database Context Usage - [ ] Change `using` to `await using` for DbContext ```csharp // BEFORE: using var context = _dbProvider.CreateDbContext(); // AFTER: await using var context = _dbProvider.CreateDbContext(); ``` #### 2.3 Transaction Handling - [ ] `BeginTransaction()` → `BeginTransactionAsync(cancellationToken)` - [ ] `Commit()` → `CommitAsync(cancellationToken)` - [ ] `Rollback()` → `RollbackAsync(cancellationToken)` - [ ] Change `using` to `await using` for transactions #### 2.4 Query Operations - [ ] `ToList()` → `ToListAsync(cancellationToken)` - [ ] `ToArray()` → `ToArrayAsync(cancellationToken)` - [ ] `ToDictionary()` → `ToDictionaryAsync(cancellationToken)` - [ ] `FirstOrDefault()` → `FirstOrDefaultAsync(cancellationToken)` - [ ] `First()` → `FirstAsync(cancellationToken)` - [ ] `SingleOrDefault()` → `SingleOrDefaultAsync(cancellationToken)` - [ ] `Single()` → `SingleAsync(cancellationToken)` - [ ] `Any()` → `AnyAsync(cancellationToken)` - [ ] `Count()` → `CountAsync(cancellationToken)` - [ ] `LongCount()` → `LongCountAsync(cancellationToken)` - [ ] `Sum()` → `SumAsync(cancellationToken)` - [ ] `Average()` → `AverageAsync(cancellationToken)` - [ ] `Min()` → `MinAsync(cancellationToken)` - [ ] `Max()` → `MaxAsync(cancellationToken)` #### 2.5 Command Operations - [ ] `SaveChanges()` → `SaveChangesAsync(cancellationToken)` - [ ] `ExecuteDelete()` → `ExecuteDeleteAsync(cancellationToken)` - [ ] `ExecuteUpdate()` → `ExecuteUpdateAsync(cancellationToken)` - [ ] `Add()` - no async version, but SaveChanges must be async - [ ] `Remove()` - no async version, but SaveChanges must be async - [ ] `Update()` - no async version, but SaveChanges must be async #### 2.6 Navigation Properties - [ ] `.Load()` → `.LoadAsync(cancellationToken)` - [ ] `.Entry().Collection().Load()` → `.Entry().Collection().LoadAsync(cancellationToken)` - [ ] `.Include()` - works with async, no change needed #### 2.7 ForEach Loops - [ ] Convert to `await foreach` for IAsyncEnumerable ```csharp // BEFORE foreach (var item in items) { // process } // AFTER await foreach (var item in items.WithCancellation(cancellationToken)) { // process } ``` #### 2.8 Helper Methods - [ ] Convert all private helper methods that use database to async - [ ] Propagate async/await through call chain ### Step 3: Update Consumers #### 3.1 Service Layer - [ ] Update service interfaces to use `Task` return types - [ ] Update service implementations to use `await` - [ ] Add `CancellationToken` parameters #### 3.2 API Controllers - [ ] Add `async Task` to controller methods - [ ] Add `CancellationToken` parameter (ASP.NET Core will inject it) - [ ] Use `await` when calling services **Example**: ```csharp [HttpGet("{id}")] public async Task> GetUser( [FromRoute] Guid id, CancellationToken cancellationToken) { var user = await _userRepository.GetUserAsync(id, cancellationToken); return user is null ? NotFound() : Ok(_mapper.Map(user)); } ``` #### 3.3 Background Services - [ ] Ensure `ExecuteAsync` properly awaits all operations - [ ] Pass cancellation token through the chain ### Step 4: Update Tests #### 4.1 Test Method Signatures - [ ] Change test methods to `async Task` - [ ] Add `await` before repository calls #### 4.2 Mock Setup - [ ] Update mocks to return `Task` or `Task` ```csharp // BEFORE _mockRepository.Setup(x => x.GetUser(It.IsAny())) .Returns(user); // AFTER _mockRepository.Setup(x => x.GetUserAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(user); ``` #### 4.3 Test Assertions - [ ] Use `await` for async operations in assertions - [ ] Test cancellation scenarios **Example Test**: ```csharp [Fact] public async Task GetUserAsync_ValidId_ReturnsUser() { // Arrange var userId = Guid.NewGuid(); var expectedUser = new User { Id = userId }; _mockRepository .Setup(x => x.GetUserAsync(userId, It.IsAny())) .ReturnsAsync(expectedUser); // Act var result = await _service.GetUserAsync(userId, CancellationToken.None); // Assert Assert.NotNull(result); Assert.Equal(userId, result.Id); } [Fact] public async Task GetUserAsync_Cancelled_ThrowsOperationCanceledException() { // Arrange var cts = new CancellationTokenSource(); cts.Cancel(); // Act & Assert await Assert.ThrowsAsync( () => _service.GetUserAsync(Guid.NewGuid(), cts.Token) ); } ``` ## 🔍 Code Review Checklist Before submitting PR: - [ ] All synchronous database operations converted - [ ] `CancellationToken` passed through entire call chain - [ ] No `Task.Result` or `.Wait()` calls (causes deadlocks) - [ ] No `async void` methods (except event handlers) - [ ] Proper `ConfigureAwait(false)` usage in library code (optional in ASP.NET Core) - [ ] All tests updated and passing - [ ] No performance regressions - [ ] Memory usage acceptable - [ ] Documentation updated ## ⚡ Performance Optimization Opportunities Consider these optimizations during conversion: ### 1. Parallel Operations When operations are independent, use `Task.WhenAll`: ```csharp // Sequential (slow) await DeleteAncestorsAsync(id, cancellationToken); await DeleteImagesAsync(id, cancellationToken); await DeleteChaptersAsync(id, cancellationToken); // Parallel (fast) await Task.WhenAll( DeleteAncestorsAsync(id, cancellationToken), DeleteImagesAsync(id, cancellationToken), DeleteChaptersAsync(id, cancellationToken) ); ``` ### 2. Streaming Results For large result sets, use `IAsyncEnumerable`: ```csharp public async IAsyncEnumerable GetAllUsersAsync( [EnumeratorCancellation] CancellationToken cancellationToken = default) { await using var context = _dbProvider.CreateDbContext(); await foreach (var user in context.Users.AsAsyncEnumerable() .WithCancellation(cancellationToken)) { yield return user; } } ``` ### 3. Batch Operations Use `ExecuteUpdateAsync` / `ExecuteDeleteAsync` instead of loading then saving: ```csharp // Slow - loads all into memory var items = await context.Items.Where(x => x.IsOld).ToListAsync(); context.Items.RemoveRange(items); await context.SaveChangesAsync(); // Fast - executes in database await context.Items .Where(x => x.IsOld) .ExecuteDeleteAsync(cancellationToken); ``` ## 🐛 Common Pitfalls to Avoid ### ❌ DON'T: Use Task.Result or .Wait() ```csharp // WRONG - causes deadlocks var user = GetUserAsync(id).Result; ``` ### ❌ DON'T: Forget cancellation token ```csharp // WRONG - can't cancel long-running operations public async Task GetUserAsync(Guid id) { return await context.Users.FirstOrDefaultAsync(x => x.Id == id); } ``` ### ❌ DON'T: Use async void ```csharp // WRONG - exceptions can't be caught public async void SaveUser(User user) { } ``` ### ✅ DO: Proper async pattern ```csharp // CORRECT public async Task GetUserAsync(Guid id, CancellationToken cancellationToken = default) { await using var context = _dbProvider.CreateDbContext(); return await context.Users .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); } ``` ## 📝 Commit Message Template ``` feat(repository): Convert [RepositoryName] to async - Updated I[RepositoryName] interface with async signatures - Converted all database operations to async/await - Added CancellationToken support throughout - Updated all tests to async - Updated consumers in service and controller layers BREAKING CHANGE: All [RepositoryName] methods are now async and require await. Migration guide: [link to documentation] Closes #[issue-number] ``` ## 🎯 Success Criteria - [ ] All tests passing - [ ] No compiler warnings - [ ] Code coverage maintained or improved - [ ] No performance regressions - [ ] Documentation updated - [ ] Migration guide provided - [ ] Breaking changes documented