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)
|
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.Configure<DatabaseOptions>(context.Configuration.GetSection(DatabaseOptions.SectionName));
|
||||||
services.AddSingleton<IMongoDatabase>((IServiceProvider sp) =>
|
services.AddSingleton<IMongoDatabase>((IServiceProvider sp) =>
|
||||||
{
|
{
|
||||||
@@ -41,7 +48,7 @@ internal static class Program
|
|||||||
var client = new MongoClient(dbOptions.ConnectionString);
|
var client = new MongoClient(dbOptions.ConnectionString);
|
||||||
return client.GetDatabase(dbOptions.DatabaseName);
|
return client.GetDatabase(dbOptions.DatabaseName);
|
||||||
});
|
});
|
||||||
services.AddCorePersistence();
|
services.AddCorePersistence(databaseOptions);
|
||||||
services.AddSingleton<IBlockchainWssClientFactory, AlchemyWssClientFactory>();
|
services.AddSingleton<IBlockchainWssClientFactory, AlchemyWssClientFactory>();
|
||||||
services.AddSingleton((IServiceProvider sp) => ServerSettings.Load("server_settings.xml"));
|
services.AddSingleton((IServiceProvider sp) => ServerSettings.Load("server_settings.xml"));
|
||||||
services.AddSingleton<TradingState>();
|
services.AddSingleton<TradingState>();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Database": {
|
"Database": {
|
||||||
|
"Provider": "Mongo",
|
||||||
"ConnectionString": "mongodb://localhost:27017",
|
"ConnectionString": "mongodb://localhost:27017",
|
||||||
"DatabaseName": "PolyTraderDB"
|
"DatabaseName": "PolyTraderDB"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PolyTrader.Core.Configuration;
|
||||||
using PolyTrader.Core.Persistence;
|
using PolyTrader.Core.Persistence;
|
||||||
|
using PolyTrader.Core.Persistence.Ef;
|
||||||
using PolyTrader.Core.Persistence.Mongo;
|
using PolyTrader.Core.Persistence.Mongo;
|
||||||
|
|
||||||
namespace PolyTrader.Core.DependencyInjection
|
namespace PolyTrader.Core.DependencyInjection
|
||||||
@@ -7,15 +11,31 @@ namespace PolyTrader.Core.DependencyInjection
|
|||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registriert die Core-Persistenzschicht (Repository-Interfaces + Mongo-Implementierungen).
|
/// Registriert die Core-Persistenzschicht hinter den Repository-Interfaces.
|
||||||
/// Setzt eine registrierte IMongoDatabase voraus.
|
/// Provider "MySql" -> EF Core (Pomelo) über einen DbContextFactory (thread-safe,
|
||||||
|
/// kurzlebiger Context je Operation). Provider "Mongo" -> Mongo-Implementierungen
|
||||||
|
/// (Übergang / Config-Migration).
|
||||||
/// </summary>
|
/// </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<IAccountRepository, MongoAccountRepository>();
|
||||||
services.AddSingleton<IMarketRepository, MongoMarketRepository>();
|
services.AddSingleton<IMarketRepository, MongoMarketRepository>();
|
||||||
services.AddSingleton<IPositionRepository, MongoPositionRepository>();
|
services.AddSingleton<IPositionRepository, MongoPositionRepository>();
|
||||||
services.AddSingleton<ITradeLogRepository, MongoTradeLogRepository>();
|
services.AddSingleton<ITradeLogRepository, MongoTradeLogRepository>();
|
||||||
|
}
|
||||||
|
|
||||||
return services;
|
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