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:
co-authored by
Claude Opus 4.8
parent
ad9bb684d4
commit
d8cfdbf6be
+8
-1
@@ -34,6 +34,13 @@ internal static class Program
|
||||
|
||||
AppHost = Host.CreateDefaultBuilder().ConfigureServices(delegate(HostBuilderContext context, IServiceCollection services)
|
||||
{
|
||||
var databaseOptions = new DatabaseOptions
|
||||
{
|
||||
Provider = context.Configuration["Database:Provider"] ?? "Mongo",
|
||||
MySqlConnectionString = context.Configuration["Database:MySqlConnectionString"] ?? string.Empty,
|
||||
ConnectionString = context.Configuration["Database:ConnectionString"] ?? "mongodb://localhost:27017",
|
||||
DatabaseName = context.Configuration["Database:DatabaseName"] ?? "PolyTraderDB"
|
||||
};
|
||||
services.Configure<DatabaseOptions>(context.Configuration.GetSection(DatabaseOptions.SectionName));
|
||||
services.AddSingleton<IMongoDatabase>((IServiceProvider sp) =>
|
||||
{
|
||||
@@ -41,7 +48,7 @@ internal static class Program
|
||||
var client = new MongoClient(dbOptions.ConnectionString);
|
||||
return client.GetDatabase(dbOptions.DatabaseName);
|
||||
});
|
||||
services.AddCorePersistence();
|
||||
services.AddCorePersistence(databaseOptions);
|
||||
services.AddSingleton<IBlockchainWssClientFactory, AlchemyWssClientFactory>();
|
||||
services.AddSingleton((IServiceProvider sp) => ServerSettings.Load("server_settings.xml"));
|
||||
services.AddSingleton<TradingState>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Database": {
|
||||
"Provider": "Mongo",
|
||||
"ConnectionString": "mongodb://localhost:27017",
|
||||
"DatabaseName": "PolyTraderDB"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PolyTrader.Core.Configuration;
|
||||
using PolyTrader.Core.Persistence;
|
||||
using PolyTrader.Core.Persistence.Ef;
|
||||
using PolyTrader.Core.Persistence.Mongo;
|
||||
|
||||
namespace PolyTrader.Core.DependencyInjection
|
||||
@@ -7,15 +11,31 @@ namespace PolyTrader.Core.DependencyInjection
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registriert die Core-Persistenzschicht (Repository-Interfaces + Mongo-Implementierungen).
|
||||
/// Setzt eine registrierte IMongoDatabase voraus.
|
||||
/// Registriert die Core-Persistenzschicht hinter den Repository-Interfaces.
|
||||
/// Provider "MySql" -> EF Core (Pomelo) über einen DbContextFactory (thread-safe,
|
||||
/// kurzlebiger Context je Operation). Provider "Mongo" -> Mongo-Implementierungen
|
||||
/// (Übergang / Config-Migration).
|
||||
/// </summary>
|
||||
public static IServiceCollection AddCorePersistence(this IServiceCollection services)
|
||||
public static IServiceCollection AddCorePersistence(this IServiceCollection services, DatabaseOptions options)
|
||||
{
|
||||
if (string.Equals(options.Provider, "MySql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var conn = options.MySqlConnectionString;
|
||||
services.AddDbContextFactory<CoreDbContext>(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn)));
|
||||
|
||||
services.AddSingleton<IAccountRepository, EfAccountRepository>();
|
||||
services.AddSingleton<IMarketRepository, EfMarketRepository>();
|
||||
services.AddSingleton<IPositionRepository, EfPositionRepository>();
|
||||
services.AddSingleton<ITradeLogRepository, EfTradeLogRepository>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IAccountRepository, MongoAccountRepository>();
|
||||
services.AddSingleton<IMarketRepository, MongoMarketRepository>();
|
||||
services.AddSingleton<IPositionRepository, MongoPositionRepository>();
|
||||
services.AddSingleton<ITradeLogRepository, MongoTradeLogRepository>();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user