General Principles

βœ… Follow SOLID Principles

Design your data access layer following SOLID principles. Each UnitOfWork should have a single responsibility, and dependencies should be injected rather than created directly.

βœ… Always Use Using Statements

UnitOfWork implements IDisposable. Always use using statements or explicitly call Dispose() to ensure proper resource cleanup.

Proper Resource Management

// Good - Using statement ensures disposal
using (var uow = new UnitofWork<Customer>(dmeEditor, "SqlServer", "Customers", "CustomerID"))
{
    // Work with UnitOfWork
    await uow.Get();
    uow.New();
    // ... work with data
    await uow.CommitAsync();
} // Automatically disposed here

// Also Good - Explicit disposal
var uow = new UnitOfWork<Customer>(dmeEditor, "SqlServer", "Customers", "CustomerID");
try
{
    // Work with UnitOfWork
    await uow.Get();
    await uow.CommitAsync();
}
finally
{
    uow.Dispose();
}
⚑ Use Async/Await Consistently

Always use async versions of methods for database operations to avoid blocking the UI thread.

Async Best Practices

// Good - Proper async usage
public async Task ProcessCustomerAsync(Customer customer)
{
    await customerUoW.InsertAsync(customer);
    var result = await customerUoW.CommitAsync();
    
    if (result.Flag == Errors.Ok)
    {
        await NotifySuccess();
    }
}

// Bad - Don't block async calls
public void ProcessCustomer(Customer customer)
{
    customerUoW.InsertAsync(customer).Wait();  // Can cause deadlocks
    var result = customerUoW.CommitAsync().Result;  // Can cause deadlocks
}

UnitOfWork Best Practices

πŸ”„ Initialize Early, Use Throughout

Initialize UnitOfWork instances early in your application lifecycle and reuse them throughout the session.

Service Pattern Implementation

public class CustomerService : IDisposable
{
    private readonly UnitofWork<Customer> _customerUoW;
    private readonly IDMEEditor _dmeEditor;
    
    public CustomerService(IDMEEditor dmeEditor)
    {
        _dmeEditor = dmeEditor;
        _customerUoW = new UnitofWork<Customer>(dmeEditor, "SqlServer", "Customers", "CustomerID");
        
        // Setup event handlers once
        _customerUoW.PreInsert += ValidateCustomer;
        _customerUoW.PostEdit += LogChanges;
        _customerUoW.PreDelete += CheckDeletePermission;
    }
    
    public async Task<List<Customer>> GetActiveCustomersAsync()
    {
        var filters = new List<AppFilter>
        {
            new AppFilter {FieldName = "IsActive", Operator = "=", FilterValue = true }
        };
        
        var result = await _customerUoW.Get(filters);
        return result.ToList();
    }
    
    public async Task<bool> CreateCustomerAsync(Customer customer)
    {
        try
        {
            await _customerUoW.InsertAsync(customer);
            var result = await _customerUoW.CommitAsync();
            return result.Flag == Errors.Ok;
        }
        catch (Exception ex)
        {
            await _customerUoW.Rollback();
            throw;
        }
    }
    
    public void Dispose()
    {
        _customerUoW?.Dispose();
    }
}
βœ”οΈ Implement Proper Validation

Use event handlers to implement validation logic at appropriate lifecycle points.

Validation Event Handlers

private void ValidateCustomer(object sender, UnitofWorkParams args)
{
    var customer = args.CurrentEntity as Customer;
    
    // Basic validation
    if (string.IsNullOrWhiteSpace(customer.Email))
    {
        args.Cancel = true;
        args.ErrorMessage = "Email is required";
        return;
    }
    
    // Format validation
    if (!IsValidEmail(customer.Email))
    {
        args.Cancel = true;
        args.ErrorMessage = "Invalid email format";
        return;
    }
    
    // Business rule validation
    if (customer.CreditLimit > 100000 && customer.CustomerType != "Premium")
    {
        args.Cancel = true;
        args.ErrorMessage = "High credit limits require Premium customer type";
        return;
    }
}

private void LogChanges(object sender, UnitofWorkParams args)
{
    var customer = args.CurrentEntity as Customer;
    
    _logger.LogInformation($"Customer {customer.CustomerID} modified: {args.PropertyName} = {args.PropertyValue}");
    
    // Optional: Track changes for audit
    if (args.PropertyName != null)
    {
        _auditService.LogChange(customer.CustomerID, args.PropertyName, args.PropertyValue);
    }
}
πŸ” Check IsDirty Before Operations

Always check IsDirty before committing or closing to avoid data loss.

Unsaved Changes Handling

public async Task<bool> CloseFormAsync()
{
    if (_customerUoW.IsDirty)
    {
        var result = MessageBox.Show(
            "You have unsaved changes. Would you like to save them?", 
            "Unsaved Changes", 
            MessageBoxButtons.YesNoCancel,
            MessageBoxIcon.Question);
        
        switch (result)
        {
            case DialogResult.Yes:
                var commitResult = await _customerUoW.CommitAsync();
                if (commitResult.Flag != Errors.Ok)
                {
                    MessageBox.Show($"Failed to save changes: {commitResult.Message}");
                    return false;
                }
                return true;
                
            case DialogResult.No:
                await _customerUoW.Rollback();
                return true;
                
            case DialogResult.Cancel:
                return false;
                
            default:
                return false;
        }
    }
    
    return true;
}
Ò†©ï¸ Handle Rollback Scenarios

Implement proper rollback handling for error scenarios.

Transaction Rollback

public async Task<bool> ProcessBatchAsync(List<Customer> customers)
{
    var addedCustomers = new List<Customer>();
    
    try
    {
        foreach (var customer in customers)
        {
            // Validate before adding
            ValidateCustomerData(customer);
            
            await _customerUoW.InsertAsync(customer);
            addedCustomers.Add(customer);
            
            // Optional: Progress reporting
            OnProgressUpdate?.Invoke(addedCustomers.Count, customers.Count);
        }
        
        var result = await _customerUoW.CommitAsync();
        if (result.Flag != Errors.Ok)
        {
            await _customerUoW.Rollback();
            return false;
        }
        
        return true;
    }
    catch (ValidationException ex)
    {
        await _customerUoW.Rollback();
        _logger.LogWarning($"Batch processing failed due to validation: {ex.Message}");
        throw;
    }
    catch (Exception ex)
    {
        await _customerUoW.Rollback();
        _logger.LogError(ex, "Batch processing failed unexpectedly");
        return false;
    }
}

MultiDataSourceUnitOfWork Best Practices

πŸ”— Setup Relationships Early

Define all relationships before starting data operations to ensure proper coordination.

Relationship Configuration

private async Task InitializeMultiUoWAsync()
{
    _multiUoW = new MultiDataSourceUnitOfWork(_dmeEditor);
    
    // Add all entity types first
    await _multiUoW.AddUnitOfWorkAsync<Customer>("SqlServer", "Customers", "CustomerID");
    await _multiUoW.AddUnitOfWorkAsync<Order>("MongoDB", "Orders", "OrderID");
    await _multiUoW.AddUnitOfWorkAsync<Address>("SqlServer", "Addresses", "AddressID");
    await _multiUoW.AddUnitOfWorkAsync<AuditLog>("DocumentDB", "AuditLogs", "LogID");
    
    // Define relationships with appropriate behaviors
    _multiUoW.AddRelationship("Customer", "Order", "CustomerID", "CustomerID", 
                             RelationshipBehavior.Restrict, false);
    
    _multiUoW.AddRelationship("Customer", "Address", "CustomerID", "CustomerID", 
                             RelationshipBehavior.CascadeDelete, false);
    
    _multiUoW.AddRelationship("Order", "AuditLog", "OrderID", "EntityID", 
                             RelationshipBehavior.CascadeDelete, true);
    
    // Setup event handlers
    _multiUoW.ChildDataChanged += OnChildDataChanged;
    
    // Load initial data
    await LoadInitialDataAsync();
}

private async Task LoadInitialDataAsync()
{
    // Load customers first (parent entities)
    var customers = _multiUoW.GetEntities<Customer>("Customer");
    await customers.LoadAsync();
    
    // Then load related data
    if (customers.Any())
    {
        var firstCustomer = customers.First();
        await _multiUoW.NavigateToParentAsync("Customer", firstCustomer);
    }
}
πŸ“‘ Use Events for UI Updates

Subscribe to ChildDataChanged events to keep UI synchronized with data changes.

Event-Driven UI Updates

private async void OnChildDataChanged(object sender, ChildDataChangedEventArgs args)
{
    // Update UI based on which child data changed
    switch (args.ChildEntityName)
    {
        case "Order":
            await RefreshOrdersGrid();
            UpdateOrderSummary();
            break;
            
        case "Address":
            await RefreshAddressControls();
            break;
            
        case "AuditLog":
            // Don't refresh UI for audit logs to avoid performance issues
            break;
    }
    
    // Update any aggregate displays
    UpdateCustomerSummary();
}

private async Task RefreshOrdersGrid()
{
    var currentCustomer = _multiUoW.CurrentParentEntity as Customer;
    if (currentCustomer != null)
    {
        var orders = await _multiUoW.GetAllRelatedChildrenAsync<Order, Customer>("Customer", currentCustomer);
        
        // Update data binding
        ordersDataGridView.DataSource = orders["Order"];
        
        // Update status
        lblOrderCount.Text = $"Orders: {orders["Order"].Count}";
    }
}
βš™οΈ Choose Appropriate Relationship Behaviors

Select relationship behaviors based on business requirements, not technical convenience.

Relationship Behavior Selection

// For optional relationships (customer notes)
_multiUoW.AddRelationship("Customer", "Note", "CustomerID", "CustomerID", 
                         RelationshipBehavior.SetNull, false);

// For required audit trails (prevent deletion if history exists)
_multiUoW.AddRelationship("Order", "OrderHistory", "OrderID", "OrderID", 
                         RelationshipBehavior.Restrict, true);

// For true parent-child relationships (invoice lines)
_multiUoW.AddRelationship("Invoice", "InvoiceLine", "InvoiceID", "InvoiceID", 
                         RelationshipBehavior.CascadeDelete, true);

// For lookup relationships (don't delete customer if orders exist)
_multiUoW.AddRelationship("Customer", "Order", "CustomerID", "CustomerID", 
                         RelationshipBehavior.Restrict, false);

Performance Optimization

πŸ” Use Filtering at Database Level

Apply filters at the database level rather than in memory to improve performance.

Efficient Filtering

// Good - Database filtering
public async Task<List<Customer>> GetActiveCustomersAsync(DateTime since)
{
    var filters = new List<AppFilter>
    {
        new AppFilter {FieldName = "IsActive", Operator = "=", FilterValue = true },
        new AppFilter {FieldName = "LastOrderDate", Operator = ">=", FilterValue = since },
        new AppFilter {FieldName = "CustomerType", Operator = "IN", FilterValue = new[] { "Premium", "Gold" } }
    };
    
    var result = await _customerUoW.Get(filters);
    return result.ToList();
}

// Bad - In-memory filtering (loads all customers first)
public async Task<List<Customer>> GetActiveCustomersSlow(DateTime since)
{
    var allCustomers = await _customerUoW.Get(); // Loads everything!
    
    return allCustomers
        .Where(c => c.IsActive && 
                   c.LastOrderDate >= since && 
                   (c.CustomerType == "Premium" || c.CustomerType == "Gold"))
        .ToList();
}
πŸ“¦ Batch Operations When Possible

For large datasets, process data in batches to avoid memory issues.

Batch Processing

public async Task ProcessLargeDatasetAsync(IEnumerable<ImportData> data, 
                                                   IProgress<int> progress = null)
{
    const int batchSize = 1000;
    var batch = new List<Customer>();
    var totalProcessed = 0;
    
    foreach (var item in data)
    {
        var customer = CreateCustomerFromImport(item);
        batch.Add(customer);
        
        if (batch.Count >= batchSize)
        {
            await ProcessBatchAsync(batch);
            
            totalProcessed += batch.Count;
            progress?.Report(totalProcessed);
            
            batch.Clear();
            
            // Optional: Allow for cancellation and cleanup
            if (_cancellationToken.IsCancellationRequested)
            {
                break;
            }
            
            // Optional: Brief pause to prevent overwhelming the database
            await Task.Delay(10, _cancellationToken);
        }
    }
    
    // Process remaining items
    if (batch.Any())
    {
        await ProcessBatchAsync(batch);
        totalProcessed += batch.Count;
        progress?.Report(totalProcessed);
    }
}

private async Task ProcessBatchAsync(List<Customer> batch)
{
    try
    {
        foreach (var customer in batch)
        {
            await _customerUoW.InsertAsync(customer);
        }
        
        await _customerUoW.CommitAsync();
    }
    catch
    {
        await _customerUoW.Rollback();
        throw;
    }
}
πŸ“Š Use Progress Reporting for Long Operations

Implement progress reporting for operations that might take significant time.

Progress Reporting

public async Task ImportDataWithProgressAsync(List<ImportData> data)
{
    var progress = new Progress<PassedArgs>(args => 
    {
        // Update progress bar
        progressBar.Value = args.ParameterInt1;
        progressBar.Maximum = args.ParameterInt2;
        
        // Update status label
        lblStatus.Text = args.ParameterString1;
        
        // Allow UI to refresh
        Application.DoEvents();
    });

    var cancellationTokenSource = new CancellationTokenSource();
    btnCancel.Click += (s, e) => cancellationTokenSource.Cancel();

    try
    {
        var result = await _customerUoW.Commit(progress, cancellationTokenSource.Token);
        
        if (result.Flag == Errors.Ok)
        {
            MessageBox.Show("Import completed successfully!");
        }
    }
    catch (OperationCanceledException)
    {
        MessageBox.Show("Import was cancelled.");
        await _customerUoU.Rollback();
    }
}

Error Handling Best Practices

πŸ›‘οΈ Implement Comprehensive Error Handling

Handle different types of errors appropriately and provide meaningful feedback.

Comprehensive Error Handling

public async Task<ServiceResult> CreateCustomerAsync(CustomerData data)
{
    try
    {
        var customer = MapToEntity(data);
        
        // Pre-validation
        ValidateCustomerData(customer);
        
        await _customerUoW.InsertAsync(customer);
        var result = await _customerUoW.CommitAsync();
        
        if (result.Flag == Errors.Ok)
        {
            _logger.LogInformation($"Customer created successfully: {customer.CustomerID}");
            return ServiceResult.Success(customer.CustomerID);
        }
        else
        {
            _logger.LogWarning($"Database error creating customer: {result.Message}");
            return ServiceResult.Failure($"Database error: {result.Message}");
        }
    }
    catch (ValidationException ex)
    {
        _logger.LogWarning($"Validation error creating customer: {ex.Message}");
        return ServiceResult.ValidationError(ex.Message);
    }
    catch (SqlException ex) when (ex.Number == 2) // Timeout
    {
        _logger.LogError(ex, "Database timeout creating customer");
        return ServiceResult.Failure("Database timeout. Please try again later.");
    }
    catch (SqlException ex) when (ex.Number == 2627) // Duplicate key
    {
        _logger.LogWarning($"Duplicate customer creation attempt: {ex.Message}");
        return ServiceResult.Failure("A customer with this information already exists.");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error creating customer");
        return ServiceResult.Failure("An unexpected error occurred. Please contact support.");
    }
}
πŸ“ Log Important Operations

Enable logging for audit trails and debugging purposes.

Audit Logging

public void ConfigureLogging()
{
    // Enable logging
    _customerUoW.IsLogging = true;
    
    // Setup periodic log saving
    _logTimer = new Timer(SaveLogsToFile, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}

private void SaveLogsToFile(object state)
{
    try
    {
        var logPath = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
            "MyApp", "Logs", $"customer-changes-{DateTime.Now:yyyyMMdd}.json");
        
        Directory.CreateDirectory(Path.GetDirectoryName(logPath));
        
        _customerUoW.SaveLog(logPath);
        
        _logger.LogDebug($"Change log saved to {logPath}");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed to save change log");
    }
}

Testing Strategies

πŸ§ͺ Use List Mode for Unit Testing

Use list mode for unit testing to avoid database dependencies.

Unit Testing with List Mode

[TestFixture]
public class CustomerUnitOfWorkTests
{
    private UnitofWork<Customer> _uow;
    private ObservableBindingList<Customer> _testData;
    private Mock<IDMEEditor> _mockDMEEditor;

    [SetUp]
    public void Setup()
    {
        _mockDMEEditor = new Mock<IDMEEditor>();
        _testData = new ObservableBindingList<Customer>();
        
        // Create UnitOfWork in list mode (no database)
        _uow = new UnitofWork<Customer>(_mockDMEEditor.Object, true, _testData, "CustomerID");
    }

    [Test]
    public void Should_Track_New_Entity_As_Added()
    {
        // Arrange & Act
        _uow.New();
        var customer = _uow.CurrentItem;
        customer.Name = "Test Customer";
        customer.Email = "test@example.com";
        
        // Assert
        Assert.IsTrue(_uow.IsDirty);
        Assert.AreEqual(1, _uow.GetAddedEntities().Count());
        Assert.AreEqual("Test Customer", customer.Name);
    }

    [Test]
    public void Should_Track_Modified_Entity()
    {
        // Arrange
        var existingCustomer = new Customer { CustomerID = 1, Name = "Original Name" };
        _testData.Add(existingCustomer);
        _uow.MoveTo(0);
        
        // Act
        _uow.CurrentItem.Name = "Modified Name";
        
        // Assert
        Assert.IsTrue(_uow.IsDirty);
        Assert.AreEqual(1, _uow.GetModifiedEntities().Count());
    }

    [Test]
    public void Should_Track_Deleted_Entity()
    {
        // Arrange
        var customerToDelete = new Customer { CustomerID = 1, Name = "To Delete" };
        _testData.Add(customerToDelete);
        
        // Act
        _uow.Delete(customerToDelete);
        
        // Assert
        Assert.IsTrue(_uow.IsDirty);
        Assert.AreEqual(1, _uow.GetDeletedEntities().Count());
        Assert.AreEqual(0, _testData.Count);
    }

    [TearDown]
    public void TearDown()
    {
        _uow?.Dispose();
    }
}
🎭 Mock Dependencies

Mock IDMEEditor and IDataSource for isolated unit testing.

Mocking Dependencies

[TestFixture]
public class CustomerServiceTests
{
    private CustomerService _customerService;
    private Mock<IDMEEditor> _mockDMEEditor;
    private Mock<IDataSource> _mockDataSource;
    private Mock<ILogger> _mockLogger;

    [SetUp]
    public void Setup()
    {
        _mockDMEEditor = new Mock<IDMEEditor>();
        _mockDataSource = new Mock<IDataSource>();
        _mockLogger = new Mock<ILogger>();
        
        // Setup mock returns
        _mockDMEEditor.Setup(x => x.GetDataSource(It.IsAny<string>()))
                      .Returns(_mockDataSource.Object);
        
        _mockDataSource.Setup(x => x.GetEntity(It.IsAny<string>(), It.IsAny<List<AppFilter>>()))
                       .Returns(new List<Customer>());
        
        _mockDataSource.Setup(x => x.InsertEntity(It.IsAny<string>(), It.IsAny<object>()))
                       .Returns(new ErrorsInfo { Flag = Errors.Ok });
        
        _customerService = new CustomerService(_mockDMEEditor.Object, _mockLogger.Object);
    }

    [Test]
    public async Task Should_Create_Customer_Successfully()
    {
        // Arrange
        var customer = new Customer { Name = "Test Customer", Email = "test@example.com" };
        
        // Act
        var result = await _customerService.CreateCustomerAsync(customer);
        
        // Assert
        Assert.IsTrue(result.IsSuccess);
        _mockDataSource.Verify(x => x.InsertEntity("Customers", It.IsAny<object>()), Times.Once);
    }

    [Test]
    public async Task Should_Handle_Validation_Errors()
    {
        // Arrange
        var invalidCustomer = new Customer { Name = "", Email = "invalid-email" };
        
        // Act & Assert
        var exception = await Assert.ThrowsAsync<ValidationException>(
            () => _customerService.CreateCustomerAsync(invalidCustomer));
        
        Assert.That(exception.Message, Contains.Substring("Name is required"));
    }
}

Security Guidelines

πŸ”’ Validate Input Data

Always validate input data before processing to prevent injection attacks and data corruption.

Input Validation

private void ValidateCustomerInput(Customer customer)
{
    // Required field validation
    if (string.IsNullOrWhiteSpace(customer.Name))
        throw new ValidationException("Name is required");
    
    // Length validation
    if (customer.Name.Length > 100)
        throw new ValidationException("Name cannot exceed 100 characters");
    
    // Format validation
    if (!string.IsNullOrEmpty(customer.Email) && !IsValidEmail(customer.Email))
        throw new ValidationException("Invalid email format");
    
    // SQL injection prevention (if building dynamic queries)
    if (ContainsSqlInjectionPatterns(customer.Name))
        throw new ValidationException("Invalid characters detected");
    
    // XSS prevention
    customer.Name = HttpUtility.HtmlEncode(customer.Name?.Trim());
    customer.Email = customer.Email?.Trim().ToLowerInvariant();
}

private bool IsValidEmail(string email)
{
    return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}

private bool ContainsSqlInjectionPatterns(string input)
{
    var patterns = new[] { "--", ";", "/*", "*/", "xp_", "sp_", "exec", "execute" };
    return patterns.Any(pattern => input.ToLower().Contains(pattern));
}
πŸ›‘οΈ Implement Authorization Checks

Check user permissions before allowing data operations.

Authorization Implementation

private void CheckPermissions(string operation, Customer customer)
{
    var currentUser = _securityService.GetCurrentUser();
    
    // Check general permissions
    if (!currentUser.HasPermission($"Customer.{operation}"))
    {
        throw new UnauthorizedAccessException($"User not authorized for {operation} operation");
    }
    
    // Check record-level security
    switch (operation)
    {
        case "Delete":
            if (customer.CreatedBy != currentUser.UserId && !currentUser.HasRole("Admin"))
            {
                throw new UnauthorizedAccessException("Can only delete own records");
            }
            break;
            
        case "Update":
            if (customer.IsLocked && !currentUser.HasRole("Admin"))
            {
                throw new UnauthorizedAccessException("Cannot modify locked records");
            }
            break;
            
        case "View":
            if (customer.IsConfidential && !currentUser.HasPermission("Customer.ViewConfidential"))
            {
                throw new UnauthorizedAccessException("Cannot view confidential records");
            }
            break;
    }
    
    // Log access for audit
    _auditService.LogAccess(currentUser.UserId, operation, customer.CustomerID);
}

Common Anti-Patterns to Avoid

❌ Don't Create UnitOfWork in Loops

Creating new UnitOfWork instances in loops is inefficient and can cause resource issues.

Loop Anti-Pattern

// BAD - Don't do this
public async Task ImportCustomersBadAsync(List<ImportData> importData)
{
    foreach (var customerData in importData)
    {
        // Creates new connection each time - VERY INEFFICIENT
        using (var uow = new UnitofWork<Customer>(dmeEditor, "SqlServer", "Customers", "CustomerID"))
        {
            await uow.InsertAsync(CreateCustomer(customerData));
            await uow.CommitAsync();  // Individual commits - slow and not transactional
        }
    }
}

// GOOD - Reuse UnitOfWork
public async Task ImportCustomersGoodAsync(List<ImportData> importData)
{
    using (var uow = new UnitofWork<Customer>(dmeEditor, "SqlServer", "Customers", "CustomerID"))
    {
        foreach (var customerData in importData)
        {
            var customer = CreateCustomer(customerData);
            await uow.InsertAsync(customer);
        }
        
        // Single commit for all operations - faster and transactional
        await uow.CommitAsync();
    }
}
❌ Don't Ignore Event Cancellation

Always respect the Cancel flag in event handlers.

Event Cancellation Anti-Pattern

// BAD - Ignoring cancellation
private void OnPreInsert_Bad(object sender, UnitofWorkParams args)
{
    var customer = args.CurrentEntity as Customer;
    if (customer.Email == null)
    {
        args.Cancel = true;  // Set cancel but continue processing
        customer.Email = "unknown@example.com";  // This shouldn't happen after cancel
        
        // Continue with other operations that should be skipped
        ValidateOtherFields(customer);
    }
}

// GOOD - Respect cancellation
private void OnPreInsert_Good(object sender, UnitofWorkParams args)
{
    var customer = args.CurrentEntity as Customer;
    
    if (customer.Email == null)
    {
        args.Cancel = true;
        args.ErrorMessage = "Email is required";
        return;  // Stop processing immediately
    }
    
    // Only continue if not cancelled
    ValidateOtherFields(customer);
}
❌ Don't Mix Synchronous and Asynchronous Calls

Mixing sync and async calls can lead to deadlocks and performance issues.

Async/Sync Mixing Anti-Pattern

// BAD - Blocking async calls (can cause deadlocks)
public void ProcessCustomer_Bad(Customer customer)
{
    try
    {
        _customerUoW.InsertAsync(customer).Wait();  // DANGEROUS!
        var result = _customerUoW.CommitAsync().Result;  // DANGEROUS!
        
        if (result.Flag == Errors.Ok)
        {
            UpdateUI();  // May never be reached due to deadlock
        }
    }
    catch (AggregateException ex)
    {
        // Have to unwrap aggregate exceptions
        HandleException(ex.InnerException);
    }
}

// GOOD - Async all the way
public async Task ProcessCustomer_Good(Customer customer)
{
    try
    {
        await _customerUoW.InsertAsync(customer);
        var result = await _customerUoW.CommitAsync();
        
        if (result.Flag == Errors.Ok)
        {
            await UpdateUIAsync();
        }
    }
    catch (Exception ex)
    {
        // Direct exception handling
        HandleException(ex);
    }
}

Deployment and Production Checklist

πŸ“‹ Pre-Deployment Checklist

Ensure these items are verified before deploying to production:

πŸ”§ Configuration

  • βœ… Connection timeouts configured appropriately
  • βœ… Batch sizes optimized for data volume
  • βœ… Relationship behaviors documented
  • βœ… Environment-specific settings applied

πŸ“ Logging & Monitoring

  • βœ… Logging levels configured correctly
  • βœ… Error alerting system in place
  • βœ… Performance monitoring enabled
  • βœ… Audit trail configuration verified

πŸ§ͺ Testing

  • βœ… Load testing completed
  • βœ… Rollback scenarios tested
  • βœ… Error handling verified
  • βœ… Resource disposal confirmed

πŸ”’ Security

  • βœ… Input validation implemented
  • βœ… Authorization checks in place
  • βœ… SQL injection prevention verified
  • βœ… Sensitive data protection confirmed

πŸ₯ Health Checks

  • βœ… Data source health monitoring
  • βœ… Connection pool monitoring
  • βœ… Memory usage tracking
  • βœ… Performance baseline established

πŸ“š Documentation

  • βœ… Support procedures documented
  • βœ… Common error scenarios documented
  • βœ… Backup/recovery procedures ready
  • βœ… Troubleshooting guide available
πŸ’‘ Remember

These best practices are guidelines based on common scenarios. Always adapt them to your specific requirements and constraints. Regular code reviews and performance testing will help ensure your implementation remains optimal as your application evolves.