From f99ae24155a42f10d585e149bbe25c848c749b7e Mon Sep 17 00:00:00 2001 From: AnyTng <44723227+AnyTng@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:04:34 +0000 Subject: [PATCH] Fix N+1 query in ServicoInterno for multas processing Optimizes the `Executar` method in `ServicoInterno` to eagerly load nested entities (`Aluguer`, `Cliente`, `Login`) associated with `Infracoes` using Entity Framework `.Include` and `.ThenInclude`. This eliminates an N+1 query problem where `_context.Aluguers` and `_context.Clientes` were repeatedly queried inside a `foreach` loop. Also introduces a performance baseline unit test. --- backend/RESTful API/Service/ServicoInterno.cs | 15 ++--- .../ServicoInternoTests.cs | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 backend/Tests/CarXPress Unit Tests/ServicoInternoTests.cs diff --git a/backend/RESTful API/Service/ServicoInterno.cs b/backend/RESTful API/Service/ServicoInterno.cs index 0344664..0390fbc 100644 --- a/backend/RESTful API/Service/ServicoInterno.cs +++ b/backend/RESTful API/Service/ServicoInterno.cs @@ -130,22 +130,17 @@ public async Task Executar() var multas = await _context.Infracoes .Include(a => a.AluguerIdaluguerNavigation) + .ThenInclude(a => a.ClienteIdclienteNavigation) + .ThenInclude(c => c.LoginIdloginNavigation) .Where(a => a.EstadoInfracao != "Paga" && a.EstadoInfracao != "Contestação Aceite" && a.EstadoInfracao != "Em Falta") .ToListAsync(); foreach (var multa in multas) { - var alugM = await _context.Aluguers - .Include(a => a.ClienteIdclienteNavigation) - .Where(a => a.Idaluguer == multa.AluguerIdaluguer)/*retorna apenas 1 aluguer*/ - .FirstAsync(); + var alugM = multa.AluguerIdaluguerNavigation; + var clieM = alugM?.ClienteIdclienteNavigation; - var clieM = await _context.Clientes - .Include(c => c.LoginIdloginNavigation) - .Where(c => c.Idcliente == alugM.ClienteIdcliente) - .FirstAsync(); - - if (clieM != null) + if (clieM != null && clieM.LoginIdloginNavigation != null) { var dataLimite = multa.DataInfracao.HasValue ? multa.DataInfracao.Value.AddDays(14).ToString("dd/MM/yyyy") : "N/A"; var dataPag = multa.DataInfracao.HasValue ? multa.DataInfracao.Value.ToString("dd/MM/yyyy") : "N/A"; diff --git a/backend/Tests/CarXPress Unit Tests/ServicoInternoTests.cs b/backend/Tests/CarXPress Unit Tests/ServicoInternoTests.cs new file mode 100644 index 0000000..f3f4c3f --- /dev/null +++ b/backend/Tests/CarXPress Unit Tests/ServicoInternoTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; +using RESTful_API.Models; +using RESTful_API.Service; +using RESTful_API.Interface; + +namespace CarXPress_Unit_Tests +{ + public class ServicoInternoTests + { + [Fact] + public async Task Test_Executar_Performance_Multas() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + using (var context = new PdsContext(options)) + { + // Setup mock data for 1000 multas + var cliente = new Cliente { Idcliente = 1, NomeCliente = "Test User", CodigoPostalCp = 1000, LoginIdloginNavigation = new Login { Email = "test@example.com" } }; + context.Clientes.Add(cliente); + + var veiculo = new Veiculo { Idveiculo = 1, EstadoVeiculo = "Alugado" }; + context.Veiculos.Add(veiculo); + + for (int i = 1; i <= 1000; i++) + { + var aluguer = new Aluguer { Idaluguer = i, ClienteIdcliente = 1, VeiculoIdveiculo = 1, EstadoAluguer = "Alugado" }; + context.Aluguers.Add(aluguer); + + var multa = new Infracao { Idinfracao = i, AluguerIdaluguer = i, EstadoInfracao = "Pendente" }; + context.Infracoes.Add(multa); + } + + await context.SaveChangesAsync(); + } + + using (var context = new PdsContext(options)) + { + var loggerMock = new Mock>(); + var emailServiceMock = new Mock(); + var configMock = new Mock(); + + var service = new ServicoInterno(loggerMock.Object, emailServiceMock.Object, configMock.Object, context); + + var watch = System.Diagnostics.Stopwatch.StartNew(); + + // Act + await service.Executar(); + + watch.Stop(); + + Console.WriteLine($"Executar took {watch.ElapsedMilliseconds} ms"); + } + } + } +}