OElite.Data.Platform 5.0.9-develop.476
OElite.Data.Platform
Platform-specific data access layer for the OElite eCommerce and business platform, providing repositories, data access patterns, and platform-specific data operations built on MongoDB.
Overview
OElite.Data.Platform implements the data access layer for the OElite eCommerce and business platform. Built on top of OElite.Data and OElite.Restme.MongoDb, it provides specialized repositories, query builders, and data access patterns for platform-specific entities like customers, products, orders, and payments. This package ensures efficient, scalable, and consistent data operations across the entire platform.
Features
Platform Repositories
- Customer Repository: Customer management, segmentation, and loyalty data access
- Product Repository: Product catalog, inventory, and pricing data operations
- Order Repository: Order lifecycle, order items, and order status management
- Payment Repository: Payment processing, transactions, and refund data access
- Catalog Repository: Categories, brands, and product taxonomy management
Advanced Query Capabilities
- Complex Queries: Multi-criteria searches with filtering, sorting, and pagination
- Aggregation Pipelines: Business intelligence queries and analytics operations
- Denormalized Field Management: Automatic population and synchronization of denormalized data
- Performance Optimization: Efficient queries with proper indexing strategies
- Cache Integration: Seamless integration with Redis caching layer
Data Access Patterns
- Repository Pattern: Clean separation of data access logic
- Unit of Work: Transaction management and data consistency
- Specification Pattern: Reusable query specifications and business rules
- CQRS Support: Command and query separation for optimal performance
- Event Sourcing: Audit trails and event-driven data changes
Installation
dotnet add package OElite.Data.Platform
Quick Start
Dependency Injection Registration
using OElite.Data.Platform;
using Microsoft.Extensions.DependencyInjection;
// Register platform repositories
services.AddOElitePlatformRepositories(configuration);
// Or register specific repositories
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IPaymentRepository, PaymentRepository>();
Basic Repository Usage
public class CustomerService
{
private readonly ICustomerRepository _customerRepository;
public CustomerService(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public async Task<Customer?> GetCustomerAsync(string customerId)
{
return await _customerRepository.GetByIdAsync(customerId);
}
public async Task<Customer> CreateCustomerAsync(Customer customer)
{
return await _customerRepository.CreateAsync(customer);
}
public async Task<List<Customer>> SearchCustomersAsync(string searchTerm)
{
return await _customerRepository.SearchAsync(searchTerm);
}
}
Core Repositories
Customer Repository
public interface ICustomerRepository : IBaseRepository<Customer>
{
Task<Customer?> GetByEmailAsync(string email);
Task<Customer?> GetByPhoneAsync(string phoneNumber);
Task<List<Customer>> GetBySegmentAsync(string segmentId);
Task<List<Customer>> SearchAsync(string searchTerm, int pageIndex = 0, int pageSize = 20);
Task<bool> ExistsByEmailAsync(string email);
Task<CustomerAnalytics> GetAnalyticsAsync(string customerId);
Task<List<Customer>> GetRecentCustomersAsync(int count = 10);
Task<List<Customer>> GetCustomersByStatusAsync(CustomerStatus status);
Task<int> GetLoyaltyPointsBalanceAsync(string customerId);
Task<bool> UpdateLoyaltyPointsAsync(string customerId, int pointsChange);
}
public class CustomerRepository : BaseRepository<Customer>, ICustomerRepository
{
public CustomerRepository(IMongoDbAdapter adapter, ICacheProvider cache)
: base(adapter, cache)
{
}
public async Task<Customer?> GetByEmailAsync(string email)
{
return await Query()
.Where(c => c.Email == email)
.Where(c => c.IsActive == true)
.FirstOrDefaultAsync();
}
public async Task<List<Customer>> SearchAsync(string searchTerm, int pageIndex = 0, int pageSize = 20)
{
var query = Query();
if (!string.IsNullOrEmpty(searchTerm))
{
query = query.Where(c =>
c.FirstName.Contains(searchTerm) ||
c.LastName.Contains(searchTerm) ||
c.Email.Contains(searchTerm));
}
return await query
.Where(c => c.IsActive == true)
.OrderByDescending(c => c.CreatedOnUtc)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<List<Customer>> GetBySegmentAsync(string segmentId)
{
// Complex query joining customer segments
return await Query()
.Join<CustomerSegmentMembership>("customer_segment_memberships",
c => c.Id,
csm => csm.CustomerId,
(customer, membership) => new { customer, membership })
.Where(joined => joined.membership.SegmentId == segmentId)
.Where(joined => joined.customer.IsActive == true)
.Select(joined => joined.customer)
.ToListAsync();
}
public async Task<CustomerAnalytics> GetAnalyticsAsync(string customerId)
{
// Aggregation pipeline for customer analytics
var pipeline = CreateAggregationPipeline<CustomerAnalytics>()
.Match(c => c.Id == customerId)
.Lookup<Order>("orders", c => c.Id, o => o.CustomerId, "orders")
.Lookup<Payment>("payments", c => c.Id, p => p.CustomerId, "payments")
.Project(c => new CustomerAnalytics
{
CustomerId = c.Id,
TotalOrders = c.Orders.Count(),
TotalSpent = c.Orders.Sum(o => o.TotalAmount),
LastOrderDate = c.Orders.Max(o => o.PlacedAt),
AverageOrderValue = c.Orders.Average(o => o.TotalAmount)
});
var results = await pipeline.ToListAsync();
return results.FirstOrDefault() ?? new CustomerAnalytics { CustomerId = customerId };
}
}
Product Repository
public interface IProductRepository : IBaseRepository<Product>
{
Task<Product?> GetBySkuAsync(string sku);
Task<List<Product>> GetByCategoryAsync(string categoryId, int pageIndex = 0, int pageSize = 20);
Task<List<Product>> GetByBrandAsync(string brandId);
Task<List<Product>> SearchAsync(ProductSearchCriteria criteria);
Task<List<Product>> GetFeaturedProductsAsync(int count = 10);
Task<List<Product>> GetLowStockProductsAsync(int threshold = 10);
Task<bool> UpdateStockAsync(string productId, int newStock);
Task<bool> ReserveStockAsync(string productId, int quantity);
Task<bool> ReleaseStockAsync(string productId, int quantity);
Task<ProductPerformanceMetrics> GetPerformanceMetricsAsync(string productId);
}
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public ProductRepository(IMongoDbAdapter adapter, ICacheProvider cache)
: base(adapter, cache)
{
}
public async Task<Product?> GetBySkuAsync(string sku)
{
return await GetFromCacheOrDbAsync($"product:sku:{sku}", async () =>
{
return await Query()
.Where(p => p.SKU == sku)
.Where(p => p.Status != ProductStatus.Deleted)
.FirstOrDefaultAsync();
});
}
public async Task<List<Product>> SearchAsync(ProductSearchCriteria criteria)
{
var query = Query();
// Apply filters
if (!string.IsNullOrEmpty(criteria.SearchTerm))
{
query = query.Where(p =>
p.Name.Contains(criteria.SearchTerm) ||
p.Description.Contains(criteria.SearchTerm) ||
p.SKU.Contains(criteria.SearchTerm));
}
if (!string.IsNullOrEmpty(criteria.CategoryId))
{
query = query.Where(p => p.CategoryId == criteria.CategoryId);
}
if (!string.IsNullOrEmpty(criteria.BrandId))
{
query = query.Where(p => p.BrandId == criteria.BrandId);
}
if (criteria.MinPrice.HasValue)
{
query = query.Where(p => p.BasePrice >= criteria.MinPrice.Value);
}
if (criteria.MaxPrice.HasValue)
{
query = query.Where(p => p.BasePrice <= criteria.MaxPrice.Value);
}
if (criteria.InStock)
{
query = query.Where(p => p.StockQuantity > 0);
}
// Apply sorting
query = criteria.SortBy switch
{
ProductSortBy.Name => query.OrderBy(p => p.Name),
ProductSortBy.Price => query.OrderBy(p => p.BasePrice),
ProductSortBy.PriceDescending => query.OrderByDescending(p => p.BasePrice),
ProductSortBy.Newest => query.OrderByDescending(p => p.CreatedOnUtc),
ProductSortBy.Featured => query.OrderByDescending(p => p.IsFeatured).ThenBy(p => p.SortOrder),
_ => query.OrderBy(p => p.Name)
};
// Apply pagination
return await query
.Skip(criteria.PageIndex * criteria.PageSize)
.Take(criteria.PageSize)
.ToListAsync();
}
public async Task<bool> UpdateStockAsync(string productId, int newStock)
{
var updateResult = await UpdateOneAsync(
p => p.Id == productId,
Builders<Product>.Update
.Set(p => p.StockQuantity, newStock)
.Set(p => p.UpdatedOnUtc, DateTime.UtcNow));
if (updateResult.ModifiedCount > 0)
{
// Invalidate cache
await InvalidateCacheAsync($"product:{productId}");
// Trigger stock change event
await PublishStockChangeEventAsync(productId, newStock);
}
return updateResult.ModifiedCount > 0;
}
public async Task<ProductPerformanceMetrics> GetPerformanceMetricsAsync(string productId)
{
// Complex aggregation for performance metrics
var pipeline = CreateAggregationPipeline<ProductPerformanceMetrics>()
.Match(p => p.Id == productId)
.Lookup<OrderItem>("order_items", p => p.Id, oi => oi.ProductId, "order_items")
.Lookup<ProductView>("product_views", p => p.Id, pv => pv.ProductId, "views")
.Project(p => new ProductPerformanceMetrics
{
ProductId = p.Id,
TotalSales = p.OrderItems.Sum(oi => oi.LineTotal),
TotalQuantitySold = p.OrderItems.Sum(oi => oi.Quantity),
TotalViews = p.Views.Count(),
ConversionRate = (double)p.OrderItems.Count() / p.Views.Count() * 100
});
var results = await pipeline.ToListAsync();
return results.FirstOrDefault() ?? new ProductPerformanceMetrics { ProductId = productId };
}
}
public class ProductSearchCriteria
{
public string? SearchTerm { get; set; }
public string? CategoryId { get; set; }
public string? BrandId { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
public bool InStock { get; set; } = false;
public ProductSortBy SortBy { get; set; } = ProductSortBy.Name;
public int PageIndex { get; set; } = 0;
public int PageSize { get; set; } = 20;
}
public enum ProductSortBy
{
Name,
Price,
PriceDescending,
Newest,
Featured
}
Order Repository
public interface IOrderRepository : IBaseRepository<Order>
{
Task<Order?> GetByOrderNumberAsync(string orderNumber);
Task<List<Order>> GetByCustomerAsync(string customerId, int pageIndex = 0, int pageSize = 20);
Task<List<Order>> GetByStatusAsync(OrderStatus status);
Task<List<Order>> GetByDateRangeAsync(DateTime startDate, DateTime endDate);
Task<OrderAnalytics> GetAnalyticsAsync(DateTime? startDate = null, DateTime? endDate = null);
Task<List<Order>> GetRecentOrdersAsync(int count = 10);
Task<bool> UpdateOrderStatusAsync(string orderId, OrderStatus status);
Task<bool> AddOrderItemAsync(string orderId, OrderItem item);
Task<bool> RemoveOrderItemAsync(string orderId, string orderItemId);
Task<decimal> GetCustomerTotalSpentAsync(string customerId);
}
public class OrderRepository : BaseRepository<Order>, IOrderRepository
{
public OrderRepository(IMongoDbAdapter adapter, ICacheProvider cache)
: base(adapter, cache)
{
}
public async Task<Order?> GetByOrderNumberAsync(string orderNumber)
{
return await GetFromCacheOrDbAsync($"order:number:{orderNumber}", async () =>
{
return await Query()
.Where(o => o.OrderNumber == orderNumber)
.FirstOrDefaultAsync();
});
}
public async Task<List<Order>> GetByCustomerAsync(string customerId, int pageIndex = 0, int pageSize = 20)
{
return await Query()
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.PlacedAt)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<OrderAnalytics> GetAnalyticsAsync(DateTime? startDate = null, DateTime? endDate = null)
{
var matchConditions = new List<Expression<Func<Order, bool>>>();
if (startDate.HasValue)
matchConditions.Add(o => o.PlacedAt >= startDate.Value);
if (endDate.HasValue)
matchConditions.Add(o => o.PlacedAt <= endDate.Value);
var query = Query();
foreach (var condition in matchConditions)
{
query = query.Where(condition);
}
// Perform aggregation for analytics
var orders = await query.ToListAsync();
return new OrderAnalytics
{
TotalOrders = orders.Count,
TotalRevenue = orders.Sum(o => o.TotalAmount),
AverageOrderValue = orders.Count > 0 ? orders.Average(o => o.TotalAmount) : 0,
CompletedOrders = orders.Count(o => o.Status == OrderStatus.Delivered),
PendingOrders = orders.Count(o => o.Status == OrderStatus.Pending),
CancelledOrders = orders.Count(o => o.Status == OrderStatus.Cancelled)
};
}
public async Task<bool> UpdateOrderStatusAsync(string orderId, OrderStatus status)
{
var updateResult = await UpdateOneAsync(
o => o.Id == orderId,
Builders<Order>.Update
.Set(o => o.Status, status)
.Set(o => o.UpdatedOnUtc, DateTime.UtcNow));
if (updateResult.ModifiedCount > 0)
{
await InvalidateCacheAsync($"order:{orderId}");
await PublishOrderStatusChangedEventAsync(orderId, status);
}
return updateResult.ModifiedCount > 0;
}
public async Task<decimal> GetCustomerTotalSpentAsync(string customerId)
{
var totalSpent = await Query()
.Where(o => o.CustomerId == customerId)
.Where(o => o.Status == OrderStatus.Delivered)
.SumAsync(o => o.TotalAmount);
return totalSpent;
}
}
Payment Repository
public interface IPaymentRepository : IBaseRepository<Payment>
{
Task<List<Payment>> GetByOrderAsync(string orderId);
Task<List<Payment>> GetByCustomerAsync(string customerId);
Task<Payment?> GetByGatewayTransactionIdAsync(string gatewayTransactionId);
Task<List<Payment>> GetByStatusAsync(PaymentStatus status);
Task<PaymentAnalytics> GetAnalyticsAsync(DateTime? startDate = null, DateTime? endDate = null);
Task<List<Payment>> GetFailedPaymentsAsync();
Task<bool> UpdatePaymentStatusAsync(string paymentId, PaymentStatus status);
Task<bool> AddRefundAsync(string paymentId, PaymentRefund refund);
}
public class PaymentRepository : BaseRepository<Payment>, IPaymentRepository
{
public PaymentRepository(IMongoDbAdapter adapter, ICacheProvider cache)
: base(adapter, cache)
{
}
public async Task<List<Payment>> GetByOrderAsync(string orderId)
{
return await Query()
.Where(p => p.OrderId == orderId)
.OrderByDescending(p => p.ProcessedAt)
.ToListAsync();
}
public async Task<Payment?> GetByGatewayTransactionIdAsync(string gatewayTransactionId)
{
return await Query()
.Where(p => p.GatewayTransactionId == gatewayTransactionId)
.FirstOrDefaultAsync();
}
public async Task<PaymentAnalytics> GetAnalyticsAsync(DateTime? startDate = null, DateTime? endDate = null)
{
var query = Query();
if (startDate.HasValue)
query = query.Where(p => p.ProcessedAt >= startDate.Value);
if (endDate.HasValue)
query = query.Where(p => p.ProcessedAt <= endDate.Value);
var payments = await query.ToListAsync();
return new PaymentAnalytics
{
TotalPayments = payments.Count,
TotalAmount = payments.Where(p => p.Status == PaymentStatus.Captured).Sum(p => p.Amount),
SuccessfulPayments = payments.Count(p => p.Status == PaymentStatus.Captured),
FailedPayments = payments.Count(p => p.Status == PaymentStatus.Failed),
RefundedAmount = payments.SelectMany(p => p.Refunds)
.Where(r => r.Status == RefundStatus.Completed)
.Sum(r => r.Amount),
SuccessRate = payments.Count > 0
? (double)payments.Count(p => p.Status == PaymentStatus.Captured) / payments.Count * 100
: 0
};
}
}
Advanced Data Access Patterns
Specification Pattern
public abstract class Specification<T>
{
public abstract Expression<Func<T, bool>> ToExpression();
public static implicit operator Expression<Func<T, bool>>(Specification<T> specification)
{
return specification.ToExpression();
}
}
// Customer specifications
public class ActiveCustomersSpecification : Specification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return customer => customer.Status == CustomerStatus.Active && customer.IsActive;
}
}
public class CustomersBySegmentSpecification : Specification<Customer>
{
private readonly string _segmentId;
public CustomersBySegmentSpecification(string segmentId)
{
_segmentId = segmentId;
}
public override Expression<Func<Customer, bool>> ToExpression()
{
// This would typically involve a join with customer segment memberships
return customer => customer.SegmentMemberships.Any(sm => sm.SegmentId == _segmentId);
}
}
// Product specifications
public class InStockProductsSpecification : Specification<Product>
{
public override Expression<Func<Product, bool>> ToExpression()
{
return product => product.StockQuantity > 0 && product.Status == ProductStatus.Active;
}
}
public class ProductsInPriceRangeSpecification : Specification<Product>
{
private readonly decimal _minPrice;
private readonly decimal _maxPrice;
public ProductsInPriceRangeSpecification(decimal minPrice, decimal maxPrice)
{
_minPrice = minPrice;
_maxPrice = maxPrice;
}
public override Expression<Func<Product, bool>> ToExpression()
{
return product => product.BasePrice >= _minPrice && product.BasePrice <= _maxPrice;
}
}
// Usage in repositories
public async Task<List<Customer>> GetActiveCustomersInSegmentAsync(string segmentId)
{
var activeSpec = new ActiveCustomersSpecification();
var segmentSpec = new CustomersBySegmentSpecification(segmentId);
return await Query()
.Where(activeSpec)
.Where(segmentSpec)
.ToListAsync();
}
Unit of Work Pattern
public interface IUnitOfWork : IDisposable
{
ICustomerRepository Customers { get; }
IProductRepository Products { get; }
IOrderRepository Orders { get; }
IPaymentRepository Payments { get; }
Task<bool> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly IMongoDbAdapter _adapter;
private readonly ICacheProvider _cache;
private IClientSessionHandle? _session;
public UnitOfWork(IMongoDbAdapter adapter, ICacheProvider cache)
{
_adapter = adapter;
_cache = cache;
Customers = new CustomerRepository(_adapter, _cache);
Products = new ProductRepository(_adapter, _cache);
Orders = new OrderRepository(_adapter, _cache);
Payments = new PaymentRepository(_adapter, _cache);
}
public ICustomerRepository Customers { get; }
public IProductRepository Products { get; }
public IOrderRepository Orders { get; }
public IPaymentRepository Payments { get; }
public async Task BeginTransactionAsync()
{
_session = await _adapter.Database.Client.StartSessionAsync();
_session.StartTransaction();
}
public async Task CommitTransactionAsync()
{
if (_session?.IsInTransaction == true)
{
await _session.CommitTransactionAsync();
}
}
public async Task RollbackTransactionAsync()
{
if (_session?.IsInTransaction == true)
{
await _session.AbortTransactionAsync();
}
}
public async Task<bool> SaveChangesAsync()
{
// In MongoDB, individual operations are atomic
// This method can be used for cache invalidation and event publishing
return true;
}
public void Dispose()
{
_session?.Dispose();
}
}
// Usage example
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
{
await _unitOfWork.BeginTransactionAsync();
try
{
// Create order
var order = await _unitOfWork.Orders.CreateAsync(new Order
{
// ... order properties
});
// Update product stock
foreach (var item in request.Items)
{
await _unitOfWork.Products.ReserveStockAsync(item.ProductId, item.Quantity);
}
// Create payment record
var payment = await _unitOfWork.Payments.CreateAsync(new Payment
{
// ... payment properties
});
await _unitOfWork.CommitTransactionAsync();
await _unitOfWork.SaveChangesAsync();
return order;
}
catch
{
await _unitOfWork.RollbackTransactionAsync();
throw;
}
}
}
Caching Strategies
Repository-Level Caching
public abstract class BaseRepository<T> where T : BaseEntity
{
protected readonly IMongoDbAdapter _adapter;
protected readonly ICacheProvider _cache;
protected BaseRepository(IMongoDbAdapter adapter, ICacheProvider cache)
{
_adapter = adapter;
_cache = cache;
}
protected async Task<TResult> GetFromCacheOrDbAsync<TResult>(string cacheKey, Func<Task<TResult>> dbOperation, TimeSpan? expiry = null)
{
// Try cache first
var cached = await _cache.GetAsync<TResult>(cacheKey);
if (cached != null)
return cached;
// Fallback to database
var result = await dbOperation();
if (result != null)
{
await _cache.SetAsync(cacheKey, result, expiry ?? TimeSpan.FromMinutes(15));
}
return result;
}
protected async Task InvalidateCacheAsync(string cacheKey)
{
await _cache.RemoveAsync(cacheKey);
}
protected async Task InvalidateCachePatternAsync(string pattern)
{
// Implementation depends on cache provider capabilities
await _cache.RemoveByPatternAsync(pattern);
}
}
Smart Cache Invalidation
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public override async Task<Product> UpdateAsync(Product product)
{
var updated = await base.UpdateAsync(product);
// Invalidate related caches
await InvalidateCacheAsync($"product:{product.Id}");
await InvalidateCacheAsync($"product:sku:{product.SKU}");
await InvalidateCachePatternAsync($"product:category:{product.CategoryId}:*");
await InvalidateCachePatternAsync($"product:brand:{product.BrandId}:*");
return updated;
}
}
Analytics and Reporting
Customer Analytics
public class CustomerAnalytics
{
public string CustomerId { get; set; } = string.Empty;
public int TotalOrders { get; set; }
public decimal TotalSpent { get; set; }
public DateTime? LastOrderDate { get; set; }
public decimal AverageOrderValue { get; set; }
public int LoyaltyPoints { get; set; }
public CustomerSegment[] Segments { get; set; } = Array.Empty<CustomerSegment>();
public TimeSpan AverageTimeBetweenOrders { get; set; }
public string PreferredCategory { get; set; } = string.Empty;
}
Business Intelligence Queries
public class AnalyticsRepository
{
private readonly IMongoDbAdapter _adapter;
public AnalyticsRepository(IMongoDbAdapter adapter)
{
_adapter = adapter;
}
public async Task<List<TopSellingProduct>> GetTopSellingProductsAsync(DateTime startDate, DateTime endDate, int count = 10)
{
var pipeline = _adapter.GetCollection<Order>()
.Aggregate()
.Match(o => o.PlacedAt >= startDate && o.PlacedAt <= endDate)
.Unwind(o => o.Items)
.Group(
orderItem => orderItem.Items.ProductId,
group => new TopSellingProduct
{
ProductId = group.Key,
TotalQuantitySold = group.Sum(g => g.Items.Quantity),
TotalRevenue = group.Sum(g => g.Items.LineTotal)
})
.SortByDescending(p => p.TotalQuantitySold)
.Limit(count);
return await pipeline.ToListAsync();
}
public async Task<List<CustomerLifetimeValue>> CalculateCustomerLifetimeValueAsync()
{
var pipeline = _adapter.GetCollection<Order>()
.Aggregate()
.Group(
order => order.CustomerId,
group => new CustomerLifetimeValue
{
CustomerId = group.Key,
TotalOrders = group.Count(),
TotalSpent = group.Sum(g => g.TotalAmount),
FirstOrderDate = group.Min(g => g.PlacedAt),
LastOrderDate = group.Max(g => g.PlacedAt),
AverageOrderValue = group.Average(g => g.TotalAmount)
})
.SortByDescending(clv => clv.TotalSpent);
return await pipeline.ToListAsync();
}
}
public class TopSellingProduct
{
public string ProductId { get; set; } = string.Empty;
public int TotalQuantitySold { get; set; }
public decimal TotalRevenue { get; set; }
}
public class CustomerLifetimeValue
{
public string CustomerId { get; set; } = string.Empty;
public int TotalOrders { get; set; }
public decimal TotalSpent { get; set; }
public DateTime FirstOrderDate { get; set; }
public DateTime LastOrderDate { get; set; }
public decimal AverageOrderValue { get; set; }
}
Integration with OElite Platform
With OElite.Common.Platform
// Repositories use platform entities
public async Task<Product> CreateAsync(Product product)
{
// Product is from OElite.Common.Platform
return await base.CreateAsync(product);
}
With OElite.Services.Platform
// Services use repositories for data access
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task<Product> GetProductAsync(string productId)
{
return await _productRepository.GetByIdAsync(productId);
}
}
With OElite.Restme.MongoDb
// Built on top of Restme MongoDB abstractions
public class ProductRepository : BaseRepository<Product>
{
public ProductRepository(IMongoDbAdapter adapter, ICacheProvider cache)
: base(adapter, cache)
{
// Uses MongoDbAdapter from OElite.Restme.MongoDb
}
}
Configuration
// Connection configuration
services.Configure<PlatformDataOptions>(options =>
{
options.ConnectionString = configuration.GetConnectionString("MongoDB");
options.DatabaseName = "oelite_platform";
options.CacheExpirationMinutes = 15;
options.EnableQueryLogging = environment.IsDevelopment();
});
// Register repositories
services.AddOElitePlatformRepositories(configuration);
Performance Optimization
Indexing Strategy
public class IndexConfiguration
{
public static void ConfigureIndexes(IMongoDatabase database)
{
// Customer indexes
var customers = database.GetCollection<Customer>("customers");
customers.Indexes.CreateOne(new CreateIndexModel<Customer>(
Builders<Customer>.IndexKeys.Ascending(c => c.Email),
new CreateIndexOptions { Unique = true }));
// Product indexes
var products = database.GetCollection<Product>("products");
products.Indexes.CreateOne(new CreateIndexModel<Product>(
Builders<Product>.IndexKeys
.Ascending(p => p.CategoryId)
.Ascending(p => p.Status)));
// Order indexes
var orders = database.GetCollection<Order>("orders");
orders.Indexes.CreateOne(new CreateIndexModel<Order>(
Builders<Order>.IndexKeys
.Ascending(o => o.CustomerId)
.Descending(o => o.PlacedAt)));
}
}
Requirements
- .NET 10.0+
- OElite.Common.Platform (domain entities)
- OElite.Data (base repository abstractions)
- OElite.Restme.MongoDb (MongoDB integration)
- OElite.Restme.Redis (caching)
- Microsoft.Extensions.Caching.Memory 9.0.0+
Thread Safety
All repositories are designed for thread-safe operations and can be registered as scoped services in dependency injection containers. The underlying MongoDB driver handles connection pooling and thread safety automatically.
License
Copyright © OElite Limited. All rights reserved.
Showing the top 20 packages that depend on OElite.Data.Platform.
| Packages | Downloads |
|---|---|
|
OElite.Services.Platform
Package Description
|
4 |
|
OElite.Services.Platform
Package Description
|
3 |
.NET 10.0
- OElite.Common.Platform (>= 5.0.9-develop.476)
- OElite.Data (>= 5.0.9-develop.476)
- Microsoft.Extensions.Caching.Memory (>= 9.0.0)
- OElite.Restme.MongoDb (>= 2.0.9-develop.442)
- OElite.Restme.Utils (>= 2.0.9-develop.442)