Phase 6 (Stufe 2): Core-EF-Repos + umschaltbare Registrierung

- EF-Implementierungen hinter den Core-Interfaces: EfAccountRepository,
  EfMarketRepository, EfPositionRepository, EfTradeLogRepository. Thread-safe via
  IDbContextFactory<CoreDbContext> (kurzlebiger Context je Operation).
- AddCorePersistence(DatabaseOptions): Provider "MySql" -> EF + DbContextFactory,
  "Mongo" -> Mongo-Repos. Program.cs uebergibt die aufgeloesten Optionen.
- appsettings.json Provider-Default = "Mongo" (App bleibt vorerst auf MongoDB,
  kein Verhaltenswechsel). MySQL-Connection liegt in gitignorierter appsettings.Local.json.
- Build 0 Fehler.

Naechste Stufen: Modul-Context + EF-Repos (inkl. trackers/mt_history), Config-Migration
Mongo->MySQL, dann Provider umschalten + Mongo entfernen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-05 13:43:34 +02:00
co-authored by Claude Opus 4.8
parent ad9bb684d4
commit d8cfdbf6be
7 changed files with 261 additions and 8 deletions
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Ef
{
public class EfAccountRepository : IAccountRepository
{
private readonly IDbContextFactory<CoreDbContext> _factory;
public EfAccountRepository(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
public List<AccountState> GetAll()
{
using var ctx = _factory.CreateDbContext();
return ctx.Accounts.AsNoTracking().ToList();
}
public void Upsert(AccountState account)
{
using var ctx = _factory.CreateDbContext();
var existing = ctx.Accounts.Find(account.AccountId);
if (existing == null)
ctx.Accounts.Add(account);
else
ctx.Entry(existing).CurrentValues.SetValues(account);
ctx.SaveChanges();
}
public void Delete(int accountId)
{
using var ctx = _factory.CreateDbContext();
var existing = ctx.Accounts.Find(accountId);
if (existing != null)
{
ctx.Accounts.Remove(existing);
ctx.SaveChanges();
}
}
}
}
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Ef
{
public class EfMarketRepository : IMarketRepository
{
private readonly IDbContextFactory<CoreDbContext> _factory;
public EfMarketRepository(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
public MarketData? GetById(string id)
{
using var ctx = _factory.CreateDbContext();
return ctx.Markets.AsNoTracking().FirstOrDefault(x => x.Id == id);
}
public MarketData? FindByTokenId(string tokenId)
{
using var ctx = _factory.CreateDbContext();
return ctx.Markets.AsNoTracking()
.FirstOrDefault(x => x.ClobTokenIds != null && x.ClobTokenIds.Contains(tokenId));
}
public List<MarketData> GetActive()
{
using var ctx = _factory.CreateDbContext();
return ctx.Markets.AsNoTracking().Where(x => !x.Closed).ToList();
}
public void Upsert(MarketData market)
{
using var ctx = _factory.CreateDbContext();
var existing = ctx.Markets.Find(market.Id);
if (existing == null)
ctx.Markets.Add(market);
else
ctx.Entry(existing).CurrentValues.SetValues(market);
ctx.SaveChanges();
}
public void Insert(MarketData market)
{
using var ctx = _factory.CreateDbContext();
ctx.Markets.Add(market);
ctx.SaveChanges();
}
public void Update(MarketData market)
{
using var ctx = _factory.CreateDbContext();
var existing = ctx.Markets.Find(market.Id);
if (existing != null)
{
ctx.Entry(existing).CurrentValues.SetValues(market);
ctx.SaveChanges();
}
}
// Indizes werden über die EF-Migration erstellt.
public void EnsureIndexes() { }
}
}
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Ef
{
public class EfPositionRepository : IPositionRepository
{
private readonly IDbContextFactory<CoreDbContext> _factory;
public EfPositionRepository(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
public List<Position> GetLive(int accountId)
{
using var ctx = _factory.CreateDbContext();
return ctx.Positions.AsNoTracking().Where(x => x.AccountId == accountId && !x.IsDemo).ToList();
}
public List<Position> GetDemo(int accountId)
{
using var ctx = _factory.CreateDbContext();
return ctx.Positions.AsNoTracking().Where(x => x.AccountId == accountId && x.IsDemo).ToList();
}
public Position? FindLive(int accountId, string tokenId)
{
using var ctx = _factory.CreateDbContext();
return ctx.Positions.AsNoTracking().FirstOrDefault(x => x.AccountId == accountId && !x.IsDemo && x.TokenId == tokenId);
}
public Position? FindDemo(int accountId, string tokenId)
{
using var ctx = _factory.CreateDbContext();
return ctx.Positions.AsNoTracking().FirstOrDefault(x => x.AccountId == accountId && x.IsDemo && x.TokenId == tokenId);
}
public void UpsertLive(int accountId, Position position) => Upsert(accountId, false, position);
public void UpsertDemo(int accountId, Position position) => Upsert(accountId, true, position);
private void Upsert(int accountId, bool isDemo, Position position)
{
position.AccountId = accountId;
position.IsDemo = isDemo;
using var ctx = _factory.CreateDbContext();
var existing = ctx.Positions.Find(accountId, isDemo, position.TokenId);
if (existing == null)
ctx.Positions.Add(position);
else
ctx.Entry(existing).CurrentValues.SetValues(position);
ctx.SaveChanges();
}
public void DeleteLive(int accountId, string tokenId) => Delete(accountId, false, tokenId);
public void DeleteDemo(int accountId, string tokenId) => Delete(accountId, true, tokenId);
private void Delete(int accountId, bool isDemo, string tokenId)
{
using var ctx = _factory.CreateDbContext();
var existing = ctx.Positions.Find(accountId, isDemo, tokenId);
if (existing != null)
{
ctx.Positions.Remove(existing);
ctx.SaveChanges();
}
}
public void DropDemo(int accountId)
{
using var ctx = _factory.CreateDbContext();
var rows = ctx.Positions.Where(x => x.AccountId == accountId && x.IsDemo).ToList();
if (rows.Count > 0)
{
ctx.Positions.RemoveRange(rows);
ctx.SaveChanges();
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Ef
{
public class EfTradeLogRepository : ITradeLogRepository
{
private readonly IDbContextFactory<CoreDbContext> _factory;
public EfTradeLogRepository(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
// Indizes werden über die EF-Migration erstellt.
public void EnsureIndexes() { }
public void Insert(TradeRecord record)
{
using var ctx = _factory.CreateDbContext();
ctx.TradeLog.Add(record);
ctx.SaveChanges();
}
public List<TradeRecord> GetRecent(int limit)
{
using var ctx = _factory.CreateDbContext();
return ctx.TradeLog.AsNoTracking().OrderByDescending(x => x.ClosedAt).Take(limit).ToList();
}
public List<TradeRecord> Find(Expression<Func<TradeRecord, bool>> predicate)
{
using var ctx = _factory.CreateDbContext();
return ctx.TradeLog.AsNoTracking().Where(predicate).ToList();
}
}
}