### Password Generation in C# Source: https://context7.com/havit/havitframework/llms.txt Provides examples of generating cryptographically secure passwords with configurable options. Demonstrates static and instance-based usage with various complexity settings. ```csharp using Havit.Security; // Quick static method usage string password1 = PasswordGenerator.Generate(12, PasswordCharacterSet.LettersAndDigits); // Example: "K7mPn2xR4vQw" string password2 = PasswordGenerator.Generate(16, PasswordCharacterSet.LettersDigitsAndSpecialCharacters); // Example: "K7m$Pn2@xR4v!Qw#" // Full control with static overload string password3 = PasswordGenerator.Generate( minimumLength: 10, maximumLength: 14, passwordCharacterSet: PasswordCharacterSet.Letters, allowRepeatingCharacters: false, // No character appears twice allowConsecutiveCharacters: false // No "aa", "bb" patterns ); // Instance-based usage for reusable configuration var generator = new PasswordGenerator { MinimumLength = 12, MaximumLength = 16, PasswordCharacterSet = PasswordCharacterSet.LettersDigitsAndSpecialCharacters, AllowRepeatingCharacters = true, AllowConsecutiveCharacters = false, Exclusions = "0O1lI" // Exclude ambiguous characters }; string securePassword = generator.Generate(); // Example: "K7m$Pn2@xR4v!Qw#" (12-16 chars, no consecutive chars, excludes 0,O,1,l,I) // Character set options: // - LowerCaseLetters: a-z only // - Letters: a-z and A-Z // - LettersAndDigits: a-z, A-Z, 0-9 // - LettersDigitsAndSpecialCharacters: full set including `~!@#$%^&*()-_=+[]{}\\|;:'",<.>/? ``` -------------------------------- ### Complex Queries with Joins using IDataSource Source: https://context7.com/havit/havitframework/llms.txt Perform complex queries, including joins and projections, on the IDataSource.Data. The example demonstrates selecting related data into a DTO. ```csharp return await _productDataSource.Data .Select(p => new ProductSummary { Id = p.Id, Name = p.Name, CategoryName = p.Category.Name, OrderCount = p.OrderItems.Count }) .ToListAsync(ct); ``` -------------------------------- ### Configure EF Core DI Services Source: https://context7.com/havit/havitframework/llms.txt Sets up Entity Framework Core, repositories, unit of work, and other data layer services. Ensure DbContext and other services are registered correctly. ```csharp using Havit.Data.EntityFrameworkCore.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; public class Startup { public void ConfigureServices(IServiceCollection services) { // Register DbContext using standard EF Core registration services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); // Register as IDbContext interface services.AddScoped(sp => sp.GetRequiredService()); // Register core data layer services services.AddDataLayerCoreServices(new ComponentRegistrationOptions { // Configure caching behavior // Options: NoCachingInstaller, DefaultCachingInstaller, // CacheAllEntitiesWithDefaultSlidingExpirationCachingInstaller }); // Register generated data layer services (repositories, data sources, etc.) // This method is typically generated by the code generator services.AddDataLayerServices(); // Register data seeds from assembly services.AddDataSeeds(typeof(InitialDataSeed).Assembly); // Register localization services services.AddLocalizationServices(); // Register lookup services (cached reference data) services.AddLookupService(); } } // Example DbContext setup public class ApplicationDbContext : Havit.Data.EntityFrameworkCore.DbContext, IDbContext { public ApplicationDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Apply Havit conventions (cascade delete to restrict, etc.) modelBuilder.UseDefaultHavitConventions(); // Apply entity configurations modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); } } ``` -------------------------------- ### Implement File Storage Service Operations Source: https://context7.com/havit/havitframework/llms.txt Provides methods for interacting with file storage, supporting various backends. Use this service for common file operations like checking existence, reading, saving, and deleting. ```csharp using Havit.Services.FileStorage; public class DocumentService { private readonly IFileStorageService _fileStorage; public DocumentService(IFileStorageService fileStorage) { _fileStorage = fileStorage; } // Check if file exists public async Task DocumentExistsAsync(string path, CancellationToken ct) { return await _fileStorage.ExistsAsync(path, ct); } // Read file content public async Task GetDocumentAsync(string path, CancellationToken ct) { using var stream = await _fileStorage.OpenReadAsync(path, ct); using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream, ct); return memoryStream.ToArray(); } // Save file public async Task SaveDocumentAsync(string path, Stream content, string contentType, CancellationToken ct) { await _fileStorage.SaveAsync(path, content, contentType, ct); } // Upload using write stream public async Task UploadLargeFileAsync(string path, string contentType, Func writeAction, CancellationToken ct) { using var stream = await _fileStorage.OpenCreateAsync(path, contentType, ct); await writeAction(stream); } // Copy between storages public async Task ArchiveDocumentAsync( string sourcePath, IFileStorageService archiveStorage, CancellationToken ct) { await _fileStorage.CopyAsync(sourcePath, archiveStorage, $"archive/{sourcePath}", ct); } // Move file public async Task RenameDocumentAsync(string oldPath, string newPath, CancellationToken ct) { await _fileStorage.MoveAsync(oldPath, newPath, ct); } // Delete file public async Task DeleteDocumentAsync(string path, CancellationToken ct) { await _fileStorage.DeleteAsync(path, ct); } // List files public async IAsyncEnumerable ListDocumentsAsync( string pattern = null, [EnumeratorCancellation] CancellationToken ct = default) { await foreach (var file in _fileStorage.EnumerateFilesAsync(pattern, ct)) { yield return file; } } // Get file metadata public async Task GetLastModifiedAsync(string path, CancellationToken ct) { return await _fileStorage.GetLastModifiedTimeUtcAsync(path, ct); } } ``` -------------------------------- ### Run Data Seeds using IDataSeedRunner in C# Source: https://context7.com/havit/havitframework/llms.txt Initialize the database by running all data seeds for a specific profile using the IDataSeedRunner. This typically involves injecting the runner and calling SeedDataAsync. ```csharp // Running seeds public class DatabaseInitializer { private readonly IDataSeedRunner _seedRunner; public DatabaseInitializer(IDataSeedRunner seedRunner) { _seedRunner = seedRunner; } public async Task InitializeAsync(CancellationToken ct) { // Run all seeds for default profile await _seedRunner.SeedDataAsync(ct); } } ``` -------------------------------- ### Configure EnterpriseGridView with ModalEditorExtender Source: https://github.com/havit/havitframework/blob/master/Havit.Web.Bootstrap.Tutorial/sections/controls/samples/ModalEditorExtenderSample.txt Set up an EnterpriseGridView with basic columns and enable editing. Associate it with a ModalEditorExtender for inline editing functionality. ```aspx <%-- can be in skin (be careful when choosing validation group) --%> ``` -------------------------------- ### EF Core DbRepository Implementation Source: https://context7.com/havit/havitframework/llms.txt Implement a concrete repository for Entity Framework Core, supporting caching, soft deletes, and automatic reference loading. Override GetLoadReferences to specify always-loaded properties. ```csharp using Havit.Data.EntityFrameworkCore.Patterns.Repositories; using System.Linq.Expressions; // Generated repository implementation (typically auto-generated) public partial class CustomerRepository : DbRepository, ICustomerRepository { public CustomerRepository( IDbContext dbContext, IEntityKeyAccessor entityKeyAccessor, IDataLoader dataLoader, ISoftDeleteManager softDeleteManager, IEntityCacheManager entityCacheManager, IRepositoryQueryProvider repositoryQueryProvider) : base(dbContext, entityKeyAccessor, dataLoader, softDeleteManager, entityCacheManager, repositoryQueryProvider) { } // Override to define properties that are always loaded with the entity protected override IEnumerable>> GetLoadReferences() { yield return c => c.Address; yield return c => c.PrimaryContact; } // Custom query methods use protected Data/DataIncludingDeleted properties public Customer GetByEmail(string email) { return Data.FirstOrDefault(c => c.Email == email); } public async Task> GetActiveCustomersAsync(CancellationToken ct) { return await Data .Where(c => c.IsActive) .OrderBy(c => c.Name) .ToListAsync(ct); } // Access deleted customers for audit public async Task> GetDeletedCustomersAsync(CancellationToken ct) { return await DataIncludingDeleted .Where(c => c.Deleted != null) .ToListAsync(ct); } } ``` -------------------------------- ### Define a Simple Data Seed in C# Source: https://context7.com/havit/havitframework/llms.txt Create a basic data seed by inheriting from DataSeed and implementing the SeedData method. Use the Seed method with PairBy to match existing records by a specified property. ```csharp using Havit.Data.Patterns.DataSeeds; // Simple data seed public class CountrySeed : DataSeed { public override void SeedData() { var countries = new[] { new Country { Code = "CZ", Name = "Czech Republic" }, new Country { Code = "SK", Name = "Slovakia" }, new Country { Code = "DE", Name = "Germany" } }; // Seed with pair-by expression (matches existing records by Code) Seed(For(countries).PairBy(c => c.Code)); } } ``` -------------------------------- ### String Manipulation with Havit.Core Extensions Source: https://context7.com/havit/havitframework/llms.txt Utilize extension methods for string manipulation like extracting substrings, removing diacritics, and normalizing for URLs. Handles null inputs and edge cases gracefully. ```csharp using Havit; // Extract left/right portions of a string string text = "Hello World"; string leftPart = text.Left(5); // Returns "Hello" string rightPart = text.Right(5); // Returns "World" // Handle edge cases gracefully string safeLeft = text.Left(100); // Returns full string when length exceeds string length string empty = ((string)null).Left(5); // Returns empty string for null input // Remove diacritics from text (useful for search, indexing) string czech = "Příliš žluťoučký kůň"; string normalized = czech.RemoveDiacritics(); // Returns "Prilis zlutoucky kun" // Normalize text for SEO-friendly URLs string articleTitle = "Příliš žluťoučký kůň úpěl ďábelské ódy!"; string urlSlug = articleTitle.NormalizeForUrl(); // Returns "prilis-zlutoucky-kun-upel-dabelske-ody" // Steps: lowercase -> remove diacritics -> replace non-alphanumeric with hyphen -> merge multiple hyphens -> trim hyphens ``` -------------------------------- ### Outer Joins with Havit.Linq Extensions Source: https://context7.com/havit/havitframework/llms.txt Perform left and full outer joins on collections using LINQ extension methods. Define join conditions and result selectors. ```csharp using Havit.Linq; // Left outer join var orders = GetOrders(); var customers = GetCustomers(); var orderWithCustomer = orders.LeftJoin( customers, order => order.CustomerId, customer => customer.Id, (order, customer) => new { Order = order, CustomerName = customer?.Name ?? "Unknown" } ); // Full outer join var fullJoin = orders.FullOuterJoin( customers, o => o.CustomerId, c => c.Id, (order, customer) => new { Order = order, Customer = customer } ); ``` -------------------------------- ### Contract Assertions in C# Source: https://context7.com/havit/havitframework/llms.txt Demonstrates how to use Contract.Requires for argument validation and Contract.Assert for internal invariants. Supports custom exception types and caller argument expressions. ```csharp using Havit.Diagnostics.Contracts; public class OrderService { public void ProcessOrder(Order order, int quantity) { // Throws ContractException if condition is false Contract.Requires(order != null); // Throws specific exception type if condition is false Contract.Requires(order != null, nameof(order)); Contract.Requires(quantity > 0, nameof(quantity)); // Use Assert for internal invariants var total = CalculateTotal(order, quantity); Contract.Assert(total >= 0, "Total must be non-negative"); // With custom exception type Contract.Assert( order.Status == OrderStatus.Pending, "Order must be in pending status" ); // Process the order... } } // Exception message format when condition fails: // "Contract failed: order != null" // Includes automatic CallerArgumentExpression in .NET 6+ ``` -------------------------------- ### Handle GridView Data Binding and Insert Row Events Source: https://github.com/havit/havitframework/blob/master/Havit.Web.Bootstrap.Tutorial/sections/controls/samples/ModalEditorExtenderSample.txt Implement server-side event handlers for OnInit, DataBinding, and GetInsertRowDataItem to manage data source and insert row logic for the EnterpriseGridView. ```csharp protected override void OnInit(EventArgs e) { base.OnInit(e); MainGV.DataBinding += MainGV_DataBinding; MainGV.GetInsertRowDataItem += MainGV_GetInsertRowDataItem; } private void MainGV_DataBinding(object sender, EventArgs e) { MainGV.DataSource = ...; } private object MainGV_GetInsertRowDataItem() { return ...; } ``` -------------------------------- ### Load Multiple Navigation Properties with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Load multiple navigation properties for an entity simultaneously using IDataLoader. This can improve performance by reducing individual database calls. ```csharp _dataLoader.Load(order, o => o.Customer, o => o.ShippingAddress); ``` -------------------------------- ### Load Through Collections with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Use AllItems() within LoadAsync to load properties for items within a collection, and chain ThenLoadAsync for further nested loading. ```csharp await _dataLoader .LoadAsync(order, o => o.OrderItems.AllItems().Product, ct) .ThenLoadAsync(p => p.Category, ct); ``` -------------------------------- ### Load Multiple Properties for Collection with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Load several navigation properties for all entities within a collection using LoadAll. This is an efficient way to fetch related data for multiple objects. ```csharp _dataLoader.LoadAll(orders, o => o.Customer, o => o.ShippingAddress, o => o.OrderItems); ``` -------------------------------- ### Critical Section Synchronization in C# Source: https://context7.com/havit/havitframework/llms.txt Illustrates value-based locking using CriticalSection for thread synchronization based on keys. Shows synchronous and asynchronous usage with ExecuteAction and ExecuteActionAsync, as well as scope-based locking. ```csharp using Havit.Threading; public class DocumentService { private readonly CriticalSection _documentLocks = new CriticalSection(); private readonly CriticalSection _userLocks = new CriticalSection(StringComparer.OrdinalIgnoreCase); // Synchronous usage with ExecuteAction public void UpdateDocument(int documentId, string content) { _documentLocks.ExecuteAction(documentId, () => { // Only one thread can update document with this ID at a time var doc = LoadDocument(documentId); doc.Content = content; SaveDocument(doc); }); } // Async usage with ExecuteActionAsync public async Task UpdateDocumentAsync(int documentId, string content, CancellationToken ct) { await _documentLocks.ExecuteActionAsync(documentId, async () => { var doc = await LoadDocumentAsync(documentId); doc.Content = content; await SaveDocumentAsync(doc); }, ct); } // Using scope pattern for more control public async Task ProcessUserRequestAsync(string username, CancellationToken ct) { using (await _userLocks.EnterScopeAsync(username, ct)) { // Critical section - only one request per user processed at a time await ValidateUserAsync(username); await ProcessRequestAsync(username); await LogActivityAsync(username); } // Lock automatically released when scope is disposed } } ``` -------------------------------- ### Code Generator Configuration File Source: https://github.com/havit/havitframework/blob/master/Havit.Data.EntityFrameworkCore.Patterns/Docs/EFCore.complete.md Configure the Entity Framework Core code generator using a JSON file. This file specifies project paths and namespaces for generated code. ```json { "ModelProjectPath": "...", "MetadataProjectPath": "...", "DataLayerProjectPath" : "...", "MetadataNamespace": "..." } ``` -------------------------------- ### Update Havit.Data.EntityFrameworkCore.CodeGenerator.Tool Source: https://github.com/havit/havitframework/blob/master/Havit.Data.EntityFrameworkCore.Patterns/Docs/EFCore.complete.md Update the code generator tool using the dotnet CLI. This command should be run from the solution's root directory. ```bash dotnet tool update Havit.Data.EntityFrameworkCore.CodeGenerator.Tool ``` -------------------------------- ### Automatic DI Service Registration with [Service] Attribute Source: https://context7.com/havit/havitframework/llms.txt Register services automatically using the [Service] attribute and assembly scanning. Supports different lifetimes and profiles for environment-specific registrations. Use the ServiceType or ServiceTypes properties for explicit registration. ```csharp using Havit.Extensions.DependencyInjection.Abstractions; using Microsoft.Extensions.DependencyInjection; // Mark classes for automatic DI registration [Service(Lifetime = ServiceLifetime.Scoped)] public class OrderService : IOrderService { // Automatically registered as IOrderService (interface matching class name) } [Service(Lifetime = ServiceLifetime.Singleton)] public class CacheService : ICacheService, IDisposable { // Registered as ICacheService with Singleton lifetime } // Explicit service type registration [Service(Lifetime = ServiceLifetime.Transient, ServiceType = typeof(IValidator))] public class OrderValidator : IValidator { } // Multiple service types [Service(Lifetime = ServiceLifetime.Scoped, ServiceTypes = new[] { typeof(IReader), typeof(IWriter) })] public class FileHandler : IReader, IWriter { } // Profile-based registration (e.g., different implementations for dev/prod) [Service(Lifetime = ServiceLifetime.Scoped, Profile = "Development")] public class FakeEmailService : IEmailService { } [Service(Lifetime = ServiceLifetime.Scoped, Profile = "Production")] public class SmtpEmailService : IEmailService { } // Registration in Startup/Program.cs public class Startup { public void ConfigureServices(IServiceCollection services) { // Register all services from assembly with default profile services.AddByServiceAttribute(typeof(OrderService).Assembly); // Register services for specific profile services.AddByServiceAttribute(typeof(OrderService).Assembly, "Production"); // Register multiple profiles services.AddByServiceAttribute( typeof(OrderService).Assembly, new[] { "Production", "FeatureX" } ); } } ``` -------------------------------- ### Skipping and Checking Collections with Havit.Linq Extensions Source: https://context7.com/havit/havitframework/llms.txt Utilities for skipping elements from the end of a collection, either a fixed number or while a predicate is true. Also includes a method to check if a collection contains all elements from another. ```csharp using Havit.Linq; // Skip last N items (useful for pagination, removing trailing elements) var items = new[] { 1, 2, 3, 4, 5 }; var withoutLastTwo = items.SkipLast(2).ToList(); // [1, 2, 3] // Skip items from end while predicate is true var numbers = new[] { 1, 2, 3, 0, 0, 0 }; var trimmed = numbers.SkipLastWhile(n => n == 0).ToList(); // [1, 2, 3] // Check if collection contains all items from another collection var required = new[] { "A", "B", "C" }; var available = new[] { "A", "B", "C", "D", "E" }; bool hasAll = available.ContainsAll(required); // true ``` -------------------------------- ### Search Products with IDataSource (Excluding Soft Deletes) Source: https://context7.com/havit/havitframework/llms.txt Use IDataSource.Data to query entities, automatically filtering out soft-deleted records. This is the standard way to access active data. ```csharp var query = _productDataSource.Data .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); return await query .OrderBy(p => p.Name) .Take(100) .ToListAsync(ct); ``` -------------------------------- ### Define Generic Repository Interface (C#) Source: https://context7.com/havit/havitframework/llms.txt Defines a generic repository interface for entity retrieval operations, supporting ID-based access, soft delete filtering, and asynchronous operations. Extend this interface for domain-specific methods. ```csharp using Havit.Data.Patterns.Repositories; using Havit.Data.Patterns.Exceptions; // Repository interface - typically generated or manually implemented public interface ICustomerRepository : IRepository { // Additional domain-specific methods can be added Customer GetByEmail(string email); } // Usage in application services public class CustomerService { private readonly ICustomerRepository _customerRepository; private readonly IOrderRepository _orderRepository; public CustomerService(ICustomerRepository customerRepository, IOrderRepository orderRepository) { _customerRepository = customerRepository; _orderRepository = orderRepository; } // Get single entity by ID public Customer GetCustomer(int id) { try { return _customerRepository.GetObject(id); } catch (ObjectNotFoundException) { throw new CustomerNotFoundException($"Customer {id} not found"); } } // Async version public async Task GetCustomerAsync(int id, CancellationToken ct = default) { return await _customerRepository.GetObjectAsync(id, ct); } // Get multiple entities by IDs public List GetCustomers(params int[] ids) { return _customerRepository.GetObjects(ids); } // Get all entities (soft-deleted entities are automatically filtered) public async Task> GetAllCustomersAsync(CancellationToken ct = default) { return await _customerRepository.GetAllAsync(ct); } } ``` -------------------------------- ### Define a Data Seed with Dependencies in C# Source: https://context7.com/havit/havitframework/llms.txt Implement data seeds that depend on other seeds by overriding GetPrerequisiteDataSeeds and returning the types of dependent seeds. Use WithoutUpdate to prevent modification of existing records. ```csharp // Data seed with dependencies public class CitySeed : DataSeed { public override void SeedData() { // This seed depends on CountrySeed running first Seed(For(GetCities()) .PairBy(c => c.Name) .WithoutUpdate()); // Don't update existing records } protected override IEnumerable GetPrerequisiteDataSeeds() { yield return typeof(CountrySeed); } } ``` -------------------------------- ### Load Properties for Multiple Entities with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Efficiently load a specific navigation property for a collection of entities using LoadAllAsync. This is useful when dealing with lists of objects. ```csharp var orders = await _orderRepository.GetAllAsync(ct); await _dataLoader.LoadAllAsync(orders, o => o.Customer, ct); ``` -------------------------------- ### Fluent Chaining for Nested Properties with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Chain multiple LoadAsync calls to load nested navigation properties. This allows for complex data loading scenarios in a readable manner. ```csharp await _dataLoader .LoadAsync(order, o => o.Customer, ct) .ThenLoadAsync(c => c.Address, ct); ``` -------------------------------- ### Load Single Navigation Property with IDataLoader Source: https://context7.com/havit/havitframework/llms.txt Use IDataLoader to lazily load a single navigation property for an entity. Ensure IDataLoader is injected into your service. ```csharp await _dataLoader.LoadAsync(order, o => o.Customer, ct); ``` -------------------------------- ### Conditional Filtering with Havit.Linq Extensions Source: https://context7.com/havit/havitframework/llms.txt Apply LINQ Where clauses conditionally using `WhereIf`. Useful for dynamic query building based on runtime conditions. ```csharp using Havit.Linq; // Conditional filtering - apply Where clause only when condition is true var products = GetProducts(); bool filterByCategory = true; string category = "Electronics"; var filtered = products .WhereIf(filterByCategory, p => p.Category == category) .WhereIf(minPrice.HasValue, p => p.Price >= minPrice.Value) .ToList(); ``` -------------------------------- ### Implement Unit of Work for Transactional Operations (C#) Source: https://context7.com/havit/havitframework/llms.txt Coordinates entity changes and persists them in a single transaction using the Unit of Work pattern. Supports entity registration for insert/update/delete operations with automatic soft delete handling and after-commit callbacks. ```csharp using Havit.Data.Patterns.UnitOfWorks; public class OrderProcessingService { private readonly IUnitOfWork _unitOfWork; private readonly IOrderRepository _orderRepository; private readonly IInventoryRepository _inventoryRepository; private readonly INotificationService _notificationService; public OrderProcessingService( IUnitOfWork unitOfWork, IOrderRepository orderRepository, IInventoryRepository inventoryRepository, INotificationService notificationService) { _unitOfWork = unitOfWork; _orderRepository = orderRepository; _inventoryRepository = inventoryRepository; _notificationService = notificationService; } public async Task ProcessOrderAsync(OrderRequest request, CancellationToken ct) { // Create new entity var order = new Order { CustomerId = request.CustomerId, OrderDate = DateTime.UtcNow, Status = OrderStatus.Pending }; _unitOfWork.AddForInsert(order); // Update existing entities foreach (var item in request.Items) { var inventory = await _inventoryRepository.GetObjectAsync(item.ProductId, ct); inventory.Quantity -= item.Quantity; _unitOfWork.AddForUpdate(inventory); } // Delete entities (uses soft delete if entity supports it) var expiredReservations = await GetExpiredReservationsAsync(ct); _unitOfWork.AddRangeForDelete(expiredReservations); // Register action to execute after successful commit _unitOfWork.RegisterAfterCommitAction(() => { _notificationService.SendOrderConfirmation(order.Id); }); // Register async after-commit action (only works with CommitAsync) _unitOfWork.RegisterAfterCommitAction(async (cancellationToken) => { await _notificationService.SendInventoryAlertAsync(cancellationToken); }); // Save all changes in a single transaction await _unitOfWork.CommitAsync(ct); } // Clear all tracked changes without saving public void DiscardChanges() { _unitOfWork.Clear(); } } ``` -------------------------------- ### Conditional Seeding with AfterSave Callback in C# Source: https://context7.com/havit/havitframework/llms.txt Execute custom logic after data seeds are saved by implementing the AfterSave callback. This is useful for performing actions based on newly saved or updated records, such as logging. ```csharp // Conditional seeding with AfterSave callback public class UserRoleSeed : DataSeed { public override void SeedData() { var roles = new[] { new Role { Name = "Admin", Permissions = "All" }, new Role { Name = "User", Permissions = "Read" } }; Seed(For(roles) .PairBy(r => r.Name) .AfterSave(AfterSaveOperation)); } private void AfterSaveOperation(AfterSaveDataArgs args) { // Execute logic after roles are saved foreach (var role in args.SeededObjects.Where(r => args.IsNew(r))) { LogNewRole(role); } } } ``` -------------------------------- ### Access All Records Including Soft Deletes with IDataSource Source: https://context7.com/havit/havitframework/llms.txt Utilize IDataSource.DataIncludingDeleted to query all records, including those that have been soft-deleted. This is typically used for administrative or auditing purposes. ```csharp return await _productDataSource.DataIncludingDeleted .OrderByDescending(p => p.CreatedDate) .ToListAsync(ct); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.