using Microsoft.EntityFrameworkCore; using RobotNet10.NavigationTune.Shared.Interfaces; using RobotNet10.NavigationTune.Shared.Models; namespace RobotNet10.NavigationTune.Data; /// /// Repository for test runs /// public class TestRepository(TuningDbContext context) : ITestRepository { public async Task GetByIdAsync(Guid id) { var testRun = await context.TestRuns .Include(r => r.Metrics) .Include(r => r.SafetyViolations) .FirstOrDefaultAsync(r => r.Id == id); if (testRun != null) { // Load scenario and parameter set separately var scenarioEntity = await context.TestScenarios.FindAsync(testRun.ScenarioId); if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = await context.ParameterSets.FindAsync(testRun.ParameterSetId); } return testRun; } public async Task> GetAllAsync() { var testRuns = await context.TestRuns .Include(r => r.Metrics) .OrderByDescending(r => r.StartTime) .ToListAsync(); // Load scenarios and parameter sets foreach (var testRun in testRuns) { var scenarioEntity = await context.TestScenarios.FindAsync(testRun.ScenarioId); if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = await context.ParameterSets.FindAsync(testRun.ParameterSetId); } return testRuns; } public async Task GetCountAsync() { return await context.TestRuns.CountAsync(); } public async Task> GetPagedAsync(int skip, int take) { var testRuns = await context.TestRuns .Include(r => r.Metrics) .OrderByDescending(r => r.StartTime) .Skip(skip) .Take(take) .ToListAsync(); var scenarioIds = testRuns.Select(r => r.ScenarioId).Distinct().ToList(); var parameterSetIds = testRuns.Select(r => r.ParameterSetId).Distinct().ToList(); var scenarios = await context.TestScenarios .Where(s => scenarioIds.Contains(s.Id)) .ToListAsync(); var parameterSets = await context.ParameterSets .Where(p => parameterSetIds.Contains(p.Id)) .ToListAsync(); foreach (var testRun in testRuns) { var scenarioEntity = scenarios.FirstOrDefault(s => s.Id == testRun.ScenarioId); if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = parameterSets.FirstOrDefault(p => p.Id == testRun.ParameterSetId); } return testRuns; } public async Task> GetByScenarioAsync(Guid scenarioId) { var testRuns = await context.TestRuns .Include(r => r.Metrics) .Where(r => r.ScenarioId == scenarioId) .OrderByDescending(r => r.StartTime) .ToListAsync(); // Load scenarios and parameter sets var scenarioEntity = await context.TestScenarios.FindAsync(scenarioId); foreach (var testRun in testRuns) { if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = await context.ParameterSets.FindAsync(testRun.ParameterSetId); } return testRuns; } public async Task> GetByParameterSetAsync(Guid parameterSetId) { var testRuns = await context.TestRuns .Include(r => r.Metrics) .Where(r => r.ParameterSetId == parameterSetId) .OrderByDescending(r => r.StartTime) .ToListAsync(); // Load scenarios and parameter sets var parameterSet = await context.ParameterSets.FindAsync(parameterSetId); foreach (var testRun in testRuns) { var scenarioEntity = await context.TestScenarios.FindAsync(testRun.ScenarioId); if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = parameterSet; } return testRuns; } public async Task> GetByDateRangeAsync(DateTime from, DateTime to) { var testRuns = await context.TestRuns .Include(r => r.Metrics) .Where(r => r.StartTime >= from && r.StartTime <= to) .OrderByDescending(r => r.StartTime) .ToListAsync(); // Load scenarios and parameter sets var scenarioIds = testRuns.Select(r => r.ScenarioId).Distinct().ToList(); var parameterSetIds = testRuns.Select(r => r.ParameterSetId).Distinct().ToList(); var scenarios = await context.TestScenarios .Where(s => scenarioIds.Contains(s.Id)) .ToListAsync(); var parameterSets = await context.ParameterSets .Where(p => parameterSetIds.Contains(p.Id)) .ToListAsync(); foreach (var testRun in testRuns) { var scenarioEntity = scenarios.FirstOrDefault(s => s.Id == testRun.ScenarioId); if (scenarioEntity != null) testRun.Scenario = scenarioEntity.ToTestScenario(); testRun.ParameterSet = parameterSets.FirstOrDefault(p => p.Id == testRun.ParameterSetId); } return testRuns; } public async Task SaveAsync(TestRun testRun) { context.TestRuns.Add(testRun); await context.SaveChangesAsync(); return testRun.Id; } public async Task UpdateAsync(TestRun testRun) { var entry = context.Entry(testRun); if (entry.State == EntityState.Detached) context.TestRuns.Update(testRun); await context.SaveChangesAsync(); } /// /// Update test run from execution result without loading/replacing navigation properties, /// to avoid DbUpdateConcurrencyException when entity was created in another scope. /// public async Task UpdateFromResultAsync( Guid testRunId, TestStatus status, DateTime? endTime, double duration, string? errorMessage, TestMetrics? metrics, List? safetyViolations) { var testRun = await context.TestRuns .AsNoTracking() .FirstOrDefaultAsync(r => r.Id == testRunId); if (testRun == null) return; testRun.Status = status; testRun.EndTime = endTime; testRun.Duration = duration; testRun.ErrorMessage = errorMessage; context.TestRuns.Update(testRun); await context.SaveChangesAsync(); var existingMetrics = await context.TestMetrics.Where(m => m.TestRunId == testRunId).ToListAsync(); if (existingMetrics.Count > 0) context.TestMetrics.RemoveRange(existingMetrics); var existingViolations = await context.SafetyViolations.Where(v => v.TestRunId == testRunId).ToListAsync(); if (existingViolations.Count > 0) context.SafetyViolations.RemoveRange(existingViolations); if (metrics != null) { metrics.TestRunId = testRunId; if (metrics.Id == default) metrics.Id = Guid.NewGuid(); context.TestMetrics.Add(metrics); } if (safetyViolations != null && safetyViolations.Count > 0) { foreach (var v in safetyViolations) { v.TestRunId = testRunId; if (v.Id == default) v.Id = Guid.NewGuid(); } context.SafetyViolations.AddRange(safetyViolations); } await context.SaveChangesAsync(); } public async Task DeleteAsync(Guid id) { var testRun = await GetByIdAsync(id); if (testRun != null) { context.TestRuns.Remove(testRun); await context.SaveChangesAsync(); } } public async Task DeleteManyAsync(IEnumerable ids) { var idList = ids.Distinct().ToList(); if (idList.Count == 0) return; var toRemove = await context.TestRuns .Where(r => idList.Contains(r.Id)) .ToListAsync(); if (toRemove.Count > 0) { context.TestRuns.RemoveRange(toRemove); await context.SaveChangesAsync(); } } }