modules/ai-guide/Write-AiGuide-Persistence.ps1
|
param( [string] $ProjectName, [string] $OrmMode, [string] $DbProvider = "MSSQL", [string] $OutputPath ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $guideDir = Join-Path $OutputPath "ai-guide" function Write-Guide($fileName, $content) { Write-CoreFile (Join-Path $guideDir $fileName) $content } $ormShort = switch ($OrmMode) { "Dapper" { "Dapper (raw SQL, micro-ORM)" } "EFCore" { "EF Core 10 (Code-First, LINQ)" } "Hybrid" { "Hybrid - Write: Dapper | Read: EF Core" } } $dbNote = switch ($DbProvider) { "Postgres" { "PostgreSQL (Npgsql)" } default { "MS SQL Server" } } $ormNote = switch ($OrmMode) { "Dapper" { "All data access is written using Dapper with raw SQL. EF Core and DbContext do NOT exist." } "EFCore" { "All data access is written using EF Core 10. DbContext is used to manage entities Code-First." } "Hybrid" { "Write (Add/Update/Delete): Dapper | Read (GetById/GetAll/GetPaged): EF Core with AsNoTracking()" } } $dbContextNote = if ($OrmMode -ne "Dapper") { @' ### AppDbContext Registration When adding a new entity (e.g. `Category`), add its `DbSet` and global query filter (for soft delete) in `AppDbContext.cs`: ```csharp // {P}-backend/src/{P}.Persistence/Context/AppDbContext.cs public DbSet<Category> Categories => Set<Category>(); // In OnModelCreating: builder.Entity<Category>().HasQueryFilter(e => !e.IsDeleted); ``` ### EF Core Value Object Property Conversion To map a Value Object (like `Email`) to a string database column, configure it in `AppDbContext.cs`: ```csharp builder.Entity<User>(entity => { entity.Property(u => u.Email) .HasConversion(email => email.Value, value => new Email(value)) .HasMaxLength(256); }); ``` '@.Replace("{P}", $ProjectName) } else { "" } $dapperNote = if ($OrmMode -match "Dapper|Hybrid") { @' ### Dapper Custom Type Handler For custom Value Objects (like `Email`), register a custom `SqlMapper.TypeHandler` inside `PersistenceServiceRegistration.cs` so Dapper can map them automatically: ```csharp // {P}-backend/src/{P}.Persistence/PersistenceServiceRegistration.cs public class EmailTypeHandler : SqlMapper.TypeHandler<Email> { public override void SetValue(IDbDataParameter parameter, Email? value) { parameter.Value = value?.Value ?? (object)DBNull.Value; } public override Email? Parse(object value) { return value is string strVal ? new Email(strVal) : null; } } // In ConfigureServices: SqlMapper.AddTypeHandler(new EmailTypeHandler()); ``` '@.Replace("{P}", $ProjectName) } else { "" } $uowNote = if ($OrmMode -match "EFCore|Hybrid") { @' ### Unit of Work Transaction Management In EF Core & Hybrid modes, inject `IUnitOfWork` to orchestrate multiple transactions: ```csharp await _uow.BeginTransactionAsync(); try { await _categoryRepo.AddAsync(category); await _productRepo.AddAsync(product); await _uow.SaveChangesAsync(); await _uow.CommitTransactionAsync(); } catch { await _uow.RollbackTransactionAsync(); throw; } ``` '@ } else { "" } $migrationNote = if ($OrmMode -ne "Dapper") { @' ### EF Core Migrations Use the path-agnostic scripts inside `{P}-backend/scripts/` to manage migrations: ```powershell # Windows PowerShell: cd {P}-backend .\scripts\migrate.ps1 -MigrationName "AddCategoryTable" # Linux/Mac Bash: cd {P}-backend ./scripts/migrate.sh AddCategoryTable ``` '@.Replace("{P}", $ProjectName) } else { "" } $persistenceGuideContent = @' # Persistence Layer AI Developer Guide You are an AI assistant helping developers build the Persistence Layer of the {P} solution. This layer manages database connections, repository implementations, ORM configuration, and migrations. --- ## Technical Context - **ORM Strategy:** {O} - **Database Engine:** {D} - **Rule:** {R} --- ## Strict Persistence Rules 1. **Clean Interfaces:** Repositories must implement the repository interfaces defined in the Domain layer. 2. **Soft Delete enforcement:** All deletions (`DeleteAsync`) must call `entity.Delete()` domain method to mark the entity as deleted instead of running physical SQL deletes. 3. **Global Filtering:** Always apply global query filters (like `IgnoreQueryFilters()` in EF Core) if soft-deleted items must be explicitly loaded for administrative tasks. --- {C} {M} {U} {G} --- ## Service Registration Every new repository must be registered in the dependency injection container: ```csharp // {P}-backend/src/{P}.Persistence/PersistenceServiceRegistration.cs public static class PersistenceServiceRegistration { public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration) { // AppDbContext and connection configuration... services.AddScoped<ICategoryRepository, CategoryRepository>(); return services; } } ``` '@.Replace("{P}", $ProjectName).Replace("{O}", $ormShort).Replace("{D}", $dbNote).Replace("{R}", $ormNote).Replace("{C}", $dbContextNote).Replace("{M}", $dapperNote).Replace("{U}", $uowNote).Replace("{G}", $migrationNote) Write-Guide "persistence-guide.md" $persistenceGuideContent |