using AssetManager.Data; using AssetManager.Models.DTOs; using AssetManager.Infrastructure.Services; using SqlSugar; using Microsoft.Extensions.Logging; namespace AssetManager.Services; public class PortfolioService : IPortfolioService { private readonly ISqlSugarClient _db; private readonly IMarketDataService _marketDataService; private readonly IExchangeRateService _exchangeRateService; private readonly IPortfolioNavService _navService; private readonly ILogger _logger; public PortfolioService( ISqlSugarClient db, IMarketDataService marketDataService, IExchangeRateService exchangeRateService, IPortfolioNavService navService, ILogger logger) { _db = db; _marketDataService = marketDataService; _exchangeRateService = exchangeRateService; _navService = navService; _logger = logger; } public CreatePortfolioResponse CreatePortfolio(CreatePortfolioRequest request, string userId) { var portfolio = new Portfolio { Id = "port-" + Guid.NewGuid().ToString().Substring(0, 8), UserId = userId, StrategyId = request.StrategyId, Name = request.Name, Currency = request.Currency, TotalValue = (decimal)(request.Stocks?.Sum(s => s.Price * s.Amount) ?? 0), ReturnRate = 0, Status = "运行中", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _db.Insertable(portfolio).ExecuteCommand(); // 如果选择了策略,自动加载策略配置的标的作为初始持仓 var strategyStocks = new List(); if (!string.IsNullOrEmpty(request.StrategyId)) { var Strategy = _db.Queryable() .Where(s => s.Id == request.StrategyId && s.UserId == userId) .First(); if (Strategy != null && !string.IsNullOrEmpty(Strategy.Config)) { try { // 风险平价策略 if (Strategy.Type?.Equals("risk_parity", StringComparison.OrdinalIgnoreCase) == true) { // 处理可能的双层转义 string configJson = Strategy.Config; if (configJson.StartsWith("\"") && configJson.EndsWith("\"")) { // 去掉外层的引号和转义 configJson = System.Text.Json.JsonSerializer.Deserialize(configJson); } var config = System.Text.Json.JsonSerializer.Deserialize(configJson); if (config?.Assets != null) { foreach (var asset in config.Assets) { if (!string.IsNullOrEmpty(asset.Symbol)) { strategyStocks.Add(new StockItem { Code = asset.Symbol, Name = asset.Symbol, Price = 0, // 价格留空,用户后续填写 Amount = 0, // 数量留空,用户后续填写 Currency = request.Currency, AssetType = "Stock" }); } } } } // 其他策略类型可以在这里扩展 } catch (Exception ex) { _logger.LogError(ex, "解析策略配置失败,策略ID: {StrategyId}", request.StrategyId); } } } // 合并用户传入的持仓和策略自动生成的持仓 var allStocks = (request.Stocks ?? new List()).Concat(strategyStocks).DistinctBy(s => s.Code).ToList(); // 创建初始持仓 foreach (var stock in allStocks) { if (stock.Code == null || stock.Name == null) { continue; } // 解析实际买入时间,如果解析失败则用当前时间 DateTime buyTime = DateTime.Now; if (!string.IsNullOrEmpty(stock.Date)) { if (DateTime.TryParse(stock.Date, out var parsedDate)) { buyTime = parsedDate; } } var position = new Position { Id = "pos-" + Guid.NewGuid().ToString().Substring(0, 8), PortfolioId = portfolio.Id, StockCode = stock.Code, StockName = stock.Name, AssetType = string.IsNullOrEmpty(stock.AssetType) ? "Stock" : stock.AssetType, Shares = (decimal)stock.Amount, AvgPrice = (decimal)stock.Price, Currency = request.Currency, CreatedAt = buyTime, UpdatedAt = DateTime.Now }; _db.Insertable(position).ExecuteCommand(); // 创建交易记录 var transaction = new Transaction { Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8), PortfolioId = portfolio.Id, Type = "buy", StockCode = stock.Code, AssetType = string.IsNullOrEmpty(stock.AssetType) ? "Stock" : stock.AssetType, Title = "初始建仓", Amount = (decimal)stock.Amount, Price = (decimal)stock.Price, TotalAmount = (decimal)(stock.Price * stock.Amount), Currency = request.Currency, Status = "completed", Remark = "初始建仓", TransactionTime = buyTime, CreatedAt = DateTime.Now }; _db.Insertable(transaction).ExecuteCommand(); } return new CreatePortfolioResponse { Id = portfolio.Id, TotalValue = (double)portfolio.TotalValue, ReturnRate = 0, Currency = portfolio.Currency, CreatedAt = portfolio.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") }; } public async Task> GetPortfolioListAsync(string userId) { var portfolios = _db.Queryable() .Where(p => p.UserId == userId) .ToList(); if (!portfolios.Any()) { return new List(); } // ===== 第一步:收集所有需要查询价格的股票代码 ===== var portfolioIds = portfolios.Select(p => p.Id).ToList(); var allPositions = _db.Queryable() .Where(pos => portfolioIds.Contains(pos.PortfolioId)) .ToList(); // 去重获取所有股票代码 var stockCodes = allPositions .Where(p => p.StockCode != null) .Select(p => p.StockCode!) .Distinct() .ToList(); // ===== 第二步:批量并行获取价格(利用缓存) ===== var priceDict = new Dictionary(); var priceTasks = stockCodes.Select(async code => { try { var pos = allPositions.First(p => p.StockCode == code); var price = await _marketDataService.GetPriceAsync(code, pos.AssetType ?? "Stock"); return (code, price); } catch (Exception ex) { _logger.LogWarning(ex, "批量获取价格失败: {Code}", code); return (code, null); } }).ToList(); var priceResults = await Task.WhenAll(priceTasks); foreach (var (code, price) in priceResults) { if (price != null) { priceDict[code] = price; } } // ===== 第三步:计算每个组合的数据 ===== var result = new List(); foreach (var p in portfolios) { var positions = allPositions.Where(pos => pos.PortfolioId == p.Id).ToList(); int positionCount = positions.Count; decimal totalValue = 0; decimal totalCost = 0; decimal todayProfit = 0; string portfolioCurrency = p.Currency ?? "CNY"; foreach (var pos in positions) { if (pos.StockCode == null || pos.Currency == null) { continue; } // 从预获取的价格字典中获取 decimal currentPrice = pos.AvgPrice; decimal previousClose = pos.AvgPrice; if (priceDict.TryGetValue(pos.StockCode, out var priceResponse)) { if (priceResponse.Price > 0) { currentPrice = priceResponse.Price; previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : currentPrice; } } decimal positionValue = pos.Shares * currentPrice; decimal positionCost = pos.Shares * pos.AvgPrice; decimal positionTodayProfit = previousClose > 0 ? pos.Shares * (currentPrice - previousClose) : 0; // 汇率转换(汇率服务有缓存,速度很快) var valueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, portfolioCurrency); var costInTarget = await _exchangeRateService.ConvertAmountAsync(positionCost, pos.Currency, portfolioCurrency); var todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(positionTodayProfit, pos.Currency, portfolioCurrency); totalValue += valueInTarget; totalCost += costInTarget; todayProfit += todayProfitInTarget; } double returnRate = totalCost > 0 ? (double)((totalValue - totalCost) / totalCost * 100) : 0; returnRate = Math.Round(returnRate, 2); result.Add(new PortfolioListItem { Id = p.Id, Name = p.Name, Tags = $"{p.Status} · {p.Currency}" + (positionCount > 0 ? $" · {positionCount}只" : ""), Status = p.Status, StatusType = p.Status == "运行中" ? "green" : p.Status == "已暂停" ? "yellow" : "gray", IconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P", IconBgClass = "bg-blue-100", IconTextClass = "text-blue-700", Value = (double)totalValue, Currency = p.Currency, ReturnRate = returnRate, ReturnType = returnRate >= 0 ? "positive" : "negative", TodayProfit = (double)todayProfit, TodayProfitCurrency = p.Currency }); } return result; } public List GetPortfolios(string userId) { return GetPortfolioListAsync(userId).GetAwaiter().GetResult(); } public async Task GetTotalAssetsAsync(string userId) { // 获取用户信息 var user = _db.Queryable() .Where(u => u.Id == userId) .First(); if (user == null) { throw new Exception("User not found"); } string targetCurrency = !string.IsNullOrEmpty(user.DefaultCurrency) ? user.DefaultCurrency : "CNY"; _logger.LogInformation("用户 {UserId} 默认本位币: {Currency}", userId, targetCurrency); // 获取所有组合和持仓 var portfolios = _db.Queryable() .Where(p => p.UserId == userId) .ToList(); if (!portfolios.Any()) { return new TotalAssetsResponse { TotalValue = 0, Currency = targetCurrency, TodayProfit = 0, TodayProfitCurrency = targetCurrency, TotalReturnRate = 0 }; } var portfolioIds = portfolios.Select(p => p.Id).ToList(); var allPositions = _db.Queryable() .Where(pos => portfolioIds.Contains(pos.PortfolioId)) .ToList(); if (!allPositions.Any()) { return new TotalAssetsResponse { TotalValue = 0, Currency = targetCurrency, TodayProfit = 0, TodayProfitCurrency = targetCurrency, TotalReturnRate = 0 }; } // ===== 批量并行获取价格 ===== var stockCodes = allPositions .Where(p => p.StockCode != null) .Select(p => p.StockCode!) .Distinct() .ToList(); var priceDict = new Dictionary(); var priceTasks = stockCodes.Select(async code => { try { var pos = allPositions.First(p => p.StockCode == code); var price = await _marketDataService.GetPriceAsync(code, pos.AssetType ?? "Stock"); return (code, price); } catch (Exception ex) { _logger.LogWarning(ex, "批量获取价格失败: {Code}", code); return (code, null); } }).ToList(); var priceResults = await Task.WhenAll(priceTasks); foreach (var (code, price) in priceResults) { if (price != null) { priceDict[code] = price; } } // ===== 计算总资产 ===== decimal totalValueInTargetCurrency = 0; decimal totalCostInTargetCurrency = 0; decimal totalTodayProfitInTargetCurrency = 0; foreach (var pos in allPositions) { if (pos.StockCode == null || pos.Currency == null) { continue; } decimal currentPrice = pos.AvgPrice; decimal previousClose = pos.AvgPrice; if (priceDict.TryGetValue(pos.StockCode, out var priceResponse)) { if (priceResponse.Price > 0) { currentPrice = priceResponse.Price; previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : currentPrice; } } decimal positionValue = pos.Shares * currentPrice; decimal costValue = pos.Shares * pos.AvgPrice; decimal todayProfit = previousClose > 0 ? pos.Shares * (currentPrice - previousClose) : 0; // 汇率转换 var currentInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency); var costInTarget = await _exchangeRateService.ConvertAmountAsync(costValue, pos.Currency, targetCurrency); var todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(todayProfit, pos.Currency, targetCurrency); totalValueInTargetCurrency += currentInTarget; totalCostInTargetCurrency += costInTarget; totalTodayProfitInTargetCurrency += todayProfitInTarget; } double totalReturnRate = totalCostInTargetCurrency > 0 ? (double)((totalValueInTargetCurrency - totalCostInTargetCurrency) / totalCostInTargetCurrency * 100) : 0; return new TotalAssetsResponse { TotalValue = (double)totalValueInTargetCurrency, Currency = targetCurrency, TodayProfit = (double)totalTodayProfitInTargetCurrency, TodayProfitCurrency = targetCurrency, TotalReturnRate = Math.Round(totalReturnRate, 2) }; } public async Task GetPortfolioByIdAsync(string id, string userId) { var portfolio = _db.Queryable() .Where(p => p.Id == id && p.UserId == userId) .First(); if (portfolio == null) { throw new Exception("Portfolio not found or access denied"); } var Positions = _db.Queryable() .Where(pos => pos.PortfolioId == id) .ToList(); // 获取每个持仓的实时价格并转换为组合本位币 string targetCurrency = portfolio.Currency ?? "CNY"; decimal totalPortfolioValue = 0; decimal totalCost = 0; decimal totalTodayProfit = 0; var positionItems = new List(); foreach (var pos in Positions) { if (pos.StockCode == null || pos.Currency == null) { continue; } // 获取实时价格(自动路由数据源),失败则降级使用成本价 decimal CurrentPrice = pos.AvgPrice; decimal previousClose = pos.AvgPrice; try { var priceResponse = await _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock"); if (priceResponse.Price > 0) { CurrentPrice = priceResponse.Price; previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : CurrentPrice; } } catch (Exception ex) { _logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价作为当前价", pos.StockCode); } decimal positionValue = pos.Shares * CurrentPrice; decimal cost = pos.Shares * pos.AvgPrice; decimal Profit = positionValue - cost; double ProfitRate = cost > 0 ? (double)(Profit / cost * 100) : 0; decimal TodayProfit = previousClose > 0 ? pos.Shares * (CurrentPrice - previousClose) : 0; // 转换为组合本位币 decimal positionValueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency); decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(cost, pos.Currency, targetCurrency); decimal todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(TodayProfit, pos.Currency, targetCurrency); totalPortfolioValue += positionValueInTarget; totalCost += costInTarget; totalTodayProfit += todayProfitInTarget; positionItems.Add(new PositionItem { Id = pos.Id, StockCode = pos.StockCode, StockName = pos.StockName, Symbol = pos.StockCode, Amount = (int)pos.Shares, AveragePrice = (double)pos.AvgPrice, CurrentPrice = (double)CurrentPrice, TotalValue = (double)positionValueInTarget, Profit = (double)(positionValueInTarget - costInTarget), ProfitRate = ProfitRate, ChangeAmount = (double)todayProfitInTarget, Ratio = 0, // 后面统一计算比例 DeviationRatio = 0, // 后续实现 Currency = targetCurrency }); } // 计算每个持仓的比例 foreach (var item in positionItems) { item.Ratio = totalPortfolioValue > 0 ? (item.TotalValue / (double)totalPortfolioValue) * 100 : 0; } decimal TotalReturn = totalPortfolioValue - totalCost; double totalReturnRate = totalCost > 0 ? (double)(TotalReturn / totalCost * 100) : 0; return new PortfolioDetailResponse { Id = portfolio.Id, Name = portfolio.Name, Currency = targetCurrency, Status = portfolio.Status, Strategy = new StrategyInfo { Id = portfolio.StrategyId, Name = "策略名称", Description = "策略描述" }, PortfolioValue = (double)totalPortfolioValue, TotalReturn = (double)TotalReturn, TodayProfit = (double)totalTodayProfit, HistoricalChange = totalReturnRate, DailyVolatility = 0, // 后续实现 TodayProfitCurrency = targetCurrency, LogicModel = "HFEA 风险平价逻辑", LogicModelStatus = "监控中", LogicModelDescription = "目标权重 季度调仓", TotalItems = Positions.Count, TotalRatio = 100.0, Positions = positionItems }; } // 保留同步方法作为兼容(内部调用异步) public PortfolioDetailResponse GetPortfolioById(string id, string userId) { return GetPortfolioByIdAsync(id, userId).GetAwaiter().GetResult(); } public GetTransactionsResponse GetTransactions(string portfolioId, string userId, int limit, int offset) { // 验证投资组合是否属于该用户 var portfolio = _db.Queryable() .Where(p => p.Id == portfolioId && p.UserId == userId) .First(); if (portfolio == null) { throw new Exception("Portfolio not found or access denied"); } var transactions = _db.Queryable() .Where(t => t.PortfolioId == portfolioId) .OrderByDescending(t => t.TransactionTime) .Skip(offset) .Take(limit) .ToList(); var total = _db.Queryable() .Where(t => t.PortfolioId == portfolioId) .Count(); return new GetTransactionsResponse { Items = transactions.Select(t => new TransactionItem { Id = t.Id, PortfolioId = t.PortfolioId, Date = t.TransactionTime.ToString("yyyy-MM-dd"), Time = t.TransactionTime.ToString("HH:mm:ss"), Type = t.Type, Title = t.Title, StockCode = t.StockCode, Amount = (double)t.TotalAmount, Currency = t.Currency, Status = t.Status, Remark = t.Remark }).ToList(), Total = total, Page = offset / limit + 1, PageSize = limit }; } public async Task CreateTransaction(CreateTransactionRequest request, string userId) { // 验证投资组合是否属于该用户 var portfolio = _db.Queryable() .Where(p => p.Id == request.PortfolioId && p.UserId == userId) .First(); if (portfolio == null) { throw new Exception("Portfolio not found or access denied"); } // 校验交易币种必须和组合本位币一致(双重校验) if (!string.IsNullOrEmpty(request.Currency) && !request.Currency.Equals(portfolio.Currency, StringComparison.OrdinalIgnoreCase)) { throw new Exception($"该组合本位币为 {portfolio.Currency},只能添加相同币种的标的"); } // 卖出操作校验 if (request.Type?.ToLower() == "sell") { // 校验是否有该持仓 var existingPosition = _db.Queryable() .Where(pos => pos.PortfolioId == request.PortfolioId && pos.StockCode == request.StockCode) .First(); if (existingPosition == null) { throw new Exception($"该组合中不存在标的 {request.StockCode},无法卖出"); } // 校验卖出数量不超过持仓数量 if (request.Amount > (double)existingPosition.Shares) { throw new Exception($"卖出数量不能超过持仓数量,当前持仓 {existingPosition.Shares} 份"); } } // 解析实际交易时间,如果解析失败则用当前时间 DateTime transactionTime = DateTime.Now; if (!string.IsNullOrEmpty(request.TransactionDate)) { if (DateTime.TryParse(request.TransactionDate, out var parsedDate)) { // 如果只传了日期,时间部分默认用当前时间 transactionTime = parsedDate.Date + DateTime.Now.TimeOfDay; } } else if (!string.IsNullOrEmpty(request.TransactionTime)) { if (DateTime.TryParse(request.TransactionTime, out var parsedTime)) { transactionTime = parsedTime; } } var transaction = new Transaction { Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8), PortfolioId = request.PortfolioId, Type = request.Type, StockCode = request.StockCode, AssetType = string.IsNullOrEmpty(request.AssetType) ? "Stock" : request.AssetType, Title = request.Remark ?? "交易", Amount = (decimal)request.Amount, Price = (decimal)request.Price, TotalAmount = (decimal)(request.Price * request.Amount), Currency = request.Currency, Status = "completed", Remark = request.Remark ?? string.Empty, TransactionTime = transactionTime, CreatedAt = DateTime.Now }; _db.Insertable(transaction).ExecuteCommand(); // 更新持仓 var position = _db.Queryable() .Where(pos => pos.PortfolioId == request.PortfolioId && pos.StockCode == request.StockCode) .First(); if (position != null) { if (request.Type == "buy") { // 计算新的平均价格 var newTotalShares = position.Shares + (decimal)request.Amount; var newTotalCost = (position.Shares * position.AvgPrice) + ((decimal)request.Amount * (decimal)request.Price); position.AvgPrice = newTotalCost / newTotalShares; position.Shares = newTotalShares; position.UpdatedAt = DateTime.Now; _db.Updateable(position).ExecuteCommand(); } else if (request.Type == "sell") { position.Shares -= (decimal)request.Amount; position.UpdatedAt = DateTime.Now; if (position.Shares <= 0) { _db.Deleteable(position).ExecuteCommand(); } else { _db.Updateable(position).ExecuteCommand(); } } } else if (request.Type == "buy") { // 创建新持仓 position = new Position { Id = "pos-" + Guid.NewGuid().ToString().Substring(0, 8), PortfolioId = request.PortfolioId, StockCode = request.StockCode, StockName = request.Remark ?? request.StockCode, AssetType = string.IsNullOrEmpty(request.AssetType) ? "Stock" : request.AssetType, Shares = (decimal)request.Amount, AvgPrice = (decimal)request.Price, Currency = request.Currency, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; _db.Insertable(position).ExecuteCommand(); } // 更新投资组合总价值(使用实时市值而不是成本价) var Positions = _db.Queryable() .Where(pos => pos.PortfolioId == request.PortfolioId) .ToList(); decimal totalPortfolioValue = 0; foreach (var pos in Positions) { if (pos.StockCode == null) { continue; } // 获取实时价格(自动路由数据源),失败则降级使用成本价 decimal CurrentPrice = pos.AvgPrice; try { var priceResponse = _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock").GetAwaiter().GetResult(); if (priceResponse.Price > 0) { CurrentPrice = priceResponse.Price; } } catch (Exception ex) { _logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价计算组合总价值", pos.StockCode); } totalPortfolioValue += pos.Shares * CurrentPrice; } portfolio.TotalValue = totalPortfolioValue; portfolio.UpdatedAt = DateTime.Now; _db.Updateable(portfolio).ExecuteCommand(); // 删除该交易日期之后的净值历史记录,下次请求收益曲线时会自动重新计算 try { var deletedCount = await _navService.DeleteNavHistoryAfterDateAsync(request.PortfolioId, transactionTime.Date); if (deletedCount > 0) { _logger.LogInformation("交易创建后删除净值历史: PortfolioId={PortfolioId}, Date={Date}, Count={Count}", request.PortfolioId, transactionTime.Date, deletedCount); } } catch (Exception ex) { _logger.LogWarning(ex, "删除净值历史失败,将在下次请求时重新计算"); } return new CreateTransactionResponse { Id = transaction.Id, TotalAmount = (double)transaction.TotalAmount, Status = transaction.Status, CreatedAt = transaction.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") }; } // ===== 异步方法实现 ===== public Task CreatePortfolioAsync(CreatePortfolioRequest request, string userId) { return Task.FromResult(CreatePortfolio(request, userId)); } public Task GetPortfolioDetailAsync(string portfolioId, string userId) { return GetPortfolioByIdAsync(portfolioId, userId); } public Task> GetTransactionsAsync(string portfolioId, GetTransactionsRequest request, string userId) { var response = GetTransactions(portfolioId, userId, request.Limit, request.Offset); return Task.FromResult(response.Items ?? new List()); } public async Task CreateTransactionAsync(string portfolioId, CreateTransactionRequest request, string userId) { request.PortfolioId = portfolioId; var response = await CreateTransaction(request, userId); return new TransactionItem { Id = response.Id, PortfolioId = portfolioId, Date = DateTime.Now.ToString("yyyy-MM-dd"), Time = DateTime.Now.ToString("HH:mm:ss"), Type = request.Type, StockCode = request.StockCode, Amount = response.TotalAmount, Currency = request.Currency, Status = response.Status, Remark = request.Remark }; } public async Task UpdatePortfolioAsync(string portfolioId, UpdatePortfolioRequest request, string userId) { var portfolio = _db.Queryable() .Where(p => p.Id == portfolioId && p.UserId == userId) .First(); if (portfolio == null) { return false; } // 更新字段 if (!string.IsNullOrEmpty(request.Name)) { portfolio.Name = request.Name; } // 策略可以为空(解绑策略) portfolio.StrategyId = request.StrategyId; if (!string.IsNullOrEmpty(request.Status)) { portfolio.Status = request.Status; } portfolio.UpdatedAt = DateTime.Now; _db.Updateable(portfolio).ExecuteCommand(); _logger.LogInformation("更新投资组合: {PortfolioId}, 名称: {Name}, 策略: {StrategyId}", portfolioId, portfolio.Name, portfolio.StrategyId ?? "无"); return true; } public async Task DeletePortfolioAsync(string portfolioId, string userId) { var portfolio = _db.Queryable() .Where(p => p.Id == portfolioId && p.UserId == userId) .First(); if (portfolio == null) { return false; } // 删除相关持仓 var Positions = _db.Queryable() .Where(pos => pos.PortfolioId == portfolioId) .ToList(); if (Positions.Any()) { _db.Deleteable(Positions).ExecuteCommand(); } // 删除相关交易 var transactions = _db.Queryable() .Where(t => t.PortfolioId == portfolioId) .ToList(); if (transactions.Any()) { _db.Deleteable(transactions).ExecuteCommand(); } // 删除净值历史 var navHistory = _db.Queryable() .Where(n => n.PortfolioId == portfolioId) .ToList(); if (navHistory.Any()) { _db.Deleteable(navHistory).ExecuteCommand(); } // 删除组合 _db.Deleteable(portfolio).ExecuteCommand(); return true; } }