Rapid.GenericRepositoryPattern
2.0.2
dotnet add package Rapid.GenericRepositoryPattern --version 2.0.2
NuGet\Install-Package Rapid.GenericRepositoryPattern -Version 2.0.2
<PackageReference Include="Rapid.GenericRepositoryPattern" Version="2.0.2" />
paket add Rapid.GenericRepositoryPattern --version 2.0.2
#r "nuget: Rapid.GenericRepositoryPattern, 2.0.2"
// 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
- Features
- Installation
- Quick Start
- Example Scenarios
- Best Practices
- Architecture
- Contributing
- Support
- License
📚 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 performanceCacheTimeout
: Sets how long items remain in cacheEnableAuditLogging
: Tracks all changes to entitiesUseSpecificationPattern
: Enables complex query specificationsEnableSoftDelete
: Implements logical deletion instead of physicalTrackChanges
: Controls EF Core change trackingEnableSecurityFilters
: 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 retrievalListAsync
: Flexible querying with optional predicatesInclude
: Eager loading of related entitiesFirstOrDefaultAsync
: 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:
Code Organization
- Use interfaces for dependency injection
- Implement repository pattern consistently
- Separate concerns with specifications
- Follow SOLID principles
Performance
- Use async/await consistently
- Implement caching strategically
- Use projections for specific data
- Optimize queries with includes
- Batch operations when possible
Error Handling
- Implement proper exception handling
- Use custom exceptions
- Log errors appropriately
- Maintain transaction integrity
- Handle concurrency issues
Security
- Implement row-level security
- Use audit logging
- Validate input data
- Handle sensitive data properly
- Implement proper authentication
Maintenance
- Write clean, documented code
- Use meaningful naming
- Implement proper logging
- Write unit tests
- Keep dependencies updated
Product | Versions 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. |
-
net8.0
- EFCore.BulkExtensions (>= 8.1.2)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Caching.Memory (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.0)
- System.Diagnostics.DiagnosticSource (>= 9.0.0)
- System.Linq.Dynamic.Core (>= 1.5.1)
-
net9.0
- EFCore.BulkExtensions (>= 8.1.2)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Caching.Memory (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.0)
- System.Diagnostics.DiagnosticSource (>= 9.0.0)
- System.Linq.Dynamic.Core (>= 1.5.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.