diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index f941893..bcd8d51 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: 7.0.x + dotnet-version: 8.0.x - name: Install dotnet-ef run: dotnet tool install --global dotnet-ef diff --git a/Api/Api.csproj b/Api/Api.csproj index 43bcf47..9897cbf 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -6,14 +6,11 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + diff --git a/Api/Configuration/AddAndConfigureSwagger.cs b/Api/Configuration/AddAndConfigureSwagger.cs index 6d10747..7e5c00f 100644 --- a/Api/Configuration/AddAndConfigureSwagger.cs +++ b/Api/Configuration/AddAndConfigureSwagger.cs @@ -2,14 +2,15 @@ namespace AK.DbSample.Api.Configuration; internal static partial class ServiceCollectionExtensions { - public static void AddAndConfigureSwagger(this IServiceCollection services, IWebHostEnvironment env) + public static IServiceCollection AddAndConfigureSwagger(this IServiceCollection services, IWebHostEnvironment env) { if (env.IsProduction()) // No Swagger for PROD - return; + return services; - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(c => c.UseDateOnlyTimeOnlyStringConverters()); + return services + .AddEndpointsApiExplorer() + .AddSwaggerGen(); } public static void AddAppSwaggerUi(this IApplicationBuilder app, IWebHostEnvironment env) diff --git a/Api/Startup.cs b/Api/Startup.cs index 0f9121b..61d3e38 100644 --- a/Api/Startup.cs +++ b/Api/Startup.cs @@ -18,18 +18,10 @@ public Startup(IConfiguration config, IWebHostEnvironment hostEnvironment) public void ConfigureServices(IServiceCollection services) { var settings = services.AddAndConfigureAppSettings(_configuration); - - services.AddAndConfigureDomainServices((settings.ConnectionString, true)); - - // The below converters were required for .NET 6. Since .NET 7 they work out-of-the-box, - // Swashbuckle is still lacking behind with the standard support, so need to keep these lines - // till Swashbuckle NuGet gets updated. - // Note: It'll also eliminate the need in DateOnlyTimeOnly.AspNet NuGet (https://github.com/maxkoshevoi/DateOnlyTimeOnly.AspNet) - services - .AddControllers(options => options.UseDateOnlyTimeOnlyStringConverters()) - .AddJsonOptions(options => options.UseDateOnlyTimeOnlyStringConverters()); - services.AddAndConfigureSwagger(_hostingEnvironment); + services.AddAndConfigureDomainServices((settings.ConnectionString, true)) + .AddAndConfigureSwagger(_hostingEnvironment) + .AddControllers(); } public void Configure(IApplicationBuilder app) diff --git a/Domain/Configuration/AddAndConfigureDbContext.cs b/Database/Configuration/AddAndConfigureDbContext.cs similarity index 94% rename from Domain/Configuration/AddAndConfigureDbContext.cs rename to Database/Configuration/AddAndConfigureDbContext.cs index 7969e18..dc96e61 100644 --- a/Domain/Configuration/AddAndConfigureDbContext.cs +++ b/Database/Configuration/AddAndConfigureDbContext.cs @@ -1,9 +1,7 @@ -using AK.DbSample.Database; - using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -namespace AK.DbSample.Domain.Configuration; +namespace AK.DbSample.Database.Configuration; public static partial class ServiceCollectionExtensions { diff --git a/Database/DataContext.cs b/Database/DataContext.cs index 18899b4..53672c0 100644 --- a/Database/DataContext.cs +++ b/Database/DataContext.cs @@ -1,5 +1,4 @@ using AK.DbSample.Database.Entities; -using AK.DbSample.Database.Infrastructure; using Microsoft.EntityFrameworkCore; @@ -20,8 +19,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { // Date is a DateOnly property and date on database builder.Property(x => x.Date) - // These converters are still required in EF 7 (https://github.com/dotnet/efcore/issues/24507) - .HasConversion(); + .HasColumnType("date"); // Set cascade delete builder.HasOne(p => p.Client) diff --git a/Database/Database.csproj b/Database/Database.csproj index 6ccf22a..4e8c319 100644 --- a/Database/Database.csproj +++ b/Database/Database.csproj @@ -6,8 +6,7 @@ - - - + + diff --git a/Database/Infrastructure/DateOnlyComparer.cs b/Database/Infrastructure/DateOnlyComparer.cs deleted file mode 100644 index 6b76fde..0000000 --- a/Database/Infrastructure/DateOnlyComparer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.ChangeTracking; - -namespace AK.DbSample.Database.Infrastructure; - -/// -/// Compares . -/// -public class DateOnlyComparer : ValueComparer -{ - /// - /// Creates a new instance of this converter. - /// - public DateOnlyComparer() : base( - (d1, d2) => d1 == d2 && d1.DayNumber == d2.DayNumber, - d => d.GetHashCode()) {} -} - -/// -/// Compares . -/// -public class NullableDateOnlyComparer : ValueComparer -{ - /// - /// Creates a new instance of this converter. - /// - public NullableDateOnlyComparer() : base( - (d1, d2) => d1 == d2 && d1.GetValueOrDefault().DayNumber == d2.GetValueOrDefault().DayNumber, - d => d.GetHashCode()) {} -} \ No newline at end of file diff --git a/Database/Infrastructure/DateOnlyConverter.cs b/Database/Infrastructure/DateOnlyConverter.cs deleted file mode 100644 index 12925af..0000000 --- a/Database/Infrastructure/DateOnlyConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace AK.DbSample.Database.Infrastructure; - -/// -/// Converts to and vice versa. -/// -public class DateOnlyConverter : ValueConverter -{ - /// - /// Creates a new instance of this converter. - /// - public DateOnlyConverter() : base( - d => d.ToDateTime(TimeOnly.MinValue), - d => DateOnly.FromDateTime(d)) { } -} - -/// -/// Converts to and vice versa. -/// -public class NullableDateOnlyConverter : ValueConverter -{ - /// - /// Creates a new instance of this converter. - /// - public NullableDateOnlyConverter() : base( - d => d == null - ? null - : new DateTime?(d.Value.ToDateTime(TimeOnly.MinValue)), - d => d == null - ? null - : new DateOnly?(DateOnly.FromDateTime(d.Value))) - { } -} \ No newline at end of file diff --git a/Database/Migrations/20220322114144_InititalCreate.cs b/Database/Migrations/20220322114144_InititalCreate.cs index 3456329..f2f6cdd 100644 --- a/Database/Migrations/20220322114144_InititalCreate.cs +++ b/Database/Migrations/20220322114144_InititalCreate.cs @@ -28,7 +28,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Number = table.Column(type: "nvarchar(100)", nullable: false), ClientId = table.Column(type: "bigint", nullable: false), - Date = table.Column(type: "datetime", nullable: false), + Date = table.Column(type: "date", nullable: false), Amount = table.Column(type: "decimal(18,2)", nullable: false) }, constraints: table => diff --git a/DbSample.csproj b/DbSample.csproj deleted file mode 100644 index 27f4eb5..0000000 --- a/DbSample.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - net6.0 - enable - enable - AK.DbSample.DbSample - AK.DbSample.DbSample - - - diff --git a/Directory.Build.props b/Directory.Build.props index 36a00cc..fe3af31 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net7.0 + net8.0 latest enable strict diff --git a/Domain.Tests/Domain.Tests.csproj b/Domain.Tests/Domain.Tests.csproj index 008a5fc..5e66961 100644 --- a/Domain.Tests/Domain.Tests.csproj +++ b/Domain.Tests/Domain.Tests.csproj @@ -7,23 +7,21 @@ - - - - - - - - - + + + + + + + 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.Tests/TestDbBase.cs b/Domain.Tests/TestDbBase.cs index a0e543b..2e41c32 100644 --- a/Domain.Tests/TestDbBase.cs +++ b/Domain.Tests/TestDbBase.cs @@ -1,6 +1,6 @@ using AK.DbSample.Database; +using AK.DbSample.Database.Configuration; using AK.DbSample.Database.Entities; -using AK.DbSample.Domain.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -20,21 +20,16 @@ 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 : TestBase, IAsyncLifetime +public abstract class TestDbBase(ITestOutputHelper output) : TestBase, IAsyncLifetime { protected DataContext DataContext => Container.GetRequiredService(); - protected readonly ITestOutputHelper Output; + protected readonly ITestOutputHelper Output = output; /// /// Tables that shouldn't be touched on whipping out the DB /// - private readonly Table[] _tablesToIgnore = { Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.DefaultTableName /* "__EFMigrationsHistory" */ }; + private readonly Table[] _tablesToIgnore = [Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.DefaultTableName /* "__EFMigrationsHistory" */]; - protected TestDbBase(ITestOutputHelper output) - { - Output = output; - } - /// /// Configure the DI container /// @@ -83,7 +78,7 @@ public async Task InitializeAsync() /// /// Wipe out all data in the database /// - protected async Task WipeOutDbAsync() + private async Task WipeOutDbAsync() { Respawner? respawn = null; try diff --git a/Domain/Configuration/AddAndConfigureDomainServices.cs b/Domain/Configuration/AddAndConfigureDomainServices.cs index 58b93c9..07a3e29 100644 --- a/Domain/Configuration/AddAndConfigureDomainServices.cs +++ b/Domain/Configuration/AddAndConfigureDomainServices.cs @@ -1,3 +1,4 @@ +using AK.DbSample.Database.Configuration; using AK.DbSample.Domain.Helpers; using AK.DbSample.Domain.Services; @@ -5,9 +6,9 @@ namespace AK.DbSample.Domain.Configuration; -public static partial class ServiceCollectionExtensions +public static class ServiceCollectionExtensions { - public static void AddAndConfigureDomainServices(this IServiceCollection services, (string? connectionString, bool registerMigrationsAssembly)? configureDatabase = null) + public static IServiceCollection AddAndConfigureDomainServices(this IServiceCollection services, (string? connectionString, bool registerMigrationsAssembly)? configureDatabase = null) { if (configureDatabase.HasValue) services.AddAndConfigureDbContext(configureDatabase.Value.connectionString, configureDatabase.Value.registerMigrationsAssembly); @@ -19,6 +20,6 @@ public static void AddAndConfigureDomainServices(this IServiceCollection service && t.IsAssignableTo() // All services ).ToList(); - services.RegisterAsImplementedInterfaces(types, ServiceLifetime.Scoped); + return services.RegisterAsImplementedInterfaces(types, ServiceLifetime.Scoped); } } \ No newline at end of file diff --git a/Domain/Domain.csproj b/Domain/Domain.csproj index 6411d1a..62dcdd2 100644 --- a/Domain/Domain.csproj +++ b/Domain/Domain.csproj @@ -7,13 +7,13 @@ + all - - + diff --git a/Domain/Services/BaseService.cs b/Domain/Services/BaseService.cs index 531887a..b05b170 100644 --- a/Domain/Services/BaseService.cs +++ b/Domain/Services/BaseService.cs @@ -2,12 +2,7 @@ namespace AK.DbSample.Domain.Services; -public abstract class BaseService +public abstract class BaseService(DataContext dataContext) { - protected readonly DataContext DataContext; - - protected BaseService(DataContext dataContext) - { - DataContext = dataContext; - } + protected readonly DataContext DataContext = dataContext; } \ No newline at end of file diff --git a/Domain/Services/Client/ClientCommandService.cs b/Domain/Services/Client/ClientCommandService.cs index 21c66f9..d00533b 100644 --- a/Domain/Services/Client/ClientCommandService.cs +++ b/Domain/Services/Client/ClientCommandService.cs @@ -14,10 +14,8 @@ public interface IClientCommandService Task Delete(long id); } -public class ClientCommandService: BaseService, IClientCommandService +public class ClientCommandService(DataContext dataContext) : BaseService(dataContext), IClientCommandService { - public ClientCommandService(DataContext dataContext) : base(dataContext) {} - public async Task<(long, IDomainResult)> Create(CreateUpdateClientRequest dto) { var nameCheckResult = await UniqueNameCheck(null, dto.Name); diff --git a/Domain/Services/Client/ClientQueryService.cs b/Domain/Services/Client/ClientQueryService.cs index 266ee85..c8b0ac2 100644 --- a/Domain/Services/Client/ClientQueryService.cs +++ b/Domain/Services/Client/ClientQueryService.cs @@ -13,10 +13,8 @@ public interface IClientQueryService Task GetList(GetClientListRequest filter); } -public class ClientQueryService: BaseService, IClientQueryService +public class ClientQueryService(DataContext dataContext) : BaseService(dataContext), IClientQueryService { - public ClientQueryService(DataContext dataContext) : base(dataContext) {} - public async Task<(GetClientByIdResponse, IDomainResult)> GetById(long clientId) { var client = await DataContext.Clients diff --git a/Domain/Services/Client/DTOs/ClientQueryDtos.cs b/Domain/Services/Client/DTOs/ClientQueryDtos.cs index 48fff19..3e8e06f 100644 --- a/Domain/Services/Client/DTOs/ClientQueryDtos.cs +++ b/Domain/Services/Client/DTOs/ClientQueryDtos.cs @@ -1,3 +1,4 @@ +// ReSharper disable NotAccessedPositionalProperty.Global namespace AK.DbSample.Domain.Services.Client.DTOs; public record GetClientListRequest(string? Name = null); diff --git a/Domain/Services/Invoice/DTOs/InvoiceQueryDtos.cs b/Domain/Services/Invoice/DTOs/InvoiceQueryDtos.cs index 5e5d623..3b2d9b6 100644 --- a/Domain/Services/Invoice/DTOs/InvoiceQueryDtos.cs +++ b/Domain/Services/Invoice/DTOs/InvoiceQueryDtos.cs @@ -1,3 +1,4 @@ +// ReSharper disable NotAccessedPositionalProperty.Global namespace AK.DbSample.Domain.Services.Invoice.DTOs; public record GetInvoiceListRequest(long? ClientId = null); diff --git a/Domain/Services/Invoice/InvoiceCommandService.cs b/Domain/Services/Invoice/InvoiceCommandService.cs index 447d4bc..78eae73 100644 --- a/Domain/Services/Invoice/InvoiceCommandService.cs +++ b/Domain/Services/Invoice/InvoiceCommandService.cs @@ -14,10 +14,8 @@ public interface IInvoiceCommandService Task Delete(string number); } -public class InvoiceCommandService: BaseService, IInvoiceCommandService +public class InvoiceCommandService(DataContext dataContext) : BaseService(dataContext), IInvoiceCommandService { - public InvoiceCommandService(DataContext dataContext) : base(dataContext) {} - public async Task<(string, IDomainResult)> Create(CreateInvoiceRequest dto) { var numberCheckResult = await UniqueNumberCheck(dto.Number, true); diff --git a/Domain/Services/Invoice/InvoiceQueryService.cs b/Domain/Services/Invoice/InvoiceQueryService.cs index 0b28d62..2deb5fe 100644 --- a/Domain/Services/Invoice/InvoiceQueryService.cs +++ b/Domain/Services/Invoice/InvoiceQueryService.cs @@ -13,10 +13,8 @@ public interface IInvoiceQueryService Task GetList(GetInvoiceListRequest filter); } -public class InvoiceQueryService: BaseService, IInvoiceQueryService +public class InvoiceQueryService(DataContext dataContext) : BaseService(dataContext), IInvoiceQueryService { - public InvoiceQueryService(DataContext dataContext) : base(dataContext) {} - public async Task<(GetInvoiceByNumberResponse, IDomainResult)> GetByNumber(string number) { var invoice = await DataContext.Invoices diff --git a/README.md b/README.md index 91b0306..1317416 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 7](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-7); - - [Entity Framework Core 7](https://docs.microsoft.com/en-us/ef/core/) and [dotnet-ef](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) CLI. + - [.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. - 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) v7.x; + - [.NET SDK](https://dotnet.microsoft.com/download) v8.x; - [dotnet-ef](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) CLI; - [Docker](https://www.docker.com/). diff --git a/devops/start_docker_sql_server_with_new_db.sh b/devops/start_docker_sql_server_with_new_db.sh index b4344fb..ee534f7 100755 --- a/devops/start_docker_sql_server_with_new_db.sh +++ b/devops/start_docker_sql_server_with_new_db.sh @@ -4,7 +4,7 @@ saPassword="Secret_Passw0rd" dbName="SampleDb" if [ -z "$1" ]; then - echo "Provide path to a SQL script for creating DB schema" + echo "ERROR! No path to a SQL script for creating DB schema. Provide as a parameter" exit 1 fi createDbSqlScript="$1"