R3 Slice 1: EF-Core-Infrastruktur im Core + InitialCore-Migration

- Pomelo.EntityFrameworkCore.MySql 8.0.3 + EF Core Design 8.0.11 (Core), NuGet-Allowlist erweitert
- Configuration/DatabaseOptions + DatabaseServerVersion (MariaDB 11.8.6 gepinnt)
- Persistence/Entities: CorePosition, CoreTrade, CoreBudget, CoreWorkerLog, CoreSetting
- Persistence/Ef/CoreDbContext (core_-Tabellen) + Design-Time-Factory (env IBKRTRADER_MYSQL)
- DependencyInjection/AddCorePersistence (AddDbContextFactory), in Program verdrahtet
  (Connection aus appsettings.Local.json)
- EF-Migration InitialCore erzeugt (5 core_-Tabellen)
- Consumer noch auf Dapper (folgt in Slice 2); Build + 30/30 Tests + smoke-ui gruen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-28 10:40:09 +02:00
parent 48c662a699
commit 261032f9f9
12 changed files with 782 additions and 9 deletions
+6
View File
@@ -18,6 +18,12 @@
<package pattern="Dapper" />
<package pattern="HtmlAgilityPack" />
<package pattern="Newtonsoft.Json" />
<!-- EF Core / Pomelo (MySQL/MariaDB) -->
<package pattern="Microsoft.EntityFrameworkCore.*" />
<package pattern="Pomelo.*" />
<package pattern="Humanizer.*" />
<package pattern="Mono.TextTemplating" />
<package pattern="dotnet-ef" />
<!-- Test-Pakete -->
<package pattern="xunit*" />
<package pattern="NSubstitute" />
+11 -3
View File
@@ -1,7 +1,9 @@
using IBKRTrader.Core.AI;
using IBKRTrader.Core.Budget;
using IBKRTrader.Core.Configuration;
using IBKRTrader.Core.Database;
using IBKRTrader.Core.Database.Migrations;
using IBKRTrader.Core.DependencyInjection;
using IBKRTrader.Core.IBKR;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modularity;
@@ -49,7 +51,7 @@ internal static class Program
config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false))
.ConfigureServices((context, services) =>
{
RegisterCoreServices(services);
RegisterCoreServices(services, context.Configuration);
foreach (var module in modules)
{
services.AddSingleton(module);
@@ -77,8 +79,14 @@ internal static class Program
}
/// <summary>Registriert alle Core-Services im DI-Container.</summary>
private static void RegisterCoreServices(IServiceCollection services)
private static void RegisterCoreServices(IServiceCollection services, IConfiguration configuration)
{
// EF-Core-Persistenz (Connection aus appsettings.Local.json).
services.AddCorePersistence(new DatabaseOptions
{
MySqlConnectionString = configuration["Database:MySqlConnectionString"] ?? string.Empty
});
// Settings zuerst laden (eine Quelle, als Singleton weitergereicht).
var settingsService = new SettingsService();
settingsService.Load();
@@ -194,7 +202,7 @@ internal static class Program
config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false))
.ConfigureServices((context, services) =>
{
RegisterCoreServices(services);
RegisterCoreServices(services, context.Configuration);
foreach (var module in modules)
{
services.AddSingleton(module);
+5 -6
View File
@@ -92,12 +92,11 @@ IBKRTrader.App (WinExe, Root) Generic Host + Shell (Launcher) + C
App legt keine Tabellen zur Laufzeit an. Server: **MariaDB 11.8.6** (via `--db-version` bestätigt) →
Pin `new MariaDbServerVersion(new Version(11, 8, 6))`. Verbindung aus `appsettings.Local.json`.
- [x] `--db-version`-Diagnose (Serverversion für den EF-Pin)
- [ ] `Configuration/DatabaseOptions` (aus appsettings.Local.json) + `DatabaseServerVersion`-Pin
- [ ] `AddCorePersistence` + `CoreDbContext` + Entities + EF-Repos (core_position, core_trade_history, core_budget, core_worker_log, core_settings)
- [ ] Consumer umstellen: `PortfolioService`, `BudgetService`, `TradeHistoryService`, `WorkerBase`-Log, IBKR
- [ ] Modul-DbContext (ct_) im CongressTrading-Projekt + EF-Repo
- [ ] EF-Migrationen erzeugen (`dotnet ef migrations add`); Dapper + manuelle Migrationen entfernen
- Hinweis: nur build-verifizierbar (Unit-Tests laufen ohne DB); Schema-Anwendung erfolgt extern
- [x] **Slice 1:** `Configuration/DatabaseOptions` + `DatabaseServerVersion`-Pin (MariaDB 11.8.6); `AddCorePersistence` (`AddDbContextFactory`) + `CoreDbContext` + Entities (core_position, core_trade_history, core_budget, core_worker_log, core_settings) + Design-Time-Factory; **EF-Migration `InitialCore` erzeugt**
- [ ] **Slice 2:** Consumer umstellen: `PortfolioService`, `BudgetService`, `TradeHistoryService`, `WorkerBase`-Log
- [ ] **Slice 3:** Modul-DbContext (ct_) im CongressTrading-Projekt + Umstellung
- [ ] **Slice 4:** Dapper + `DatabaseService` + manuelle Migrationen (CoreMigrations/IBKRMigrations/CongressMigrations) entfernen; IBKR-Marktdaten auf EF
- Hinweis: nur build-verifizierbar (Unit-Tests ohne DB); Schema-Anwendung extern via `dotnet ef database update` (env `IBKRTRADER_MYSQL`)
### R4 Trading-Kern einфügen
- [ ] Risk/Execution/Portfolio/Broker-Seam nach Core/Trading (aus Phase 3 portiert)
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
namespace IBKRTrader.Core.Configuration;
/// <summary>
/// Datenbank-Konfiguration, gebunden an die "Database"-Sektion in appsettings(.Local).json.
/// </summary>
public class DatabaseOptions
{
public const string SectionName = "Database";
/// <summary>MySQL/MariaDB-Connection-String (aus gitignorierter appsettings.Local.json).</summary>
public string MySqlConnectionString { get; set; } = string.Empty;
}
/// <summary>
/// Fest gepinnte Ziel-Server-Version (MariaDB 11.8.6, via <c>--db-version</c> bestätigt). Bewusst
/// gepinnt statt <c>ServerVersion.AutoDetect</c>: AutoDetect öffnet beim Bau der DbContext-Optionen
/// eine blockierende DB-Verbindung ist die DB langsam/nicht erreichbar, hängt der Start. Mit fester
/// Version startet die App unabhängig von der DB, und Migrations-Scaffolding läuft ohne DB-Verbindung.
/// </summary>
public static class DatabaseServerVersion
{
public static ServerVersion Value => new MariaDbServerVersion(new Version(11, 8, 6));
}
@@ -0,0 +1,21 @@
using IBKRTrader.Core.Configuration;
using IBKRTrader.Core.Persistence.Ef;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace IBKRTrader.Core.DependencyInjection;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registriert die Core-Persistenzschicht (EF Core / Pomelo / MariaDB) über einen
/// DbContextFactory (thread-safe, kurzlebiger Context je Operation).
/// </summary>
public static IServiceCollection AddCorePersistence(this IServiceCollection services, DatabaseOptions options)
{
services.AddDbContextFactory<CoreDbContext>(o =>
o.UseMySql(options.MySqlConnectionString, DatabaseServerVersion.Value));
return services;
}
}
@@ -15,6 +15,12 @@
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
<!-- EF Core / Pomelo (MariaDB 11.8.6). EF 8 laeuft auf net10. -->
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<!-- Erlaubt dem Testprojekt, interne Helfer zu testen. -->
@@ -0,0 +1,75 @@
using IBKRTrader.Core.Persistence.Entities;
using Microsoft.EntityFrameworkCore;
namespace IBKRTrader.Core.Persistence.Ef;
/// <summary>
/// EF-Core-Kontext für die Core-Entitäten (core_-Tabellen). Modul-Entitäten liegen in eigenen
/// Modul-DbContexts (gleiche MariaDB, andere Tabellen mit Modul-Präfix).
/// </summary>
public class CoreDbContext : DbContext
{
public CoreDbContext(DbContextOptions<CoreDbContext> options) : base(options) { }
public DbSet<CorePosition> Positions => Set<CorePosition>();
public DbSet<CoreTrade> TradeHistory => Set<CoreTrade>();
public DbSet<CoreBudget> Budgets => Set<CoreBudget>();
public DbSet<CoreWorkerLog> WorkerLog => Set<CoreWorkerLog>();
public DbSet<CoreSetting> Settings => Set<CoreSetting>();
protected override void OnModelCreating(ModelBuilder b)
{
b.Entity<CorePosition>(e =>
{
e.ToTable("core_position");
e.HasKey(x => new { x.Module, x.Symbol });
e.Property(x => x.Module).HasMaxLength(50);
e.Property(x => x.Symbol).HasMaxLength(20);
e.Property(x => x.AvgPrice).HasPrecision(18, 4);
});
b.Entity<CoreTrade>(e =>
{
e.ToTable("core_trade_history");
e.HasKey(x => x.Id);
e.Property(x => x.Module).HasMaxLength(50);
e.Property(x => x.Symbol).HasMaxLength(20);
e.Property(x => x.Action).HasMaxLength(10);
e.Property(x => x.IbkrOrderId).HasMaxLength(100);
e.Property(x => x.Status).HasMaxLength(50);
e.Property(x => x.Quantity).HasPrecision(18, 4);
e.Property(x => x.Price).HasPrecision(18, 4);
e.Property(x => x.TotalValue).HasPrecision(18, 4);
e.HasIndex(x => x.Symbol);
e.HasIndex(x => x.Module);
});
b.Entity<CoreBudget>(e =>
{
e.ToTable("core_budget");
e.HasKey(x => x.Module);
e.Property(x => x.Module).HasMaxLength(50);
e.Property(x => x.TotalBudget).HasPrecision(18, 2);
e.Property(x => x.UsedBudget).HasPrecision(18, 2);
e.Property(x => x.MaxPerTrade).HasPrecision(18, 2);
});
b.Entity<CoreWorkerLog>(e =>
{
e.ToTable("core_worker_log");
e.HasKey(x => x.Id);
e.Property(x => x.WorkerName).HasMaxLength(100);
e.Property(x => x.Module).HasMaxLength(50);
e.Property(x => x.Status).HasMaxLength(20);
e.HasIndex(x => new { x.WorkerName, x.StartedAt });
});
b.Entity<CoreSetting>(e =>
{
e.ToTable("core_settings");
e.HasKey(x => x.Key);
e.Property(x => x.Key).HasMaxLength(100);
e.Property(x => x.Value).HasColumnType("text");
});
}
}
@@ -0,0 +1,25 @@
using IBKRTrader.Core.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace IBKRTrader.Core.Persistence.Ef;
/// <summary>
/// Design-Time-Factory für EF-Tooling (dotnet ef migrations/database). Liest den Connection-String
/// aus der Umgebungsvariable IBKRTRADER_MYSQL, damit keine Zugangsdaten im Repo landen. Nutzt die
/// fest gepinnte Server-Version, sodass Migrations-Scaffolding OHNE DB-Verbindung funktioniert.
/// </summary>
public class CoreDbContextFactory : IDesignTimeDbContextFactory<CoreDbContext>
{
public CoreDbContext CreateDbContext(string[] args)
{
var conn = Environment.GetEnvironmentVariable("IBKRTRADER_MYSQL")
?? "Server=localhost;Port=3306;Database=ibkrtrader;User ID=root;Password=;";
var options = new DbContextOptionsBuilder<CoreDbContext>()
.UseMySql(conn, DatabaseServerVersion.Value)
.Options;
return new CoreDbContext(options);
}
}
@@ -0,0 +1,198 @@
// <auto-generated />
using System;
using IBKRTrader.Core.Persistence.Ef;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IBKRTrader.Core.Persistence.Ef.Migrations
{
[DbContext(typeof(CoreDbContext))]
[Migration("20260728083916_InitialCore")]
partial class InitialCore
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.13")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreBudget", b =>
{
b.Property<string>("Module")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<decimal>("MaxPerTrade")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TotalBudget")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("UsedBudget")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.HasKey("Module");
b.ToTable("core_budget", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CorePosition", b =>
{
b.Property<string>("Module")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Symbol")
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<decimal>("AvgPrice")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Module", "Symbol");
b.ToTable("core_position", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("core_settings", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreTrade", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("IbkrOrderId")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<decimal>("Price")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("Quantity")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<string>("Status")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<decimal>("TotalValue")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<DateTime>("TradedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Module");
b.HasIndex("Symbol");
b.ToTable("core_trade_history", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreWorkerLog", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime?>("FinishedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<DateTime?>("StartedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<string>("WorkerName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.HasKey("Id");
b.HasIndex("WorkerName", "StartedAt");
b.ToTable("core_worker_log", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,157 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IBKRTrader.Core.Persistence.Ef.Migrations
{
/// <inheritdoc />
public partial class InitialCore : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "core_budget",
columns: table => new
{
Module = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TotalBudget = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
UsedBudget = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
MaxPerTrade = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_core_budget", x => x.Module);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "core_position",
columns: table => new
{
Module = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Symbol = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Quantity = table.Column<int>(type: "int", nullable: false),
AvgPrice = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_core_position", x => new { x.Module, x.Symbol });
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "core_settings",
columns: table => new
{
Key = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<string>(type: "text", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_core_settings", x => x.Key);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "core_trade_history",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Module = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Symbol = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Action = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Quantity = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
Price = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
TotalValue = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
TradedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IbkrOrderId = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_core_trade_history", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "core_worker_log",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
WorkerName = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Module = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
StartedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
FinishedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
Status = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Message = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_core_worker_log", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_core_trade_history_Module",
table: "core_trade_history",
column: "Module");
migrationBuilder.CreateIndex(
name: "IX_core_trade_history_Symbol",
table: "core_trade_history",
column: "Symbol");
migrationBuilder.CreateIndex(
name: "IX_core_worker_log_WorkerName_StartedAt",
table: "core_worker_log",
columns: new[] { "WorkerName", "StartedAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "core_budget");
migrationBuilder.DropTable(
name: "core_position");
migrationBuilder.DropTable(
name: "core_settings");
migrationBuilder.DropTable(
name: "core_trade_history");
migrationBuilder.DropTable(
name: "core_worker_log");
}
}
}
@@ -0,0 +1,195 @@
// <auto-generated />
using System;
using IBKRTrader.Core.Persistence.Ef;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IBKRTrader.Core.Persistence.Ef.Migrations
{
[DbContext(typeof(CoreDbContext))]
partial class CoreDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.13")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreBudget", b =>
{
b.Property<string>("Module")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<decimal>("MaxPerTrade")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TotalBudget")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("UsedBudget")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.HasKey("Module");
b.ToTable("core_budget", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CorePosition", b =>
{
b.Property<string>("Module")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Symbol")
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<decimal>("AvgPrice")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Module", "Symbol");
b.ToTable("core_position", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("core_settings", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreTrade", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("IbkrOrderId")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<decimal>("Price")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("Quantity")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<string>("Status")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<decimal>("TotalValue")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<DateTime>("TradedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Module");
b.HasIndex("Symbol");
b.ToTable("core_trade_history", (string)null);
});
modelBuilder.Entity("IBKRTrader.Core.Persistence.Entities.CoreWorkerLog", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime?>("FinishedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<DateTime?>("StartedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<string>("WorkerName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.HasKey("Id");
b.HasIndex("WorkerName", "StartedAt");
b.ToTable("core_worker_log", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,58 @@
namespace IBKRTrader.Core.Persistence.Entities;
/// <summary>Offene Position eines Moduls (Tabelle core_position, PK Module+Symbol).</summary>
public class CorePosition
{
public string Module { get; set; } = "";
public string Symbol { get; set; } = "";
public int Quantity { get; set; }
public decimal AvgPrice { get; set; }
public DateTime UpdatedAt { get; set; }
}
/// <summary>Trade-Historie (Tabelle core_trade_history).</summary>
public class CoreTrade
{
public long Id { get; set; }
public string Module { get; set; } = "";
public string Symbol { get; set; } = "";
public string Action { get; set; } = ""; // BUY / SELL
public decimal Quantity { get; set; }
public decimal Price { get; set; }
public decimal TotalValue { get; set; }
public DateTime TradedAt { get; set; }
public string? IbkrOrderId { get; set; }
public string? Status { get; set; }
public string? Notes { get; set; }
public DateTime CreatedAt { get; set; }
}
/// <summary>Budget je Modul (Tabelle core_budget, PK Module).</summary>
public class CoreBudget
{
public string Module { get; set; } = "";
public decimal TotalBudget { get; set; }
public decimal UsedBudget { get; set; }
public decimal MaxPerTrade { get; set; }
public DateTime UpdatedAt { get; set; }
}
/// <summary>Worker-Lauf-Protokoll (Tabelle core_worker_log).</summary>
public class CoreWorkerLog
{
public long Id { get; set; }
public string WorkerName { get; set; } = "";
public string Module { get; set; } = "Core";
public DateTime? StartedAt { get; set; }
public DateTime? FinishedAt { get; set; }
public string Status { get; set; } = "Running";
public string? Message { get; set; }
}
/// <summary>Key-Value-Einstellungen (Tabelle core_settings, PK Key).</summary>
public class CoreSetting
{
public string Key { get; set; } = "";
public string? Value { get; set; }
public DateTime UpdatedAt { get; set; }
}