From 1ad6dd00b6ea2ccf97834815ac98c9b22653a099 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Sun, 29 Dec 2024 17:04:59 +1000 Subject: [PATCH 1/4] Upgrade to .NET 9 and get the NuGets up-to-date --- .github/workflows/build_test.yml | 2 +- Api/Api.csproj | 6 +++--- Database/Database.csproj | 4 ++-- Directory.Build.props | 2 +- Domain.Tests/Domain.Tests.csproj | 6 +++--- Domain/Domain.csproj | 2 +- README.md | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index bcd8d51..e8a0852 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install dotnet-ef run: dotnet tool install --global dotnet-ef diff --git a/Api/Api.csproj b/Api/Api.csproj index 9897cbf..ff360d0 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -6,10 +6,10 @@ - - + + - + diff --git a/Database/Database.csproj b/Database/Database.csproj index 4e8c319..fbd4df0 100644 --- a/Database/Database.csproj +++ b/Database/Database.csproj @@ -6,7 +6,7 @@ - - + + diff --git a/Directory.Build.props b/Directory.Build.props index fe3af31..d5fff65 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net8.0 + net9.0 latest enable strict diff --git a/Domain.Tests/Domain.Tests.csproj b/Domain.Tests/Domain.Tests.csproj index 5e66961..a22cb37 100644 --- a/Domain.Tests/Domain.Tests.csproj +++ b/Domain.Tests/Domain.Tests.csproj @@ -12,16 +12,16 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Domain/Domain.csproj b/Domain/Domain.csproj index 62dcdd2..5a865ae 100644 --- a/Domain/Domain.csproj +++ b/Domain/Domain.csproj @@ -13,7 +13,7 @@ - + diff --git a/README.md b/README.md index 1317416..d6c41bc 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ See "[Pain & Gain of automated tests against SQL (MS SQL or PostgreSQL)](https:/ ### Technologies - Main project: - - [.NET 8](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8); - - [Entity Framework Core 8](https://docs.microsoft.com/en-us/ef/core/) and [dotnet-ef](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) CLI. + - [.NET 9](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview); + - [Entity Framework Core 9](https://docs.microsoft.com/en-us/ef/core/) and [dotnet-ef](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) CLI. - Test project: - [xUnit](https://xunit.net/) + [Respawn](https://github.com/jbogard/Respawn); - [Docker](https://www.docker.com/) + [SQL Server image](https://hub.docker.com/_/microsoft-mssql-server). @@ -34,7 +34,7 @@ See "[Pain & Gain of automated tests against SQL (MS SQL or PostgreSQL)](https:/ ## Getting Started (locally) Firstly, check out this Git repo and install dependencies: - - [.NET SDK](https://dotnet.microsoft.com/download) v8.x; + - [.NET SDK](https://dotnet.microsoft.com/download) v9.x; - [dotnet-ef](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) CLI; - [Docker](https://www.docker.com/). From 984caa338056c0f309857c59038038aab831674f Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Sun, 29 Dec 2024 17:08:45 +1000 Subject: [PATCH 2/4] Forgotten files --- Api/Api.csproj | 2 +- Domain.Tests/Domain.Tests.csproj | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Api/Api.csproj b/Api/Api.csproj index ff360d0..76acf7f 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -10,7 +10,7 @@ - + diff --git a/Domain.Tests/Domain.Tests.csproj b/Domain.Tests/Domain.Tests.csproj index a22cb37..6b2b952 100644 --- a/Domain.Tests/Domain.Tests.csproj +++ b/Domain.Tests/Domain.Tests.csproj @@ -7,13 +7,13 @@ - - - - - + + + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 117538db7d8c246ca0412f53a6c099a0e9c26cb6 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Sun, 29 Dec 2024 17:18:19 +1000 Subject: [PATCH 3/4] Added `AsNoTracking()` where no need in the tracking/caching of DB records --- Domain/Services/Client/ClientCommandService.cs | 2 +- Domain/Services/Client/ClientQueryService.cs | 2 ++ Domain/Services/Invoice/InvoiceCommandService.cs | 4 ++-- Domain/Services/Invoice/InvoiceQueryService.cs | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Domain/Services/Client/ClientCommandService.cs b/Domain/Services/Client/ClientCommandService.cs index d00533b..9cd55fe 100644 --- a/Domain/Services/Client/ClientCommandService.cs +++ b/Domain/Services/Client/ClientCommandService.cs @@ -62,7 +62,7 @@ private async Task UniqueNameCheck(long? id, string name) if (string.IsNullOrWhiteSpace(name)) return IDomainResult.Failed("Name can't be empty"); - var clientsQuery = DataContext.Clients.Where(c => c.Name == name); + var clientsQuery = DataContext.Clients.AsNoTracking().Where(c => c.Name == name); if (id.HasValue) clientsQuery = clientsQuery.Where(c => c.Id != id); diff --git a/Domain/Services/Client/ClientQueryService.cs b/Domain/Services/Client/ClientQueryService.cs index c8b0ac2..41ef050 100644 --- a/Domain/Services/Client/ClientQueryService.cs +++ b/Domain/Services/Client/ClientQueryService.cs @@ -19,6 +19,7 @@ public class ClientQueryService(DataContext dataContext) : BaseService(dataConte { var client = await DataContext.Clients .Include(c => c.Invoices) + .AsNoTracking() .SingleOrDefaultAsync(c => c.Id == clientId); if (client == null) return IDomainResult.NotFound("Client not found"); @@ -31,6 +32,7 @@ public class ClientQueryService(DataContext dataContext) : BaseService(dataConte public async Task GetList(GetClientListRequest filter) { var query = from c in DataContext.Clients.Include(c => c.Invoices) + .AsNoTracking() select new GetClientListResponse( c.Id, c.Name, diff --git a/Domain/Services/Invoice/InvoiceCommandService.cs b/Domain/Services/Invoice/InvoiceCommandService.cs index 78eae73..d338ea2 100644 --- a/Domain/Services/Invoice/InvoiceCommandService.cs +++ b/Domain/Services/Invoice/InvoiceCommandService.cs @@ -73,7 +73,7 @@ private async Task UniqueNumberCheck(string newNumber, bool creat if (string.IsNullOrWhiteSpace(newNumber)) return IDomainResult.Failed("Number can't be empty"); - if (await DataContext.Invoices.Where(c => c.Number == newNumber).AnyAsync() + if (await DataContext.Invoices.AsNoTracking().Where(c => c.Number == newNumber).AnyAsync() && creatingNew) return IDomainResult.Failed($"Invoice number '{newNumber}' already exists"); @@ -82,7 +82,7 @@ private async Task UniqueNumberCheck(string newNumber, bool creat private async Task ClientExistsCheck(long clientId) { - return !await DataContext.Clients.Where(c => c.Id == clientId).AnyAsync() + return !await DataContext.Clients.AsNoTracking().Where(c => c.Id == clientId).AnyAsync() ? IDomainResult.NotFound("Client not found") : IDomainResult.Success(); } diff --git a/Domain/Services/Invoice/InvoiceQueryService.cs b/Domain/Services/Invoice/InvoiceQueryService.cs index 2deb5fe..ae44f27 100644 --- a/Domain/Services/Invoice/InvoiceQueryService.cs +++ b/Domain/Services/Invoice/InvoiceQueryService.cs @@ -19,6 +19,7 @@ public class InvoiceQueryService(DataContext dataContext) : BaseService(dataCont { var invoice = await DataContext.Invoices .Include(i => i.Client) + .AsNoTracking() .SingleOrDefaultAsync(c => c.Number == number); if (invoice == null) return IDomainResult.NotFound("Invoice not found"); @@ -36,6 +37,7 @@ public async Task GetList(GetInvoiceListRequest filter { var query = DataContext.Invoices .Include(i => i.Client) + .AsNoTracking() .Select(i => i); if (filter.ClientId != null) query = query.Where(i => i.ClientId == filter.ClientId); From d05a8e4120f4ead77b928211ab04b732ff6609f9 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Sun, 29 Dec 2024 17:21:04 +1000 Subject: [PATCH 4/4] Made data seeding and assertions in the tests scoped --- .../Client/ClientCreateUpdateDeleteTests.cs | 28 +++++++--- .../Services/Client/ClientQueryTests.cs | 2 +- .../ClientUniqueNameOnCreateUpdateTests.cs | 8 ++- .../Invoice/InvoiceCreateUpdateDeleteTests.cs | 42 +++++++++------ .../Services/Invoice/InvoiceQueryTests.cs | 18 +++---- .../InvoiceUniqueNameOnCreateUpdateTests.cs | 10 ++-- Domain.Tests/TestDbBase.cs | 52 +++++++++++++++---- 7 files changed, 112 insertions(+), 48 deletions(-) diff --git a/Domain.Tests/Services/Client/ClientCreateUpdateDeleteTests.cs b/Domain.Tests/Services/Client/ClientCreateUpdateDeleteTests.cs index 56f14be..605b59f 100644 --- a/Domain.Tests/Services/Client/ClientCreateUpdateDeleteTests.cs +++ b/Domain.Tests/Services/Client/ClientCreateUpdateDeleteTests.cs @@ -24,9 +24,13 @@ public async Task Create_Client_Works() // THEN client appears in the DB Assert.True(result.IsSuccess); - var client = await DataContext.Clients.FindAsync(clientId); - Assert.NotNull(client); - Assert.Equal("Test", client.Name); + await ScopedDataContextExecAsync( + async context => + { + var client = await context.Clients.FindAsync(clientId); + Assert.NotNull(client); + Assert.Equal("Test", client.Name); + }); } [Fact] @@ -40,9 +44,13 @@ public async Task Update_Client_Works() // THEN the name is updated Assert.True(result.IsSuccess); - var client = await DataContext.Clients.FindAsync(clientId); - Assert.NotNull(client); - Assert.Equal("XYZ", client.Name); + await ScopedDataContextExecAsync( + async context => + { + var client = await context.Clients.FindAsync(clientId); + Assert.NotNull(client); + Assert.Equal("XYZ", client.Name); + }); } [Fact] @@ -56,7 +64,11 @@ public async Task Delete_Client_Works() // THEN the client cease to exist Assert.True(result.IsSuccess); - var client = await DataContext.Clients.FindAsync(clientId); - Assert.Null(client); + await ScopedDataContextExecAsync( + async context => + { + var client = await context.Clients.FindAsync(clientId); + Assert.Null(client); + }); } } \ No newline at end of file diff --git a/Domain.Tests/Services/Client/ClientQueryTests.cs b/Domain.Tests/Services/Client/ClientQueryTests.cs index 8255247..da018e7 100644 --- a/Domain.Tests/Services/Client/ClientQueryTests.cs +++ b/Domain.Tests/Services/Client/ClientQueryTests.cs @@ -40,6 +40,6 @@ public async Task Get_Clients_List_Works() // THEN get 2 clients Assert.Equal(2, clients.Length); - Assert.True(new[] {"Name1", "Name2"}.SequenceEqual(clients.OrderBy(c => c.Name).Select(c=>c.Name))); + Assert.Equal(new[] {"Name1", "Name2"}, clients.OrderBy(c => c.Name).Select(c=>c.Name)); } } \ No newline at end of file diff --git a/Domain.Tests/Services/Client/ClientUniqueNameOnCreateUpdateTests.cs b/Domain.Tests/Services/Client/ClientUniqueNameOnCreateUpdateTests.cs index d5d67ab..fce4414 100644 --- a/Domain.Tests/Services/Client/ClientUniqueNameOnCreateUpdateTests.cs +++ b/Domain.Tests/Services/Client/ClientUniqueNameOnCreateUpdateTests.cs @@ -25,8 +25,12 @@ public async Task Create_Client_With_Non_Unique_Name_Fails() // THEN operation fails Assert.False(result.IsSuccess); - var clientCount = await DataContext.Clients.CountAsync(); - Assert.Equal(1, clientCount); + await ScopedDataContextExecAsync( + async context => + { + var clientCount = await context.Clients.CountAsync(); + Assert.Equal(1, clientCount); + }); } [Fact] diff --git a/Domain.Tests/Services/Invoice/InvoiceCreateUpdateDeleteTests.cs b/Domain.Tests/Services/Invoice/InvoiceCreateUpdateDeleteTests.cs index 7947655..c1a3998 100644 --- a/Domain.Tests/Services/Invoice/InvoiceCreateUpdateDeleteTests.cs +++ b/Domain.Tests/Services/Invoice/InvoiceCreateUpdateDeleteTests.cs @@ -24,12 +24,16 @@ public async Task Create_Invoice_Works() // THEN client appears in the DB Assert.True(result.IsSuccess); - var invoice = (await DataContext.Invoices.FindAsync(invoiceId))!; - Assert.NotNull(invoice); - Assert.Equal("INV-01", invoice.Number); - Assert.Equal(DateOnly.Parse("2020-07-07"), invoice.Date); - Assert.Equal(clientId, invoice.ClientId); - Assert.Equal(20, invoice.Amount); + await ScopedDataContextExecAsync( + async context => + { + var invoice = await context.Invoices.FindAsync(invoiceId); + Assert.NotNull(invoice); + Assert.Equal("INV-01", invoice.Number); + Assert.Equal(DateOnly.Parse("2020-07-07"), invoice.Date); + Assert.Equal(clientId, invoice.ClientId); + Assert.Equal(20, invoice.Amount); + }); } [Fact] @@ -37,16 +41,20 @@ public async Task Update_Invoice_Works() { // GIVEN a client & an invoice var clientId = await SeedClient("Name"); - var (invoiceId, _) = await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), clientId, 20)); + await SeedInvoice("INV-01", clientId, DateOnly.Parse("2020-07-07"), 20); // WHEN update amount of the invoice - var result = await InvoiceCommandService.Update(invoiceId, new UpdateInvoiceRequest(DateOnly.Parse("2020-07-07"), 30)); + var result = await InvoiceCommandService.Update("INV-01", new UpdateInvoiceRequest(DateOnly.Parse("2020-07-07"), 30)); // THEN the amount is updated Assert.True(result.IsSuccess); - var invoice = (await DataContext.Invoices.FindAsync(invoiceId))!; - Assert.NotNull(invoice); - Assert.Equal(30, invoice.Amount); + await ScopedDataContextExecAsync( + async context => + { + var invoice = await context.Invoices.FindAsync("INV-01"); + Assert.NotNull(invoice); + Assert.Equal(30, invoice.Amount); + }); } [Fact] @@ -54,14 +62,18 @@ public async Task Delete_Invoice_Works() { // GIVEN a client & an invoice var clientId = await SeedClient("Name"); - var (invoiceId, _) = await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), clientId, 20)); + await SeedInvoice("INV-01", clientId, DateOnly.Parse("2020-07-07"), 20); // WHEN delete the invoice - var result = await InvoiceCommandService.Delete(invoiceId); + var result = await InvoiceCommandService.Delete("INV-01"); // THEN the invoice cease to exist Assert.True(result.IsSuccess); - var client = await DataContext.Invoices.FindAsync(invoiceId); - Assert.Null(client); + await ScopedDataContextExecAsync( + async context => + { + var client = await context.Invoices.FindAsync("INV-01"); + Assert.Null(client); + }); } } \ No newline at end of file diff --git a/Domain.Tests/Services/Invoice/InvoiceQueryTests.cs b/Domain.Tests/Services/Invoice/InvoiceQueryTests.cs index 664ea11..0cf175c 100644 --- a/Domain.Tests/Services/Invoice/InvoiceQueryTests.cs +++ b/Domain.Tests/Services/Invoice/InvoiceQueryTests.cs @@ -18,10 +18,10 @@ public async Task Get_Invoice_By_Number_Works() { // GIVEN a client & an invoice var clientId = await SeedClient("Name"); - var (invoiceId, _) = await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), clientId, 20)); + await SeedInvoice("INV-01", clientId, DateOnly.Parse("2020-07-07"), 20); // WHEN get invoice by number - var (invoice, result) = await InvoiceQueryService.GetByNumber(invoiceId); + var (invoice, result) = await InvoiceQueryService.GetByNumber("INV-01"); // THEN invoice gets resolved Assert.True(result.IsSuccess); @@ -33,8 +33,8 @@ public async Task Get_Invoice_List_Works() { // GIVEN a client & 2 invoices var clientId = await SeedClient("Name"); - await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), clientId, 10)); - await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-02", DateOnly.Parse("2020-08-07"), clientId, 20)); + await SeedInvoice("INV-01", clientId, DateOnly.Parse("2020-07-07"), 10); + await SeedInvoice("INV-02", clientId, DateOnly.Parse("2020-08-07"), 20); // WHEN get a list of invoices var invoices = await InvoiceQueryService.GetList(new GetInvoiceListRequest()); @@ -42,9 +42,9 @@ public async Task Get_Invoice_List_Works() // THEN get 2 invoices Assert.Equal(2, invoices.Length); var orderedList = invoices.OrderBy(c => c.Number).ToArray(); - Assert.True(new[] {"INV-01", "INV-02"}.SequenceEqual(orderedList.Select(c => c.Number))); - Assert.True(new[] {DateOnly.Parse("2020-07-07"), DateOnly.Parse("2020-08-07")}.SequenceEqual(orderedList.Select(c => c.Date))); - Assert.True(new[] {10m, 20m}.SequenceEqual(orderedList.Select(c => c.Amount))); + Assert.Equal(new[] {"INV-01", "INV-02"}, orderedList.Select(c => c.Number)); + Assert.Equal(new[] {DateOnly.Parse("2020-07-07"), DateOnly.Parse("2020-08-07")}, orderedList.Select(c => c.Date)); + Assert.Equal(new[] {10m, 20m}, orderedList.Select(c => c.Amount)); } [Fact] @@ -52,10 +52,10 @@ public async Task Get_Invoice_List_Filtered_By_Client_Works() { // GIVEN a client 1 with an invoice var client1Id = await SeedClient("Homer Simpson"); - await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), client1Id, 10)); + await SeedInvoice("INV-01", client1Id, DateOnly.Parse("2020-07-07"), 10); // and a client 2 with an invoice var client2Id = await SeedClient("Marge Simpson"); - await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-02", DateOnly.Parse("2020-08-07"), client2Id, 20)); + await SeedInvoice("INV-02", client2Id, DateOnly.Parse("2020-08-07"), 20); // WHEN get a list of invoices for client 1 var invoices = await InvoiceQueryService.GetList(new GetInvoiceListRequest(client1Id)); diff --git a/Domain.Tests/Services/Invoice/InvoiceUniqueNameOnCreateUpdateTests.cs b/Domain.Tests/Services/Invoice/InvoiceUniqueNameOnCreateUpdateTests.cs index a3e7a33..f54c7b0 100644 --- a/Domain.Tests/Services/Invoice/InvoiceUniqueNameOnCreateUpdateTests.cs +++ b/Domain.Tests/Services/Invoice/InvoiceUniqueNameOnCreateUpdateTests.cs @@ -19,14 +19,18 @@ public async Task Create_Invoice_With_Non_Unique_Number_Fails() { // GIVEN a client & an invoice var clientId = await SeedClient("Name"); - await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-07-07"), clientId, 20)); + await SeedInvoice("INV-01", clientId, DateOnly.Parse("2020-07-07"), 20); // WHEN create a new invoice with the same number var (_, result) = await InvoiceCommandService.Create(new CreateInvoiceRequest("INV-01", DateOnly.Parse("2020-08-01"), clientId, 10)); // THEN operation fails Assert.False(result.IsSuccess); - var invoiceCount = await DataContext.Invoices.CountAsync(); - Assert.Equal(1, invoiceCount); + await ScopedDataContextExecAsync( + async context => + { + var invoiceCount = await context.Invoices.CountAsync(); + Assert.Equal(1, invoiceCount); + }); } } \ No newline at end of file diff --git a/Domain.Tests/TestDbBase.cs b/Domain.Tests/TestDbBase.cs index 2e41c32..2cf0421 100644 --- a/Domain.Tests/TestDbBase.cs +++ b/Domain.Tests/TestDbBase.cs @@ -20,10 +20,10 @@ namespace AK.DbSample.Domain.Tests; /// Base test class with a DI container and DB connection. /// Derive from it when need DI and DB connection /// -public abstract class TestDbBase(ITestOutputHelper output) : TestBase, IAsyncLifetime +// ReSharper disable once InconsistentNaming +public abstract class TestDbBase(ITestOutputHelper _output) : TestBase, IAsyncLifetime { - protected DataContext DataContext => Container.GetRequiredService(); - protected readonly ITestOutputHelper Output = output; + private DataContext DataContext => Container.GetRequiredService(); /// /// Tables that shouldn't be touched on whipping out the DB @@ -42,12 +42,21 @@ protected override void ConfigureIocContainer(IServiceCollection services) base.ConfigureIocContainer(services); } - protected async Task SeedData(params object[] entities) - { - await DataContext.AddRangeAsync(entities); - await DataContext.SaveChangesAsync(); - } + /// + /// Seed an invoice in the DB + /// + protected Task SeedInvoice(string number, long clientId, DateOnly date, decimal amount) + => SeedData (new Invoice + { + Number = number, + ClientId = clientId, + Amount = amount, + Date = date + }); + /// + /// Seed a client in the DB + /// protected async Task SeedClient(string name = "Test Client 1") { await SeedData(new Client { Name = name }); @@ -91,12 +100,12 @@ private async Task WipeOutDbAsync() } catch (ArgumentNullException ergExc) { - Output.WriteLine(ergExc.Message); + _output.WriteLine(ergExc.Message); throw; } catch(Exception e) { - Output.WriteLine(e.Message +" \n"+ (respawn?.DeleteSql ?? "no delete SQL")); + _output.WriteLine(e.Message +" \n"+ (respawn?.DeleteSql ?? "no delete SQL")); throw; } } @@ -110,6 +119,29 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + /// + /// Perform on a new scope of DataContext, to avoid skewed test results due to the EF cache. + /// + /// The function to perform on the newly created DAtaContext scope + protected async Task ScopedDataContextExecAsync(Func dataContextFunc) + { + using var assertionScope = Container.CreateScope(); + await using var dataContext = assertionScope.ServiceProvider.GetRequiredService(); + { + // Disable change tracking + dataContext.ChangeTracker.AutoDetectChangesEnabled = false; + await dataContextFunc(dataContext); + } + } + + private Task SeedData(params object[] entities) + => ScopedDataContextExecAsync( + async context => + { + await context.AddRangeAsync(entities); + await context.SaveChangesAsync(); + }); private string GetSqlConnectionStringFromConfiguration() {