Rapid.GenericRepositoryPattern 2.0.2

dotnet add package Rapid.GenericRepositoryPattern --version 2.0.2                
NuGet\Install-Package Rapid.GenericRepositoryPattern -Version 2.0.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rapid.GenericRepositoryPattern" Version="2.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Rapid.GenericRepositoryPattern --version 2.0.2                
#r "nuget: Rapid.GenericRepositoryPattern, 2.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Rapid.GenericRepositoryPattern as a Cake Addin
#addin nuget:?package=Rapid.GenericRepositoryPattern&version=2.0.2

// Install Rapid.GenericRepositoryPattern as a Cake Tool
#tool nuget:?package=Rapid.GenericRepositoryPattern&version=2.0.2                

Rapid.GenericRepositoryPattern

A comprehensive Generic Repository Pattern implementation for .NET applications, providing a robust and flexible data access layer with advanced features including async operations, specifications pattern, and EF Core integration.

📋 Table of Contents

📚 Example Scenarios

1. Basic Entity Setup

The foundation of the repository pattern starts with well-defined entities. Here's a complete example of entity setup:

// Entity Definition
public class Product : IEntity<int>
{
    public int Id { get; set; }                    // Primary key
    public string Name { get; set; }               // Product name
    public decimal Price { get; set; }             // Product price
    public string Description { get; set; }        // Product description
    public bool IsActive { get; set; }             // Product status
    public DateTime CreatedAt { get; set; }        // Creation timestamp
    public DateTime? UpdatedAt { get; set; }       // Last update timestamp
    public int CategoryId { get; set; }            // Foreign key to Category
    public virtual Category Category { get; set; } // Navigation property
}

public class Category : IEntity<int>
{
    public int Id { get; set; }                    // Primary key
    public string Name { get; set; }               // Category name
    // Navigation property for related products
    public virtual ICollection<Product> Products { get; set; }
}

Key Points:

  • Entities implement IEntity<TKey> interface for type-safe primary keys
  • Virtual properties enable lazy loading in EF Core
  • Navigation properties define relationships between entities
  • Nullable UpdatedAt allows tracking modifications
  • IsActive flag supports soft-delete functionality

2. Repository Registration

Configure the repository pattern in your application's startup:

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddRapidRepository<AppDbContext>(options => 
    {
        // Enable caching for better performance
        options.EnableCaching = true;
        options.CacheTimeout = TimeSpan.FromMinutes(30);
        
        // Enable audit logging for tracking changes
        options.EnableAuditLogging = true;
        
        // Enable specification pattern for complex queries
        options.UseSpecificationPattern = true;
        
        // Enable soft delete instead of physical deletion
        options.EnableSoftDelete = true;
        
        // Enable change tracking for better performance
        options.TrackChanges = true;
        
        // Enable security filters for row-level security
        options.EnableSecurityFilters = true;
    });
}

Configuration Options Explained:

  • EnableCaching: Caches query results to improve performance
  • CacheTimeout: Sets how long items remain in cache
  • EnableAuditLogging: Tracks all changes to entities
  • UseSpecificationPattern: Enables complex query specifications
  • EnableSoftDelete: Implements logical deletion instead of physical
  • TrackChanges: Controls EF Core change tracking
  • EnableSecurityFilters: Implements row-level security

3. Basic CRUD Operations

Create Operations
// Single Entity Creation
public async Task<Product> CreateProductAsync(Product product)
{
    // Add new product to repository
    await _repository.AddAsync(product);
    // Persist changes to database
    await _unitOfWork.SaveChangesAsync();
    return product;
}

// Multiple Entity Creation
public async Task<IEnumerable<Product>> CreateProductsAsync(IEnumerable<Product> products)
{
    // Efficiently add multiple products at once
    await _repository.AddRangeAsync(products);
    // Single SaveChanges for better performance
    await _unitOfWork.SaveChangesAsync();
    return products;
}

Key Points:

  • Use AddAsync for single entity creation
  • Use AddRangeAsync for bulk insertions
  • Always call SaveChangesAsync to persist changes
  • Unit of Work ensures transaction consistency
Read Operations
// Get By Id - Retrieve single entity by primary key
var product = await _repository.GetByIdAsync(1);

// Get All - Retrieve all entities
var allProducts = await _repository.ListAsync();

// Get with Conditions - Filter active products
var activeProducts = await _repository.ListAsync(p => p.IsActive);

// Get with Include - Eager load related data
var productsWithCategory = await _repository.ListAsync(
    predicate: p => p.IsActive,
    include: p => p.Include(x => x.Category)
);

// Get First or Default - Get single entity matching criteria
var firstProduct = await _repository.FirstOrDefaultAsync(
    p => p.Price > 100,
    orderBy: q => q.OrderByDescending(x => x.CreatedAt)
);

// Pagination - Get data in pages
var pagedProducts = await _repository.ListAsync(
    predicate: p => p.IsActive,
    orderBy: q => q.OrderByDescending(x => x.CreatedAt),
    skip: (page - 1) * pageSize,
    take: pageSize
);

Query Operations Explained:

  • GetByIdAsync: Efficient single entity retrieval
  • ListAsync: Flexible querying with optional predicates
  • Include: Eager loading of related entities
  • FirstOrDefaultAsync: Single result with ordering
  • Pagination: Skip and take for efficient data loading
Update Operations
// Single Entity Update - Update all properties
public async Task UpdateProductAsync(Product product)
{
    // Update entity in repository
    await _repository.UpdateAsync(product);
    // Persist changes
    await _unitOfWork.SaveChangesAsync();
}

// Partial Update - Update specific properties
public async Task UpdateProductPriceAsync(int productId, decimal newPrice)
{
    // Get existing product
    var product = await _repository.GetByIdAsync(productId);
    // Update only required properties
    product.Price = newPrice;
    product.UpdatedAt = DateTime.UtcNow;
    // Save changes
    await _repository.UpdateAsync(product);
    await _unitOfWork.SaveChangesAsync();
}

Update Strategies:

  • Full entity update when all properties change
  • Partial update for specific property changes
  • Automatic update timestamp tracking
  • Optimistic concurrency handling
Delete Operations
// Hard Delete - Physical removal
public async Task DeleteProductAsync(int productId)
{
    var product = await _repository.GetByIdAsync(productId);
    await _repository.DeleteAsync(product);
    await _unitOfWork.SaveChangesAsync();
}

// Soft Delete - Logical removal
public async Task SoftDeleteProductAsync(int productId)
{
    // Marks entity as deleted without physical removal
    await _repository.SoftDeleteAsync(productId);
    await _unitOfWork.SaveChangesAsync();
}

Deletion Strategies:

  • Hard Delete: Physical removal from database
  • Soft Delete: Logical deletion preserving data
  • Cascading deletion handling
  • Transaction safety

4. Advanced Queries

Complex Queries
// Complex Query with Multiple Conditions
var products = await _repository.ListAsync(
    // Multiple conditions combined
    p => p.IsActive && p.Price > 100 && p.Category.Name == "Electronics",
    // Multiple includes for related data
    include: q => q.Include(x => x.Category)
                   .Include(x => x.Supplier),
    // Complex ordering
    orderBy: q => q.OrderByDescending(x => x.CreatedAt)
                   .ThenBy(x => x.Name)
);

// Dynamic Filtering - Build queries dynamically
public async Task<IEnumerable<Product>> FilterProductsAsync(
    string searchTerm = null,
    decimal? minPrice = null,
    decimal? maxPrice = null,
    string category = null,
    bool? isActive = null)
{
    // Start with base query
    var query = _repository.Query();

    // Add conditions dynamically
    if (!string.IsNullOrEmpty(searchTerm))
        query = query.Where(p => p.Name.Contains(searchTerm) || 
                                p.Description.Contains(searchTerm));

    if (minPrice.HasValue)
        query = query.Where(p => p.Price >= minPrice.Value);

    if (maxPrice.HasValue)
        query = query.Where(p => p.Price <= maxPrice.Value);

    if (!string.IsNullOrEmpty(category))
        query = query.Where(p => p.Category.Name == category);

    if (isActive.HasValue)
        query = query.Where(p => p.IsActive == isActive.Value);

    return await query.ToListAsync();
}

Advanced Query Features:

  • Multiple conditions with logical operators
  • Multiple includes for related data
  • Complex ordering with multiple fields
  • Dynamic query building
  • Flexible parameter handling

5. Specification Pattern

Basic Specification
// Simple specification for active products
public class ActiveProductsSpecification : Specification<Product>
{
    public ActiveProductsSpecification()
    {
        // Define base query criteria
        Query.Where(p => p.IsActive);
    }
}

// Usage
var activeProducts = await _repository.ListAsync(new ActiveProductsSpecification());
Complex Specification
// Specification with parameters and complex logic
public class DiscountedProductsSpecification : Specification<Product>
{
    public DiscountedProductsSpecification(decimal maxPrice, string category)
    {
        Query
            .Where(p => p.IsActive && p.Price <= maxPrice)
            .Include(p => p.Category)
            .Where(p => p.Category.Name == category)
            .OrderByDescending(p => p.UpdatedAt);
    }
}

// Composite specification for featured products
public class FeaturedProductsSpecification : Specification<Product>
{
    public FeaturedProductsSpecification()
    {
        Query
            .Where(p => p.IsActive && p.IsFeatured)
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .OrderByDescending(p => p.Rating);
    }
}

// Combining specifications
var specification = new ActiveProductsSpecification()
    .And(new DiscountedProductsSpecification(100, "Electronics"));
var products = await _repository.ListAsync(specification);

Specification Pattern Benefits:

  • Encapsulate query logic
  • Reusable query components
  • Combine specifications
  • Clean and maintainable code
  • Type-safe queries

6. Transaction Management

Basic Transaction
// Example of handling product category transfer with transaction
public async Task<bool> TransferProductCategoryAsync(int productId, int newCategoryId)
{
    // Begin transaction
    using (var transaction = await _unitOfWork.BeginTransactionAsync())
    {
        try
        {
            // Update product category
            var product = await _repository.GetByIdAsync(productId);
            product.CategoryId = newCategoryId;
            await _repository.UpdateAsync(product);
            
            // Update category product count
            var category = await _categoryRepository.GetByIdAsync(newCategoryId);
            category.ProductCount++;
            await _categoryRepository.UpdateAsync(category);
            
            // Save all changes and commit transaction
            await _unitOfWork.SaveChangesAsync();
            await transaction.CommitAsync();
            return true;
        }
        catch
        {
            // Rollback on any error
            await transaction.RollbackAsync();
            throw;
        }
    }
}

Transaction Features:

  • Atomic operations
  • Automatic rollback on errors
  • Consistent data state
  • Multiple repository operations
  • ACID compliance
Distributed Transaction
// Example of processing an order with distributed transaction
public async Task<bool> ProcessOrderAsync(Order order)
{
    // Create distributed transaction scope
    using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        try
        {
            // 1. Update inventory across multiple products
            foreach (var item in order.Items)
            {
                var product = await _productRepository.GetByIdAsync(item.ProductId);
                product.StockQuantity -= item.Quantity;
                await _productRepository.UpdateAsync(product);
            }

            // 2. Create new order record
            await _orderRepository.AddAsync(order);
            
            // 3. Update customer statistics
            var customer = await _customerRepository.GetByIdAsync(order.CustomerId);
            customer.TotalOrders++;
            customer.LastOrderDate = DateTime.UtcNow;
            await _customerRepository.UpdateAsync(customer);

            // 4. Save all changes and complete transaction
            await _unitOfWork.SaveChangesAsync();
            scope.Complete();
            return true;
        }
        catch
        {
            // Transaction automatically rolls back if scope is not completed
            throw;
        }
    }
}

Distributed Transaction Benefits:

  • Coordinate multiple operations
  • Cross-database transactions
  • Automatic rollback
  • Consistent state across systems
  • Resource management

7. Caching Scenarios

Basic Caching
// Method-level caching with duration
[Cached(Duration = 300)] // Cache for 5 minutes
public async Task<Product> GetProductWithCachingAsync(int id)
{
    // Result will be cached for 5 minutes
    return await _repository.GetByIdAsync(id);
}

// Custom cache key generation
[Cached(Duration = 300, Key = "product-{0}")]
public async Task<Product> GetProductWithCustomKeyAsync(int id)
{
    // Uses custom key format: "product-123"
    return await _repository.GetByIdAsync(id);
}

Basic Caching Features:

  • Duration-based caching
  • Custom cache keys
  • Method-level control
  • Automatic cache management
  • Memory-efficient storage
Advanced Caching
// Cache with dependencies
[Cached(Duration = 300, Dependencies = new[] { "Products", "Categories" })]
public async Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId)
{
    // Result cached with category dependencies
    return await _repository.ListAsync(p => p.CategoryId == categoryId);
}

// Manual cache invalidation
public async Task UpdateProductWithCacheInvalidationAsync(Product product)
{
    // Update product
    await _repository.UpdateAsync(product);
    await _unitOfWork.SaveChangesAsync();
    
    // Invalidate specific cache entry
    await _cacheManager.InvalidateAsync($"product-{product.Id}");
    
    // Invalidate related caches
    await _cacheManager.InvalidateByTagAsync("Products");
}

Advanced Caching Features:

  • Cache dependencies
  • Tag-based invalidation
  • Selective cache clearing
  • Cache synchronization
  • Memory optimization

8. Bulk Operations

Basic Bulk Operations
// Bulk insert multiple entities efficiently
public async Task BulkInsertProductsAsync(IEnumerable<Product> products)
{
    // Efficiently insert large number of entities
    await _repository.BulkInsertAsync(products);
}

// Bulk update multiple entities
public async Task BulkUpdateProductsAsync(IEnumerable<Product> products)
{
    // Update multiple entities in a single operation
    await _repository.BulkUpdateAsync(products);
}

// Bulk delete by IDs
public async Task BulkDeleteProductsAsync(IEnumerable<int> productIds)
{
    // Delete multiple entities by their IDs
    await _repository.BulkDeleteAsync(productIds);
}

Basic Bulk Operation Features:

  • Efficient batch processing
  • Minimal database roundtrips
  • Memory optimization
  • Transaction support
  • Error handling
Advanced Bulk Operations
// Bulk upsert with matching
public async Task BulkUpsertProductsAsync(IEnumerable<Product> products)
{
    // Perform insert or update based on SKU match
    await _repository.BulkUpsertAsync(
        products,
        p => p.SKU, // Match on SKU
        updateOnMatch: true
    );
}

// Bulk operations with validation and options
public async Task BulkInsertWithValidationAsync(IEnumerable<Product> products)
{
    // Filter valid products
    var validProducts = products.Where(p => ValidateProduct(p));
    
    // Insert with specific options
    await _repository.BulkInsertAsync(
        validProducts,
        options => {
            options.BatchSize = 1000;        // Control batch size
            options.EnableAudit = true;      // Enable audit logging
        }
    );
}

Advanced Bulk Operation Features:

  • Custom matching logic
  • Validation support
  • Batch size control
  • Audit logging
  • Performance optimization

9. Security Implementation

Row-Level Security
// Repository with row-level security
public class SecureProductRepository : GenericRepository<Product>
{
    private readonly IUserContext _userContext;

    public SecureProductRepository(DbContext context, IUserContext userContext) 
        : base(context)
    {
        _userContext = userContext;
    }

    // Override query to apply security filters
    public override IQueryable<Product> Query()
    {
        var query = base.Query();
        
        // Apply organization-level security
        if (!_userContext.IsAdmin)
        {
            query = query.Where(p => p.OrganizationId == _userContext.OrganizationId);
        }

        return query;
    }
}

Row-Level Security Features:

  • Automatic security filtering
  • User context awareness
  • Organization isolation
  • Role-based access
  • Query-level security
Audit Logging
// Repository with audit logging
public class AuditedProductRepository : GenericRepository<Product>
{
    private readonly IAuditLogger _auditLogger;

    // Override update to include audit logging
    public async override Task<Product> UpdateAsync(Product entity)
    {
        // Get original state
        var original = await GetByIdAsync(entity.Id);
        
        // Perform update
        var result = await base.UpdateAsync(entity);

        // Log the change
        await _auditLogger.LogAsync(new AuditLog
        {
            EntityType = typeof(Product).Name,
            EntityId = entity.Id,
            Action = "Update",
            UserId = _userContext.UserId,
            OriginalValues = JsonSerializer.Serialize(original),
            NewValues = JsonSerializer.Serialize(entity),
            Timestamp = DateTime.UtcNow
        });

        return result;
    }
}

Audit Logging Features:

  • Change tracking
  • User attribution
  • Timestamp recording
  • Value comparison
  • Serialization support

10. Monitoring and Diagnostics

Performance Monitoring
// Repository with performance monitoring
public class MonitoredRepository<T> : GenericRepository<T> where T : class, IEntity
{
    private readonly IMetricsCollector _metrics;

    // Monitor individual operations
    public override async Task<T> GetByIdAsync(object id)
    {
        // Measure operation duration
        using (_metrics.MeasureOperation($"Repository.GetById.{typeof(T).Name}"))
        {
            return await base.GetByIdAsync(id);
        }
    }

    // Monitor query performance
    public override async Task<IEnumerable<T>> ListAsync(
        Expression<Func<T, bool>> predicate = null)
    {
        var timer = _metrics.StartTimer();
        try
        {
            var result = await base.ListAsync(predicate);
            // Record metrics
            _metrics.RecordQueryMetrics(
                typeof(T).Name,
                "List",
                timer.Elapsed,
                result.Count()
            );
            return result;
        }
        catch (Exception ex)
        {
            // Record errors
            _metrics.RecordError(typeof(T).Name, "List", ex);
            throw;
        }
    }
}

Performance Monitoring Features:

  • Operation timing
  • Query metrics
  • Error tracking
  • Resource usage monitoring
  • Performance analytics
Health Checks
// Repository health monitoring
public class RepositoryHealthCheck : IHealthCheck
{
    private readonly IRepository<Product> _repository;

    // Implement health check logic
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Verify repository functionality
            await _repository.FirstOrDefaultAsync();
            return HealthCheckResult.Healthy();
        }
        catch (Exception ex)
        {
            // Report unhealthy state with details
            return HealthCheckResult.Unhealthy(
                "Repository health check failed",
                ex
            );
        }
    }
}

Health Check Features:

  • Connection verification
  • Operation testing
  • Error reporting
  • Status monitoring
  • Diagnostic details

11. Custom Repository Implementations

Custom Query Methods
// Define custom repository interface
public interface IProductRepository : IRepository<Product>
{
    // Get top selling products
    Task<IEnumerable<Product>> GetBestSellersAsync(int count);
    
    // Get inventory levels for multiple products
    Task<IDictionary<int, int>> GetProductInventoryAsync(IEnumerable<int> productIds);
    
    // Calculate average price in category
    Task<decimal> CalculateAverageProductPriceAsync(int categoryId);
}

// Implement custom repository
public class ProductRepository : GenericRepository<Product>, IProductRepository
{
    // Get best selling products with count limit
    public async Task<IEnumerable<Product>> GetBestSellersAsync(int count)
    {
        return await Query()
            .Where(p => p.IsActive)           // Only active products
            .OrderByDescending(p => p.SalesCount)  // Sort by sales
            .Take(count)                      // Limit results
            .ToListAsync();
    }

    // Get inventory levels efficiently
    public async Task<IDictionary<int, int>> GetProductInventoryAsync(
        IEnumerable<int> productIds)
    {
        return await Query()
            .Where(p => productIds.Contains(p.Id))  // Filter by IDs
            .ToDictionaryAsync(                     // Convert to dictionary
                p => p.Id,                         // Key is product ID
                p => p.StockQuantity              // Value is stock level
            );
    }

    // Calculate category average price
    public async Task<decimal> CalculateAverageProductPriceAsync(int categoryId)
    {
        return await Query()
            .Where(p => p.CategoryId == categoryId)  // Filter by category
            .AverageAsync(p => p.Price);            // Calculate average
    }
}

Custom Query Features:

  • Domain-specific methods
  • Optimized queries
  • Business logic encapsulation
  • Type-safe operations
  • Reusable components
Domain-Specific Repository
// Order-specific repository implementation
public class OrderRepository : GenericRepository<Order>, IOrderRepository
{
    // Get all pending orders with related data
    public async Task<IEnumerable<Order>> GetPendingOrdersAsync()
    {
        return await Query()
            // Filter pending orders
            .Where(o => o.Status == OrderStatus.Pending)
            // Include order items and their products
            .Include(o => o.Items)
                .ThenInclude(i => i.Product)
            // Include customer details
            .Include(o => o.Customer)
            // Sort by creation date
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync();
    }

    // Get order summary with calculations
    public async Task<OrderSummary> GetOrderSummaryAsync(int orderId)
    {
        // Get order with items
        var order = await Query()
            .Where(o => o.Id == orderId)
            .Include(o => o.Items)
            .FirstOrDefaultAsync();

        // Create summary with calculations
        return new OrderSummary
        {
            OrderId = order.Id,
            TotalAmount = order.Items.Sum(i => i.Quantity * i.UnitPrice),
            ItemCount = order.Items.Count,
            Status = order.Status,
            CreatedAt = order.CreatedAt
        };
    }

    // Update order status with audit
    public async Task<bool> UpdateOrderStatusAsync(
        int orderId,
        OrderStatus newStatus,
        string notes = null)
    {
        // Get order
        var order = await GetByIdAsync(orderId);
        if (order == null) return false;

        // Update status and metadata
        order.Status = newStatus;
        order.StatusNotes = notes;
        order.UpdatedAt = DateTime.UtcNow;

        // Save changes
        await UpdateAsync(order);
        await UnitOfWork.SaveChangesAsync();

        return true;
    }
}

Domain-Specific Features:

  • Business logic implementation
  • Complex data relationships
  • Calculated properties
  • Status management
  • Audit trail support

12. Best Practices and Recommendations

Code Organization
// 1. Use interfaces for dependency injection
public interface IOrderService
{
    Task<Order> CreateOrderAsync(OrderDto orderDto);
    Task<OrderSummary> GetOrderSummaryAsync(int orderId);
}

public class OrderService : IOrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IUnitOfWork _unitOfWork;

    public OrderService(IOrderRepository orderRepository, IUnitOfWork unitOfWork)
    {
        _orderRepository = orderRepository;
        _unitOfWork = unitOfWork;
    }
}

// 2. Use specification classes for complex queries
public class ActiveDiscountedProductsSpecification : Specification<Product>
{
    public ActiveDiscountedProductsSpecification(decimal maxPrice)
    {
        Query
            .Where(p => p.IsActive && p.Price <= maxPrice)
            .OrderByDescending(p => p.UpdatedAt);
    }
}

// 3. Implement repository patterns consistently
public class GenericRepository<T> : IRepository<T> where T : class, IEntity
{
    protected readonly DbContext Context;
    protected readonly DbSet<T> DbSet;

    public GenericRepository(DbContext context)
    {
        Context = context;
        DbSet = context.Set<T>();
    }
}
Performance Optimization
// 1. Use async/await consistently
public async Task<IEnumerable<Product>> GetProductsAsync()
{
    return await _repository.ListAsync();
}

// 2. Implement caching for frequently accessed data
[Cached(Duration = 300)]
public async Task<IEnumerable<Product>> GetFeaturedProductsAsync()
{
    return await _repository.ListAsync(p => p.IsFeatured);
}

// 3. Use projection for specific fields
public async Task<IEnumerable<ProductSummary>> GetProductSummariesAsync()
{
    return await _repository.Query()
        .Select(p => new ProductSummary
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        })
        .ToListAsync();
}
Error Handling
public async Task<Product> UpdateProductAsync(Product product)
{
    try
    {
        // Validate input
        if (product == null)
            throw new ArgumentNullException(nameof(product));

        // Check existence
        var existing = await _repository.GetByIdAsync(product.Id);
        if (existing == null)
            throw new EntityNotFoundException($"Product {product.Id} not found");

        // Perform update within transaction
        using (var transaction = await _unitOfWork.BeginTransactionAsync())
        {
            try
            {
                var result = await _repository.UpdateAsync(product);
                await _unitOfWork.SaveChangesAsync();
                await transaction.CommitAsync();
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                await transaction.RollbackAsync();
                throw new ConcurrencyException("Product was modified by another user", ex);
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                throw new RepositoryException("Error updating product", ex);
            }
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error updating product {ProductId}", product.Id);
        throw;
    }
}

Best Practice Guidelines:

  1. Code Organization

    • Use interfaces for dependency injection
    • Implement repository pattern consistently
    • Separate concerns with specifications
    • Follow SOLID principles
  2. Performance

    • Use async/await consistently
    • Implement caching strategically
    • Use projections for specific data
    • Optimize queries with includes
    • Batch operations when possible
  3. Error Handling

    • Implement proper exception handling
    • Use custom exceptions
    • Log errors appropriately
    • Maintain transaction integrity
    • Handle concurrency issues
  4. Security

    • Implement row-level security
    • Use audit logging
    • Validate input data
    • Handle sensitive data properly
    • Implement proper authentication
  5. Maintenance

    • Write clean, documented code
    • Use meaningful naming
    • Implement proper logging
    • Write unit tests
    • Keep dependencies updated
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.2 110 1/1/2025
2.0.0 92 12/31/2024
1.0.0 2,972 12/16/2024