diff --git a/Persistence.API/Startup.cs b/Persistence.API/Startup.cs index a8d272e..d972545 100644 --- a/Persistence.API/Startup.cs +++ b/Persistence.API/Startup.cs @@ -59,7 +59,7 @@ public class Startup using var scope = host.Services.CreateScope(); var provider = scope.ServiceProvider; - var context = provider.GetRequiredService(); + var context = provider.GetRequiredService(); context.Database.EnsureCreatedAndMigrated(); } diff --git a/Persistence.Database.Postgres/DependencyInjection.cs b/Persistence.Database.Postgres/DependencyInjection.cs index 3acd55c..e775952 100644 --- a/Persistence.Database.Postgres/DependencyInjection.cs +++ b/Persistence.Database.Postgres/DependencyInjection.cs @@ -10,10 +10,10 @@ public static class DependencyInjection { string connectionStringName = "DefaultConnection"; - services.AddDbContext(options => + services.AddDbContext(options => options.UseNpgsql(configuration.GetConnectionString(connectionStringName))); - services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); return services; } diff --git a/Persistence.Database.Postgres/DesignTimeDbContextFactory.cs b/Persistence.Database.Postgres/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..2123efd --- /dev/null +++ b/Persistence.Database.Postgres/DesignTimeDbContextFactory.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Npgsql; +using Persistence.Database.Model; + +namespace Persistence.Database.Postgres; + +/// +/// Фабрика контекста для dotnet ef миграций +/// +public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public PersistencePostgresContext CreateDbContext(string[] args) + { + var connectionStringBuilder = new NpgsqlConnectionStringBuilder(); + connectionStringBuilder.Host = "localhost"; + connectionStringBuilder.Database = "persistence"; + connectionStringBuilder.Username = "postgres"; + connectionStringBuilder.Password = "q"; + connectionStringBuilder.PersistSecurityInfo = true; + var connectionString = connectionStringBuilder.ToString(); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql(connectionString); + var context = new PersistencePostgresContext(optionsBuilder.Options); + return context; + } +} diff --git a/Persistence.Database.Postgres/EFExtensionsInitialization.cs b/Persistence.Database.Postgres/EFExtensionsInitialization.cs index 1872628..a876f7a 100644 --- a/Persistence.Database.Postgres/EFExtensionsInitialization.cs +++ b/Persistence.Database.Postgres/EFExtensionsInitialization.cs @@ -44,10 +44,13 @@ public static class EFExtensionsInitialization var migrations = db.GetPendingMigrations() .Select(migration => $" ('{migration}', '{efVersionString}')"); - var sqlAddLastMigration = - $"INSERT INTO public.\"__EFMigrationsHistory\" " + - $"(\"MigrationId\", \"ProductVersion\") " + - $"VALUES {string.Join(',', migrations)};"; - db.ExecuteSqlRaw(sqlAddLastMigration); + if (migrations.Any()) + { + var sqlAddLastMigration = + $"INSERT INTO public.\"__EFMigrationsHistory\" " + + $"(\"MigrationId\", \"ProductVersion\") " + + $"VALUES {string.Join(',', migrations)};"; + db.ExecuteSqlRaw(sqlAddLastMigration); + } } } diff --git a/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs b/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs deleted file mode 100644 index 17ec6e1..0000000 --- a/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Persistence.Database.Model; - -#nullable disable - -namespace Persistence.Database.Postgres.Migrations -{ - [DbContext(typeof(PersistenceDbContext))] - [Migration("20241122074646_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseCollation("Russian_Russia.1251") - .HasAnnotation("ProductVersion", "8.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => - { - b.Property("Date") - .HasColumnType("timestamp with time zone") - .HasColumnName("date"); - - b.Property("AxialLoad") - .HasColumnType("double precision") - .HasColumnName("axialLoad"); - - b.Property("BitDepth") - .HasColumnType("double precision") - .HasColumnName("bitDepth"); - - b.Property("BlockPosition") - .HasColumnType("double precision") - .HasColumnName("blockPosition"); - - b.Property("BlockSpeed") - .HasColumnType("double precision") - .HasColumnName("blockSpeed"); - - b.Property("Flow") - .HasColumnType("double precision") - .HasColumnName("flow"); - - b.Property("HookWeight") - .HasColumnType("double precision") - .HasColumnName("hookWeight"); - - b.Property("IdFeedRegulator") - .HasColumnType("integer") - .HasColumnName("idFeedRegulator"); - - b.Property("Mode") - .HasColumnType("integer") - .HasColumnName("mode"); - - b.Property("Mse") - .HasColumnType("double precision") - .HasColumnName("mse"); - - b.Property("MseState") - .HasColumnType("smallint") - .HasColumnName("mseState"); - - b.Property("Pressure") - .HasColumnType("double precision") - .HasColumnName("pressure"); - - b.Property("Pump0Flow") - .HasColumnType("double precision") - .HasColumnName("pump0Flow"); - - b.Property("Pump1Flow") - .HasColumnType("double precision") - .HasColumnName("pump1Flow"); - - b.Property("Pump2Flow") - .HasColumnType("double precision") - .HasColumnName("pump2Flow"); - - b.Property("RotorSpeed") - .HasColumnType("double precision") - .HasColumnName("rotorSpeed"); - - b.Property("RotorTorque") - .HasColumnType("double precision") - .HasColumnName("rotorTorque"); - - b.Property("User") - .HasColumnType("text") - .HasColumnName("user"); - - b.Property("WellDepth") - .HasColumnType("double precision") - .HasColumnName("wellDepth"); - - b.HasKey("Date"); - - b.ToTable("DataSaub"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241126100631_Init.Designer.cs similarity index 76% rename from Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs rename to Persistence.Database.Postgres/Migrations/20241126100631_Init.Designer.cs index 9399dd4..e461173 100644 --- a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs +++ b/Persistence.Database.Postgres/Migrations/20241126100631_Init.Designer.cs @@ -11,9 +11,9 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { - [DbContext(typeof(PersistenceDbContext))] - [Migration("20241118052225_SetpointMigration")] - partial class SetpointMigration + [DbContext(typeof(PersistencePostgresContext))] + [Migration("20241126100631_Init")] + partial class Init { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -27,14 +27,34 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b => + { + b.Property("IdDiscriminator") + .HasColumnType("uuid") + .HasComment("Дискриминатор ссылка на тип сохраняемых данных"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Отметка времени, строго в UTC"); + + b.Property("Set") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Набор сохраняемых данных"); + + b.HasKey("IdDiscriminator", "Timestamp"); + + b.ToTable("TimestampedSets", t => + { + t.HasComment("Общая таблица данных временных рядов"); + }); + }); + modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); b.Property("AxialLoad") .HasColumnType("double precision") @@ -100,10 +120,6 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("rotorTorque"); - b.Property("TimeStamp") - .HasColumnType("integer") - .HasColumnName("timestamp"); - b.Property("User") .HasColumnType("text") .HasColumnName("user"); @@ -112,7 +128,7 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("wellDepth"); - b.HasKey("Id"); + b.HasKey("Date"); b.ToTable("DataSaub"); }); @@ -125,7 +141,7 @@ namespace Persistence.Database.Postgres.Migrations b.Property("Created") .HasColumnType("timestamp with time zone") - .HasComment("Дата изменения уставки"); + .HasComment("Дата создания уставки"); b.Property("IdUser") .HasColumnType("integer") diff --git a/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs b/Persistence.Database.Postgres/Migrations/20241126100631_Init.cs similarity index 57% rename from Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs rename to Persistence.Database.Postgres/Migrations/20241126100631_Init.cs index 22d31f0..74a549e 100644 --- a/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs +++ b/Persistence.Database.Postgres/Migrations/20241126100631_Init.cs @@ -5,7 +5,7 @@ namespace Persistence.Database.Postgres.Migrations { /// - public partial class InitialCreate : Migration + public partial class Init : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -41,6 +41,34 @@ namespace Persistence.Database.Postgres.Migrations { table.PrimaryKey("PK_DataSaub", x => x.date); }); + + migrationBuilder.CreateTable( + name: "Setpoint", + columns: table => new + { + Key = table.Column(type: "uuid", nullable: false, comment: "Ключ"), + Created = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата создания уставки"), + Value = table.Column(type: "jsonb", nullable: false, comment: "Значение уставки"), + IdUser = table.Column(type: "integer", nullable: false, comment: "Id автора последнего изменения") + }, + constraints: table => + { + table.PrimaryKey("PK_Setpoint", x => new { x.Key, x.Created }); + }); + + migrationBuilder.CreateTable( + name: "TimestampedSets", + columns: table => new + { + IdDiscriminator = table.Column(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"), + Timestamp = table.Column(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"), + Set = table.Column(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных") + }, + constraints: table => + { + table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp }); + }, + comment: "Общая таблица данных временных рядов"); } /// @@ -48,6 +76,12 @@ namespace Persistence.Database.Postgres.Migrations { migrationBuilder.DropTable( name: "DataSaub"); + + migrationBuilder.DropTable( + name: "Setpoint"); + + migrationBuilder.DropTable( + name: "TimestampedSets"); } } } diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs similarity index 98% rename from Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs rename to Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs index 8d3335c..cf0da05 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs @@ -11,8 +11,8 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { - [DbContext(typeof(PersistenceDbContext))] - partial class PersistenceDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(PersistencePostgresContext))] + partial class PersistencePostgresContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs deleted file mode 100644 index 80a9e25..0000000 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Persistence.Database.Entity; - -namespace Persistence.Database.Model; -public partial class PersistenceDbContext : DbContext -{ - public DbSet DataSaub => Set(); - public DbSet ChangeLog => Set(); - - public DbSet Setpoint => Set(); - - public DbSet TechMessage => Set(); - - public DbSet TimestampedSets => Set(); - - public PersistenceDbContext() - : base() - { - - } - - public PersistenceDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - optionsBuilder.UseNpgsql("Host=localhost;Database=persistence;Username=postgres;Password=q;Persist Security Info=True;Include Error Detail=True;" - //, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1)) - ); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.HasPostgresExtension("adminpack") - .HasAnnotation("Relational:Collation", "Russian_Russia.1251"); - - modelBuilder.Entity() - .Property(e => e.Set) - .HasJsonConversion(); - - modelBuilder.Entity(entity => - { - entity.HasOne(t => t.System) - .WithMany() - .HasForeignKey(t => t.SystemId) - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity() - .Property(e => e.Value) - .HasJsonConversion(); - } -} diff --git a/Persistence.Database.Postgres/PersistencePostgresContext.cs b/Persistence.Database.Postgres/PersistencePostgresContext.cs new file mode 100644 index 0000000..862b247 --- /dev/null +++ b/Persistence.Database.Postgres/PersistencePostgresContext.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure.Internal; + +namespace Persistence.Database.Model; + +/// +/// EF Postgres +/// +public partial class PersistencePostgresContext : PersistenceDbContext +{ + public PersistencePostgresContext(DbContextOptions options) + : base(options) + {} + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.HasPostgresExtension("adminpack") + .HasAnnotation("Relational:Collation", "Russian_Russia.1251"); + + base.OnModelCreating(modelBuilder); + + } +} diff --git a/Persistence.Database/PersistenceDbContext.cs b/Persistence.Database/PersistenceDbContext.cs new file mode 100644 index 0000000..4393ac1 --- /dev/null +++ b/Persistence.Database/PersistenceDbContext.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using Persistence.Database.Entity; +using Persistence.Database.Model; + +namespace Persistence.Database; + +/// +/// EF контекст для любых БД поддерживаемых в EF +/// +public class PersistenceDbContext : DbContext +{ + public DbSet DataSaub => Set(); + + public DbSet Setpoint => Set(); + + public DbSet TimestampedSets => Set(); + + public DbSet ChangeLog => Set(); + + public PersistenceDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(e => e.Set) + .HasJsonConversion(); + + modelBuilder.Entity() + .Property(e => e.Value) + .HasJsonConversion(); + } +} diff --git a/Persistence.IntegrationTests/BaseIntegrationTest.cs b/Persistence.IntegrationTests/BaseIntegrationTest.cs index ff4f814..7546a97 100644 --- a/Persistence.IntegrationTests/BaseIntegrationTest.cs +++ b/Persistence.IntegrationTests/BaseIntegrationTest.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Database; using Persistence.Database.Model; using Xunit; @@ -13,7 +15,7 @@ public abstract class BaseIntegrationTest : IClassFixture, { scope = factory.Services.CreateScope(); - dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext = scope.ServiceProvider.GetRequiredService(); } public void Dispose() diff --git a/Persistence.IntegrationTests/WebAppFactoryFixture.cs b/Persistence.IntegrationTests/WebAppFactoryFixture.cs index 9450608..89b5553 100644 --- a/Persistence.IntegrationTests/WebAppFactoryFixture.cs +++ b/Persistence.IntegrationTests/WebAppFactoryFixture.cs @@ -27,10 +27,11 @@ public class WebAppFactoryFixture : WebApplicationFactory builder.ConfigureServices(services => { - var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); if (descriptor != null) services.Remove(descriptor); - services.AddDbContext(options => + + services.AddDbContext(options => options.UseNpgsql(connectionString)); services.RemoveAll(); @@ -46,7 +47,7 @@ public class WebAppFactoryFixture : WebApplicationFactory using var scope = serviceProvider.CreateScope(); var scopedServices = scope.ServiceProvider; - var dbContext = scopedServices.GetRequiredService(); + var dbContext = scopedServices.GetRequiredService(); dbContext.Database.EnsureCreatedAndMigrated(); dbContext.SaveChanges(); }); @@ -54,8 +55,8 @@ public class WebAppFactoryFixture : WebApplicationFactory public override async ValueTask DisposeAsync() { - var dbContext = new PersistenceDbContext( - new DbContextOptionsBuilder() + var dbContext = new PersistencePostgresContext( + new DbContextOptionsBuilder() .UseNpgsql(connectionString) .Options);