# 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<T>`
  - `IEnumerable<T>` → `IAsyncEnumerable<T>` (for streaming) or `Task<List<T>>`
- [ ] Rename methods to include `Async` suffix
- [ ] Add XML documentation for cancellation token

**Example**:
```csharp
// BEFORE
void SaveUser(User user);
User? GetUser(Guid id);
IEnumerable<User> GetAllUsers();

// AFTER
Task SaveUserAsync(User user, CancellationToken cancellationToken = default);
Task<User?> GetUserAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<User>> GetAllUsersAsync(CancellationToken cancellationToken = default);
// OR for streaming:
IAsyncEnumerable<User> 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<T>`
- [ ] 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<ActionResult>` to controller methods
- [ ] Add `CancellationToken` parameter (ASP.NET Core will inject it)
- [ ] Use `await` when calling services

**Example**:
```csharp
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(
    [FromRoute] Guid id,
    CancellationToken cancellationToken)
{
    var user = await _userRepository.GetUserAsync(id, cancellationToken);
    return user is null ? NotFound() : Ok(_mapper.Map<UserDto>(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<T>`
  ```csharp
  // BEFORE
  _mockRepository.Setup(x => x.GetUser(It.IsAny<Guid>()))
      .Returns(user);
  
  // AFTER
  _mockRepository.Setup(x => x.GetUserAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
      .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<CancellationToken>()))
        .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<OperationCanceledException>(
        () => _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<T>`:

```csharp
public async IAsyncEnumerable<User> 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<User> 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<User?> 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
