diff --git a/Benchmarks.Core/Database/BenchmarkDbContext.cs b/Benchmarks.Core/Database/BenchmarkDbContext.cs index d19e6f7..e6cb4e7 100644 --- a/Benchmarks.Core/Database/BenchmarkDbContext.cs +++ b/Benchmarks.Core/Database/BenchmarkDbContext.cs @@ -3,8 +3,10 @@ public class BenchmarkDbContext(DbContextOptions options) : DbContext(options) { public DbSet ClusteredIndexes { get; set; } = null!; + public DbSet GuidPrimaryKeys { get; set; } = null!; + public DbSet HardDeletes { get; set; } = null!; public DbSet NonClusteredIndexes { get; set; } = null!; - public DbSet SimpleEntities { get; set; } = null!; + public DbSet SoftDeletes { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -12,11 +14,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasKey(p => p.Id) .IsClustered(); + modelBuilder.Entity() + .HasKey(p => p.Id); + + modelBuilder.Entity() + .HasKey(p => p.Id); + modelBuilder.Entity() .HasKey(p => p.Id) .IsClustered(false); - modelBuilder.Entity() + modelBuilder.Entity() .HasKey(p => p.Id); + } } diff --git a/Benchmarks.Core/Database/BenchmarkDbContextFactory.cs b/Benchmarks.Core/Database/BenchmarkDbContextFactory.cs index e0342ee..c2d1c4f 100644 --- a/Benchmarks.Core/Database/BenchmarkDbContextFactory.cs +++ b/Benchmarks.Core/Database/BenchmarkDbContextFactory.cs @@ -17,7 +17,7 @@ public static BenchmarkDbContext Create(DbServer server, string connectionString return new SqlServerDbContext(sqlServerOptions.Options); default: - throw new NotImplementedException(); + throw new InvalidEnumArgumentException(); } } } diff --git a/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.Designer.cs b/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.Designer.cs new file mode 100644 index 0000000..50f8173 --- /dev/null +++ b/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.Designer.cs @@ -0,0 +1,147 @@ +// +using System; +using Benchmarks.Core.Database.Postgres; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Benchmarks.Core.Database.Postgres.Migrations +{ + [DbContext(typeof(PostgresDbContext))] + [Migration("20240317084132_CurrentPostgres")] + partial class CurrentPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Benchmarks.Core.Entities.ClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("ClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.GuidPrimaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("GuidPrimaryKeys"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.HardDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("HardDeletes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.NonClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("NonClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.SoftDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("SoftDeletes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.cs b/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.cs new file mode 100644 index 0000000..03eeede --- /dev/null +++ b/Benchmarks.Core/Database/Postgres/Migrations/20240317084132_CurrentPostgres.cs @@ -0,0 +1,109 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System; + +#nullable disable + +namespace Benchmarks.Core.Database.Postgres.Migrations +{ + /// + public partial class CurrentPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ClusteredIndexes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Text = table.Column(type: "text", nullable: false), + CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClusteredIndexes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GuidPrimaryKeys", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Text = table.Column(type: "text", nullable: false), + CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuidPrimaryKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "HardDeletes", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Text = table.Column(type: "text", nullable: false), + CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HardDeletes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "NonClusteredIndexes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Text = table.Column(type: "text", nullable: false), + CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NonClusteredIndexes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SoftDeletes", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IsDeleted = table.Column(type: "boolean", nullable: false), + DeletedAtUtc = table.Column(type: "timestamp with time zone", nullable: true), + Text = table.Column(type: "text", nullable: false), + CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SoftDeletes", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ClusteredIndexes"); + + migrationBuilder.DropTable( + name: "GuidPrimaryKeys"); + + migrationBuilder.DropTable( + name: "HardDeletes"); + + migrationBuilder.DropTable( + name: "NonClusteredIndexes"); + + migrationBuilder.DropTable( + name: "SoftDeletes"); + } + } +} diff --git a/Benchmarks.Core/Database/Postgres/Migrations/PostgresDbContextModelSnapshot.cs b/Benchmarks.Core/Database/Postgres/Migrations/PostgresDbContextModelSnapshot.cs new file mode 100644 index 0000000..5866048 --- /dev/null +++ b/Benchmarks.Core/Database/Postgres/Migrations/PostgresDbContextModelSnapshot.cs @@ -0,0 +1,144 @@ +// +using System; +using Benchmarks.Core.Database.Postgres; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Benchmarks.Core.Database.Postgres.Migrations +{ + [DbContext(typeof(PostgresDbContext))] + partial class PostgresDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Benchmarks.Core.Entities.ClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("ClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.GuidPrimaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("GuidPrimaryKeys"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.HardDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("HardDeletes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.NonClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("NonClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.SoftDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAtUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("SoftDeletes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.Designer.cs b/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.Designer.cs new file mode 100644 index 0000000..d79e338 --- /dev/null +++ b/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.Designer.cs @@ -0,0 +1,149 @@ +// +using System; +using Benchmarks.Core.Database.SqlServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Benchmarks.Core.Database.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerDbContext))] + [Migration("20240317084134_CurrentSqlServer")] + partial class CurrentSqlServer + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Benchmarks.Core.Entities.ClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id")); + + b.ToTable("ClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.GuidPrimaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GuidPrimaryKeys"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.HardDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("HardDeletes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.NonClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); + + b.ToTable("NonClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.SoftDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("DeletedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SoftDeletes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.cs b/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.cs new file mode 100644 index 0000000..98fbcda --- /dev/null +++ b/Benchmarks.Core/Database/SqlServer/Migrations/20240317084134_CurrentSqlServer.cs @@ -0,0 +1,110 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +#nullable disable + +namespace Benchmarks.Core.Database.SqlServer.Migrations +{ + /// + public partial class CurrentSqlServer : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ClusteredIndexes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Text = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClusteredIndexes", x => x.Id) + .Annotation("SqlServer:Clustered", true); + }); + + migrationBuilder.CreateTable( + name: "GuidPrimaryKeys", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Text = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuidPrimaryKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "HardDeletes", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Text = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HardDeletes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "NonClusteredIndexes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Text = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NonClusteredIndexes", x => x.Id) + .Annotation("SqlServer:Clustered", false); + }); + + migrationBuilder.CreateTable( + name: "SoftDeletes", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + IsDeleted = table.Column(type: "bit", nullable: false), + DeletedAtUtc = table.Column(type: "datetimeoffset", nullable: true), + Text = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false), + LongInteger = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SoftDeletes", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ClusteredIndexes"); + + migrationBuilder.DropTable( + name: "GuidPrimaryKeys"); + + migrationBuilder.DropTable( + name: "HardDeletes"); + + migrationBuilder.DropTable( + name: "NonClusteredIndexes"); + + migrationBuilder.DropTable( + name: "SoftDeletes"); + } + } +} diff --git a/Benchmarks.Core/Database/SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs b/Benchmarks.Core/Database/SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs new file mode 100644 index 0000000..c0c7f94 --- /dev/null +++ b/Benchmarks.Core/Database/SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs @@ -0,0 +1,146 @@ +// +using System; +using Benchmarks.Core.Database.SqlServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Benchmarks.Core.Database.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerDbContext))] + partial class SqlServerDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Benchmarks.Core.Entities.ClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id")); + + b.ToTable("ClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.GuidPrimaryKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GuidPrimaryKeys"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.HardDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("HardDeletes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.NonClusteredIndex", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); + + b.ToTable("NonClusteredIndexes"); + }); + + modelBuilder.Entity("Benchmarks.Core.Entities.SoftDeleted", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("DeletedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LongInteger") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SoftDeletes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Benchmarks.Core/Entities/GuidPrimaryKey.cs b/Benchmarks.Core/Entities/GuidPrimaryKey.cs new file mode 100644 index 0000000..ed40983 --- /dev/null +++ b/Benchmarks.Core/Entities/GuidPrimaryKey.cs @@ -0,0 +1,15 @@ +namespace Benchmarks.Core.Entities; + +public abstract class GuidPrimaryKeyBase : IBaseEntity +{ + public Guid Id { get; init; } = Guid.NewGuid(); + public string Text { get; init; } = string.Empty; + public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow; + public long LongInteger { get; init; } +} + +public sealed class GuidPrimaryKey : GuidPrimaryKeyBase; + +public sealed class ClusteredIndex : GuidPrimaryKeyBase; + +public sealed class NonClusteredIndex : GuidPrimaryKeyBase; diff --git a/Benchmarks.Core/Entities/IBaseEntity.cs b/Benchmarks.Core/Entities/IBaseEntity.cs new file mode 100644 index 0000000..7c8dd5f --- /dev/null +++ b/Benchmarks.Core/Entities/IBaseEntity.cs @@ -0,0 +1,9 @@ +namespace Benchmarks.Core.Entities; + +public interface IBaseEntity +{ + [MaxLength(42)] + string Text { get; init; } + DateTimeOffset CreatedAtUtc { get; init; } + long LongInteger { get; init; } +} diff --git a/Benchmarks.Core/Entities/ILongPrimaryKey.cs b/Benchmarks.Core/Entities/ILongPrimaryKey.cs new file mode 100644 index 0000000..75ad31f --- /dev/null +++ b/Benchmarks.Core/Entities/ILongPrimaryKey.cs @@ -0,0 +1,6 @@ +namespace Benchmarks.Core.Entities; + +public interface ILongPrimaryKey +{ + long Id { get; init; } +} diff --git a/Benchmarks.Core/Entities/SoftDeleted.cs b/Benchmarks.Core/Entities/SoftDeleted.cs new file mode 100644 index 0000000..7a3a9d1 --- /dev/null +++ b/Benchmarks.Core/Entities/SoftDeleted.cs @@ -0,0 +1,24 @@ +namespace Benchmarks.Core.Entities; + +public interface ISoftDeletable +{ + bool IsDeleted { get; init; } + + DateTimeOffset? DeletedAtUtc { get; init; } +} + +public abstract class LongPrimaryKeyBase : IBaseEntity, ILongPrimaryKey +{ + public long Id { get; init; } + public string Text { get; init; } = string.Empty; + public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow; + public long LongInteger { get; init; } +} + +public sealed class SoftDeleted : LongPrimaryKeyBase, ISoftDeletable +{ + public bool IsDeleted { get; init; } + public DateTimeOffset? DeletedAtUtc { get; init; } +} + +public sealed class HardDeleted : LongPrimaryKeyBase; diff --git a/Benchmarks.Core/GlobalUsings.cs b/Benchmarks.Core/GlobalUsings.cs index 59f2774..66c7ff1 100644 --- a/Benchmarks.Core/GlobalUsings.cs +++ b/Benchmarks.Core/GlobalUsings.cs @@ -1,4 +1,7 @@ -global using Benchmarks.Core.Database.Postgres; +global using Benchmarks.Core.Database; +global using Benchmarks.Core.Database.Postgres; global using Benchmarks.Core.Database.SqlServer; +global using Benchmarks.Core.Entities; global using Microsoft.EntityFrameworkCore.Design; +global using System.ComponentModel; global using System.ComponentModel.DataAnnotations; diff --git a/Benchmarks.Core/Repositories/GuidPrimaryKeyRepository.cs b/Benchmarks.Core/Repositories/GuidPrimaryKeyRepository.cs new file mode 100644 index 0000000..6d907fc --- /dev/null +++ b/Benchmarks.Core/Repositories/GuidPrimaryKeyRepository.cs @@ -0,0 +1,36 @@ +namespace Benchmarks.Core.Repositories; + +public sealed class GuidPrimaryKeyRepository(DbServer dbServer, string connectionString) +{ + private static TEntity[] CreateEntities(int rowCount) + where TEntity : GuidPrimaryKeyBase, new() + { + var entities = new TEntity[rowCount]; + + // Worth benchmarking against Parallel.For implementation? + for (var row = 1; row <= rowCount; row++) + { + var now = DateTimeOffset.UtcNow; + + entities[row - 1] = new TEntity + { + Id = Guid.NewGuid(), + Text = $"Row {row:N0}", + CreatedAtUtc = now, + LongInteger = now.Ticks + }; + } + + return entities; + } + + public async Task InsertEntitiesAsync(int rowCount) + where TEntity : GuidPrimaryKeyBase, new() + { + await using var dbContext = BenchmarkDbContextFactory.Create(dbServer, connectionString); + await dbContext.Database.MigrateAsync(); + var entities = CreateEntities(rowCount); + await dbContext.Set().AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + } +} diff --git a/Benchmarks.Core/SimpleEntity.cs b/Benchmarks.Core/SimpleEntity.cs deleted file mode 100644 index e7f63d3..0000000 --- a/Benchmarks.Core/SimpleEntity.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Benchmarks.Core; - -public class SimpleEntity -{ - public Guid Id { get; init; } = Guid.Empty; - [MaxLength(42)] - public string Text { get; init; } = string.Empty; - public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow; - public long LongInteger { get; init; } - - public static TEntity[] Create(int rowCount) - where TEntity : SimpleEntity, new() - { - var entities = new TEntity[rowCount]; - - for (var row = 1; row <= rowCount; row++) - { - var now = DateTimeOffset.UtcNow; - - entities[row - 1] = new TEntity - { - Id = Guid.NewGuid(), - Text = $"Row {row:N0}", - CreatedAtUtc = now, - LongInteger = now.Ticks - }; - } - - return entities; - } -} - -public class ClusteredIndex : SimpleEntity; - -public class NonClusteredIndex : SimpleEntity; - -public class SoftDeleted : SimpleEntity -{ - public bool IsDeleted { get; init; } - public DateTimeOffset? DeletedAtUtc { get; init; } -} diff --git a/Benchmarks/GlobalUsings.cs b/Benchmarks/GlobalUsings.cs index fdb6f9d..30899cf 100644 --- a/Benchmarks/GlobalUsings.cs +++ b/Benchmarks/GlobalUsings.cs @@ -1,5 +1,8 @@ global using BenchmarkDotNet.Attributes; -global using Benchmarks.Core; -global using Microsoft.EntityFrameworkCore; +global using Benchmarks.Core.Benchmarking; +global using Benchmarks.Core.Database; +global using Benchmarks.Core.Entities; +global using Benchmarks.Core.Repositories; +global using System.ComponentModel; global using Testcontainers.MsSql; global using Testcontainers.PostgreSql; diff --git a/Benchmarks/GuidPrimaryKey.cs b/Benchmarks/GuidPrimaryKey.cs index 2c2dcb8..799235c 100644 --- a/Benchmarks/GuidPrimaryKey.cs +++ b/Benchmarks/GuidPrimaryKey.cs @@ -1,7 +1,6 @@ namespace Benchmarks; -using Core.Benchmarking; -using Core.Database; +using GuidPrimaryKeyEntity = Benchmarks.Core.Entities.GuidPrimaryKey; [BenchmarkInfo( description: "Test performance of GUID based primary keys", @@ -22,14 +21,6 @@ public class GuidPrimaryKey .WithImage("postgres:latest") .Build(); - private BenchmarkDbContext CreateDbContext(DbServer server) => - BenchmarkDbContextFactory.Create(server, server switch - { - DbServer.Postgres => _postgresContainer.GetConnectionString(), - DbServer.SqlServer => _sqlServerContainer.GetConnectionString(), - _ => throw new NotImplementedException() - }); - [GlobalSetup] public async Task Setup() { @@ -40,31 +31,28 @@ public async Task Setup() [Benchmark] public async Task InsertGuidPrimaryKeyPostgres() { - await using var dbContext = CreateDbContext(DbServer.Postgres); - await dbContext.Database.MigrateAsync(); - var entities = SimpleEntity.Create(RowCount); - await dbContext.SimpleEntities.AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); + var repository = new GuidPrimaryKeyRepository( + DbServer.Postgres, + _postgresContainer.GetConnectionString()); + await repository.InsertEntitiesAsync(RowCount); } [Benchmark] public async Task InsertGuidPrimaryKeyWithClusteredIndexSqlServer() { - await using var dbContext = CreateDbContext(DbServer.SqlServer); - await dbContext.Database.MigrateAsync(); - var entities = SimpleEntity.Create(RowCount); - await dbContext.ClusteredIndexes.AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); + var repository = new GuidPrimaryKeyRepository( + DbServer.SqlServer, + _sqlServerContainer.GetConnectionString()); + await repository.InsertEntitiesAsync(RowCount); } [Benchmark] public async Task InsertGuidPrimaryKeyWithNonClusteredIndexSqlServer() { - await using var dbContext = CreateDbContext(DbServer.SqlServer); - await dbContext.Database.MigrateAsync(); - var entities = SimpleEntity.Create(RowCount); - await dbContext.NonClusteredIndexes.AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); + var repository = new GuidPrimaryKeyRepository( + DbServer.SqlServer, + _sqlServerContainer.GetConnectionString()); + await repository.InsertEntitiesAsync(RowCount); } [GlobalCleanup] diff --git a/Benchmarks/SoftDelete.cs b/Benchmarks/SoftDelete.cs index c89d3ca..4dfd099 100644 --- a/Benchmarks/SoftDelete.cs +++ b/Benchmarks/SoftDelete.cs @@ -1,8 +1,5 @@ namespace Benchmarks; -using Core.Benchmarking; -using Core.Database; - [BenchmarkInfo( description: "Test performance of soft delete operations", links: ["https://www.milanjovanovic.tech/blog/implementing-soft-delete-with-ef-core"], @@ -27,7 +24,7 @@ private BenchmarkDbContext CreateDbContext(DbServer server) => { DbServer.Postgres => _postgresContainer.GetConnectionString(), DbServer.SqlServer => _sqlServerContainer.GetConnectionString(), - _ => throw new NotImplementedException() + _ => throw new InvalidEnumArgumentException() }); [GlobalSetup] @@ -41,20 +38,12 @@ public async Task Setup() public async Task SaveHardDelete() { await using var dbContext = CreateDbContext(DbServer); - await dbContext.Database.MigrateAsync(); - var entities = SimpleEntity.Create(RowCount); - await dbContext.SimpleEntities.AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); } [Benchmark] public async Task SaveSoftDelete() { await using var dbContext = CreateDbContext(DbServer); - await dbContext.Database.MigrateAsync(); - var entities = SimpleEntity.Create(RowCount); - await dbContext.SimpleEntities.AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); } [GlobalCleanup] diff --git a/CreateMigrations.ps1 b/CreateMigrations.ps1 index 6e63602..0279d4e 100644 --- a/CreateMigrations.ps1 +++ b/CreateMigrations.ps1 @@ -18,12 +18,14 @@ function CreateMigration { ) $context = "$dbServer" + "DbContext" $path = ".\Benchmarks.Core\Database\$dbServer\Migrations" + $name = "Current$dbServer" if (Test-Path $path) { - Write-Host "Removing existing $dbServer migrations" + Write-Host "Removing existing migrations for $dbServer" Remove-Item -Recurse -Force -Path $path } + $outputDir = "Database\$dbServer\Migrations" Write-Host "Generating migrations for $dbServer" - dotnet ef migrations add Current -p $project -c $context -o $path + dotnet ef migrations add $name -p $project -c $context -o $outputDir } CreateMigration -dbServer Postgres