From ef814e7394b09965c3691549e23edef02ad94bfc Mon Sep 17 00:00:00 2001 From: Wouter Huysentruit Date: Mon, 23 Oct 2023 21:20:42 +0200 Subject: [PATCH] Switch to MS SQL for .ToJson support See https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1779 --- .gitignore | 4 - docker-compose.yml | 15 ++- .../Features/Basket/Entities/Basket.cs | 28 ++++++ .../20230706213110_Add product.Designer.cs | 45 --------- .../Migrations/20230706213110_Add product.cs | 44 --------- ...06214258_Add product unitprice.Designer.cs | 48 ---------- .../20230706214258_Add product unitprice.cs | 29 ------ .../20231023191622_Initial.Designer.cs | 92 +++++++++++++++++++ .../Migrations/20231023191622_Initial.cs | 55 +++++++++++ .../Migrations/DataContextModelSnapshot.cs | 54 ++++++++++- src/Shop.Application/Program.cs | 4 +- src/Shop.Application/appsettings.json | 4 +- .../InfrastructureBuilderExtensions.cs | 9 +- .../Shop.Infrastructure.csproj | 2 +- 14 files changed, 242 insertions(+), 191 deletions(-) create mode 100644 src/Shop.Application/Features/Basket/Entities/Basket.cs delete mode 100644 src/Shop.Application/Migrations/20230706213110_Add product.Designer.cs delete mode 100644 src/Shop.Application/Migrations/20230706213110_Add product.cs delete mode 100644 src/Shop.Application/Migrations/20230706214258_Add product unitprice.Designer.cs delete mode 100644 src/Shop.Application/Migrations/20230706214258_Add product unitprice.cs create mode 100644 src/Shop.Application/Migrations/20231023191622_Initial.Designer.cs create mode 100644 src/Shop.Application/Migrations/20231023191622_Initial.cs diff --git a/.gitignore b/.gitignore index 3faddc1..2e71acd 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,3 @@ TestResults/ .cr/ .vs/ __mismatch__ -.mongodb -.rabbitmq -.redis -.mysql diff --git a/docker-compose.yml b/docker-compose.yml index dc607e1..76d0e58 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,12 @@ version: '3.8' services: - mysql: - image: 'mysql:8.0-debian' + mssql: + image: 'mcr.microsoft.com/mssql/server:2019-latest' restart: always environment: - MYSQL_DATABASE: shop - MYSQL_USER: mysql - MYSQL_PASSWORD: mysql - MYSQL_ROOT_PASSWORD: root + ACCEPT_EULA: Y + MSSQL_PID: Developer + SA_PASSWORD: P@ssw0rd ports: - - '3306:3306' - volumes: - - .mysql:/var/lib/mysql + - 11433:1433 diff --git a/src/Shop.Application/Features/Basket/Entities/Basket.cs b/src/Shop.Application/Features/Basket/Entities/Basket.cs new file mode 100644 index 0000000..520d935 --- /dev/null +++ b/src/Shop.Application/Features/Basket/Entities/Basket.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Shop.Application.Features.Basket.Entities; + +internal sealed record Basket +{ + public required Guid Id { get; init; } + + public required Reservation[] Reservations { get; init; } +} + +internal sealed record Reservation +{ + public required Guid ProductId { get; init; } + + public required int Quantity { get; init; } +} + +internal sealed class BasketEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + + builder.OwnsMany(x => x.Reservations, options => options.ToJson()); + } +} diff --git a/src/Shop.Application/Migrations/20230706213110_Add product.Designer.cs b/src/Shop.Application/Migrations/20230706213110_Add product.Designer.cs deleted file mode 100644 index 7b3e852..0000000 --- a/src/Shop.Application/Migrations/20230706213110_Add product.Designer.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Shop.Application; - -#nullable disable - -namespace Shop.Application.Migrations -{ - [DbContext(typeof(DataContext))] - [Migration("20230706213110_Add product")] - partial class Addproduct - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "7.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - modelBuilder.Entity("Shop.Application.Features.Catalog.Entities.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("char(36)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("Name"); - - b.ToTable("Product"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Shop.Application/Migrations/20230706213110_Add product.cs b/src/Shop.Application/Migrations/20230706213110_Add product.cs deleted file mode 100644 index f9ca1d5..0000000 --- a/src/Shop.Application/Migrations/20230706213110_Add product.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Shop.Application.Migrations -{ - /// - public partial class Addproduct : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateTable( - name: "Product", - columns: table => new - { - Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), - Name = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4") - }, - constraints: table => - { - table.PrimaryKey("PK_Product", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateIndex( - name: "IX_Product_Name", - table: "Product", - column: "Name"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Product"); - } - } -} diff --git a/src/Shop.Application/Migrations/20230706214258_Add product unitprice.Designer.cs b/src/Shop.Application/Migrations/20230706214258_Add product unitprice.Designer.cs deleted file mode 100644 index b574afc..0000000 --- a/src/Shop.Application/Migrations/20230706214258_Add product unitprice.Designer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Shop.Application; - -#nullable disable - -namespace Shop.Application.Migrations -{ - [DbContext(typeof(DataContext))] - [Migration("20230706214258_Add product unitprice")] - partial class Addproductunitprice - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "7.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - modelBuilder.Entity("Shop.Application.Features.Catalog.Entities.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("char(36)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("UnitPrice") - .HasColumnType("decimal(65,30)"); - - b.HasKey("Id"); - - b.HasIndex("Name"); - - b.ToTable("Product"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Shop.Application/Migrations/20230706214258_Add product unitprice.cs b/src/Shop.Application/Migrations/20230706214258_Add product unitprice.cs deleted file mode 100644 index 750221d..0000000 --- a/src/Shop.Application/Migrations/20230706214258_Add product unitprice.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Shop.Application.Migrations -{ - /// - public partial class Addproductunitprice : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "UnitPrice", - table: "Product", - type: "decimal(65,30)", - nullable: false, - defaultValue: 0m); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "UnitPrice", - table: "Product"); - } - } -} diff --git a/src/Shop.Application/Migrations/20231023191622_Initial.Designer.cs b/src/Shop.Application/Migrations/20231023191622_Initial.Designer.cs new file mode 100644 index 0000000..e3ea5fd --- /dev/null +++ b/src/Shop.Application/Migrations/20231023191622_Initial.Designer.cs @@ -0,0 +1,92 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shop.Application; + +#nullable disable + +namespace Shop.Application.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20231023191622_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shop.Application.Features.Basket.Entities.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Basket"); + }); + + modelBuilder.Entity("Shop.Application.Features.Catalog.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Product"); + }); + + modelBuilder.Entity("Shop.Application.Features.Basket.Entities.Basket", b => + { + b.OwnsMany("Shop.Application.Features.Basket.Entities.Reservation", "Reservations", b1 => + { + b1.Property("BasketId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("BasketId", "Id"); + + b1.ToTable("Basket"); + + b1.ToJson("Reservations"); + + b1.WithOwner() + .HasForeignKey("BasketId"); + }); + + b.Navigation("Reservations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shop.Application/Migrations/20231023191622_Initial.cs b/src/Shop.Application/Migrations/20231023191622_Initial.cs new file mode 100644 index 0000000..472fdfd --- /dev/null +++ b/src/Shop.Application/Migrations/20231023191622_Initial.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Shop.Application.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Basket", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Reservations = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Basket", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Product", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Product", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Product_Name", + table: "Product", + column: "Name"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Basket"); + + migrationBuilder.DropTable( + name: "Product"); + } + } +} diff --git a/src/Shop.Application/Migrations/DataContextModelSnapshot.cs b/src/Shop.Application/Migrations/DataContextModelSnapshot.cs index a5f9445..a427b0c 100644 --- a/src/Shop.Application/Migrations/DataContextModelSnapshot.cs +++ b/src/Shop.Application/Migrations/DataContextModelSnapshot.cs @@ -2,6 +2,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Shop.Application; @@ -16,22 +17,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + .HasAnnotation("ProductVersion", "7.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shop.Application.Features.Basket.Entities.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Basket"); + }); modelBuilder.Entity("Shop.Application.Features.Catalog.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("char(36)"); + .HasColumnType("uniqueidentifier"); b.Property("Name") .IsRequired() .HasMaxLength(200) - .HasColumnType("varchar(200)"); + .HasColumnType("nvarchar(200)"); b.Property("UnitPrice") - .HasColumnType("decimal(65,30)"); + .HasColumnType("decimal(18,2)"); b.HasKey("Id"); @@ -39,6 +53,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Product"); }); + + modelBuilder.Entity("Shop.Application.Features.Basket.Entities.Basket", b => + { + b.OwnsMany("Shop.Application.Features.Basket.Entities.Reservation", "Reservations", b1 => + { + b1.Property("BasketId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("BasketId", "Id"); + + b1.ToTable("Basket"); + + b1.ToJson("Reservations"); + + b1.WithOwner() + .HasForeignKey("BasketId"); + }); + + b.Navigation("Reservations"); + }); #pragma warning restore 612, 618 } } diff --git a/src/Shop.Application/Program.cs b/src/Shop.Application/Program.cs index 931b981..601965d 100644 --- a/src/Shop.Application/Program.cs +++ b/src/Shop.Application/Program.cs @@ -1,7 +1,7 @@ var builder = WebApplication.CreateBuilder(args); -var connectionString = builder.Configuration.GetValue("MySql:ConnectionString") - ?? throw new InvalidOperationException("MySql:ConnectionString not set"); +var connectionString = builder.Configuration.GetValue("MsSql:ConnectionString") + ?? throw new InvalidOperationException("MsSql:ConnectionString not set"); builder.Services.AddInfrastructure(x => x .AddEntityFramework(connectionString) diff --git a/src/Shop.Application/appsettings.json b/src/Shop.Application/appsettings.json index a1e0015..bc75872 100644 --- a/src/Shop.Application/appsettings.json +++ b/src/Shop.Application/appsettings.json @@ -6,7 +6,7 @@ "Microsoft.EntityFrameworkCore.Database.Command": "Information" } }, - "MySql": { - "ConnectionString": "Server=localhost; Port=3306; Database=shop; Uid=mysql; Pwd=mysql" + "MsSql": { + "ConnectionString": "Server=localhost,11433;Database=Shop;User Id=sa;Password=P@ssw0rd;TrustServerCertificate=True;Encrypt=False;" } } diff --git a/src/Shop.Infrastructure/EntityFramework/InfrastructureBuilderExtensions.cs b/src/Shop.Infrastructure/EntityFramework/InfrastructureBuilderExtensions.cs index 5b560fd..3b71345 100644 --- a/src/Shop.Infrastructure/EntityFramework/InfrastructureBuilderExtensions.cs +++ b/src/Shop.Infrastructure/EntityFramework/InfrastructureBuilderExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Shop.Infrastructure.EntityFramework; @@ -11,9 +12,13 @@ public static InfrastructureBuilder AddEntityFramework(this Infrastr builder.Services.AddPooledDbContextFactory( options => options .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) - .UseMySql(connectionString, ServerVersion.Parse("8.0.31")), +#if DEBUG + .EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, LogLevel.Information) +#endif + .UseSqlServer(connectionString), poolSize: 1024); - + return builder; } } diff --git a/src/Shop.Infrastructure/Shop.Infrastructure.csproj b/src/Shop.Infrastructure/Shop.Infrastructure.csproj index 467faa6..ed5b178 100644 --- a/src/Shop.Infrastructure/Shop.Infrastructure.csproj +++ b/src/Shop.Infrastructure/Shop.Infrastructure.csproj @@ -13,12 +13,12 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive -