using IBKRTrader.Core.Configuration; using IBKRTrader.Modules.Accounting.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; namespace IBKRTrader.Modules.Accounting.Persistence; /// /// EF-Kontext des Accounting-Moduls (gleiche MariaDB, Tabellen mit Präfix acc_). Append-only Ledger mit /// Autoincrement-PKs und Unique-Index auf dem Idempotenz-Schlüssel (kein Doppel-Buchen). /// public class AccountingDbContext : DbContext { public AccountingDbContext(DbContextOptions options) : base(options) { } public DbSet Ledger => Set(); public DbSet IngestRuns => Set(); public DbSet RawSnapshots => Set(); public DbSet FxRates => Set(); protected override void OnModelCreating(ModelBuilder b) { b.Entity(e => { e.ToTable("acc_ledger"); e.HasKey(x => x.Id); e.Property(x => x.Id).ValueGeneratedOnAdd(); e.Property(x => x.AccountId).HasMaxLength(30); e.Property(x => x.EventType).HasConversion().HasMaxLength(20); e.Property(x => x.Symbol).HasMaxLength(30); e.Property(x => x.AssetClass).HasMaxLength(10); e.Property(x => x.Currency).HasMaxLength(5); e.Property(x => x.Side).HasMaxLength(10); e.Property(x => x.Source).HasMaxLength(40); e.Property(x => x.TransactionId).HasMaxLength(60); e.Property(x => x.IdempotencyKey).HasMaxLength(120); e.Property(x => x.Quantity).HasPrecision(28, 8); e.Property(x => x.PriceNative).HasPrecision(18, 6); e.Property(x => x.GrossBase).HasPrecision(28, 8); e.Property(x => x.FeeBase).HasPrecision(28, 8); e.Property(x => x.NetBase).HasPrecision(28, 8); e.HasIndex(x => x.IdempotencyKey).IsUnique(); // Idempotenz: kein Doppel-Buchen e.HasIndex(x => new { x.AccountId, x.Timestamp }); e.HasIndex(x => x.EventType); }); b.Entity(e => { e.ToTable("acc_ingest_runs"); e.HasKey(x => x.Id); e.Property(x => x.Id).ValueGeneratedOnAdd(); e.Property(x => x.AccountId).HasMaxLength(30); e.Property(x => x.Message).HasMaxLength(1000); e.Property(x => x.BalanceAnchorBase).HasPrecision(28, 8); e.Property(x => x.LedgerNetBase).HasPrecision(28, 8); e.Property(x => x.BalanceDeltaBase).HasPrecision(28, 8); e.HasIndex(x => new { x.AccountId, x.StartedAt }); }); b.Entity(e => { e.ToTable("acc_raw"); e.HasKey(x => x.Id); e.Property(x => x.Id).ValueGeneratedOnAdd(); e.Property(x => x.AccountId).HasMaxLength(30); e.Property(x => x.SourceKind).HasMaxLength(20); e.Property(x => x.Json).HasColumnType("longtext"); e.HasIndex(x => x.IngestRunId); }); b.Entity(e => { e.ToTable("acc_fx_rates"); e.HasKey(x => x.Date); e.Property(x => x.Date).HasColumnType("date"); e.Property(x => x.UsdToEur).HasPrecision(18, 8); e.Property(x => x.Source).HasMaxLength(40); }); } } /// Design-Time-Factory für EF-Tooling (dotnet ef). Connection aus env IBKRTRADER_MYSQL. public class AccountingDbContextFactory : IDesignTimeDbContextFactory { public AccountingDbContext CreateDbContext(string[] args) { var conn = Environment.GetEnvironmentVariable("IBKRTRADER_MYSQL") ?? "Server=localhost;Port=3306;Database=ibkrtrader;User ID=root;Password=;"; var options = new DbContextOptionsBuilder() .UseMySql(conn, DatabaseServerVersion.Value) .Options; return new AccountingDbContext(options); } }