diff --git a/.gitignore b/.gitignore index bdba052..982abde 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,8 @@ fabric.properties # Vitepress docs/.vitepress/dist docs/.vitepress/cache + +.direnv +.envrc +.launch +PhenX.EntityFrameworkCore.BulkInsert.sln.DotSettings.user diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs index 8c396cd..a05a838 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs @@ -24,13 +24,13 @@ public abstract class TestDbContainer(IMessage protected abstract TBuilderEntity CreateBuilder(); - protected virtual string DbmsName => typeof(TContainerEntity).Name.Replace("Container", ""); + protected virtual string DbmsName => GetType().Name.Replace("TestDbContainer", ""); protected override TBuilderEntity Configure() { var targetFramework = GetType().Assembly.GetCustomAttributes().FirstOrDefault(e => e.Key == "TargetFramework")?.Value ?? "NA"; return CreateBuilder() - .WithReuse(true) + .WithReuse(false) .WithName($"PhenX.EntityFrameworkCore.BulkInsert.Tests.{DbmsName}-{targetFramework}") .WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSqlSnakeCase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSqlSnakeCase.cs new file mode 100644 index 0000000..2455c39 --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSqlSnakeCase.cs @@ -0,0 +1,39 @@ +using System.Data.Common; + +using Microsoft.EntityFrameworkCore; + +using Npgsql; + +using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; + +using Testcontainers.PostgreSql; + +using Xunit; +using Xunit.Abstractions; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; + +[CollectionDefinition(Name)] +public class TestDbContainerPostgreSqlSnakeCaseCollection : ICollectionFixture +{ + public const string Name = "PostgreSqlSnakeCase"; +} + +public class TestDbContainerPostgreSqlSnakeCase(IMessageSink messageSink) : TestDbContainer(messageSink) +{ + public override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; + + // GeoSpatial support, using imresamu/postgis instead of postgis/postgis for arm64 support, see https://github.com/postgis/docker-postgis/issues/216#issuecomment-2936824962 + protected override PostgreSqlBuilder CreateBuilder() => new("imresamu/postgis:17-3.5"); + + protected override void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName) + { + optionsBuilder + .UseNpgsql(GetConnectionString(databaseName), o => + { + o.UseNetTopologySuite(); + }) + .UseSnakeCaseNamingConvention() + .UseBulkInsertPostgreSql(); + } +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs index a895781..475163c 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs @@ -1,177 +1,179 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -using SmartEnum.EFCore; - -namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; - -public class TestDbContext : TestDbContextBase -{ - public DbSet TestEntities { get; set; } = null!; - public DbSet TestEntitiesWithSimpleTypes { get; set; } = null!; - public DbSet TestEntitiesWithJson { get; set; } = null!; - public DbSet TestEntitiesWithGuidId { get; set; } = null!; - public DbSet TestEntitiesWithConverter { get; set; } = null!; - public DbSet TestEntitiesWithComplexType { get; set; } = null!; - public DbSet TestEntitiesWithSmartEnum { get; set; } = null!; - public DbSet TestEntitiesWithSpecialColumnNames { get; set; } = null!; - public DbSet Students { get; set; } = null!; - public DbSet Courses { get; set; } = null!; - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureSmartEnum(); - - modelBuilder.Entity(builder => - { - builder.Property(e => e.CreatedAt) - .HasConversion(new DateTimeToBinaryConverter()); - }); - - modelBuilder.Entity(builder => - { - builder - .ComplexProperty(e => e.OwnedComplexType) - .IsRequired(); - }); - - // Many-to-many with shadow property - modelBuilder.Entity() - .HasMany(s => s.Courses) - .WithMany(c => c.Students) - .UsingEntity>( - "StudentCourse", - j => j.HasOne().WithMany().HasForeignKey("CourseId"), - j => j.HasOne().WithMany().HasForeignKey("StudentId"), - j => - { - j.Property("EnrolledAt"); - j.HasKey("StudentId", "CourseId"); - } - ); - - // Keyless entity type - modelBuilder.Entity(builder => - { - builder.HasNoKey(); - // ToView will use the given table name read-only, it doesn't have to actually be a database view. - // We just reuse the table for the standard TestEntity. - builder.ToView("test_entity"); - }); - } -} - -public class TestDbContextPostgreSql : TestDbContext -{ - public DbSet TestEntitiesWithArrays { get; set; } = null!; - public DbSet TestEntitiesWithEnumList { get; set; } = null!; - public DbSet TestEntitiesWithEnumArray { get; set; } = null!; - public DbSet TestEntitiesWithIntList { get; set; } = null!; - public DbSet TestEntitiesWithEnumListExplicitType { get; set; } = null!; - public DbSet TestRecordsWithEnumList { get; set; } = null!; - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(x => x.JsonArray).AsJsonString("jsonb"); - b.Property(x => x.JsonObject).AsJsonString("jsonb"); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.StringEnumValue).HasColumnType("text"); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.EnumList).HasColumnType("integer[]"); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.TestRun).HasColumnName("test_run"); - b.Property(x => x.Values).HasColumnName("values"); - }); - } -} - -public class TestDbContextMySql : TestDbContext -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(x => x.JsonArray).AsJsonString("json"); - b.Property(x => x.JsonObject).AsJsonString("json"); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.StringEnumValue).HasColumnType("text"); - }); - } -} - -public class TestDbContextSqlServer : TestDbContext -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(x => x.JsonArray).AsJsonString(null); - b.Property(x => x.JsonObject).AsJsonString(null); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.StringEnumValue).HasColumnType("text"); - }); - } -} - -public class TestDbContextSqlite : TestDbContext -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(x => x.JsonArray).AsJsonString(null); - b.Property(x => x.JsonObject).AsJsonString(null); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.StringEnumValue).HasColumnType("text"); - }); - } -} - -public class TestDbContextOracle : TestDbContext -{ - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.Property(x => x.JsonArray).AsJsonString(null); - b.Property(x => x.JsonObject).AsJsonString(null); - }); - - modelBuilder.Entity(b => - { - b.Property(x => x.StringEnumValue).HasColumnType("nvarchar2(255)"); - }); - } -} +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +using SmartEnum.EFCore; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +public class TestDbContext : TestDbContextBase +{ + public DbSet TestEntities { get; set; } = null!; + public DbSet TestEntitiesWithSimpleTypes { get; set; } = null!; + public DbSet TestEntitiesWithJson { get; set; } = null!; + public DbSet TestEntitiesWithGuidId { get; set; } = null!; + public DbSet TestEntitiesWithConverter { get; set; } = null!; + public DbSet TestEntitiesWithComplexType { get; set; } = null!; + public DbSet TestEntitiesWithSmartEnum { get; set; } = null!; + public DbSet TestEntitiesWithSpecialColumnNames { get; set; } = null!; + public DbSet Students { get; set; } = null!; + public DbSet Courses { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureSmartEnum(); + + modelBuilder.Entity(builder => + { + builder.Property(e => e.CreatedAt) + .HasConversion(new DateTimeToBinaryConverter()); + }); + + modelBuilder.Entity(builder => + { + builder + .ComplexProperty(e => e.OwnedComplexType) + .IsRequired(); + }); + + // Many-to-many with shadow property + modelBuilder.Entity() + .HasMany(s => s.Courses) + .WithMany(c => c.Students) + .UsingEntity>( + "StudentCourse", + j => j.HasOne().WithMany().HasForeignKey("CourseId"), + j => j.HasOne().WithMany().HasForeignKey("StudentId"), + j => + { + j.Property("EnrolledAt"); + j.HasKey("StudentId", "CourseId"); + } + ); + + // Keyless entity type + modelBuilder.Entity(builder => + { + builder.HasNoKey(); + // ToView will use the given table name read-only, it doesn't have to actually be a database view. + // We just reuse the table for the standard TestEntity. + builder.ToView("test_entity"); + }); + } +} + +public class TestDbContextPostgreSql : TestDbContext +{ + public DbSet TestEntitiesWithArrays { get; set; } = null!; + public DbSet TestEntitiesWithEnumList { get; set; } = null!; + public DbSet TestEntitiesWithEnumArray { get; set; } = null!; + public DbSet TestEntitiesWithIntList { get; set; } = null!; + public DbSet TestEntitiesWithEnumListExplicitType { get; set; } = null!; + public DbSet TestRecordsWithEnumList { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().Property(e => e.IntArray).HasDefaultValueSql("'{}'"); + + modelBuilder.Entity(b => + { + b.Property(x => x.JsonArray).AsJsonString("jsonb"); + b.Property(x => x.JsonObject).AsJsonString("jsonb"); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.StringEnumValue).HasColumnType("text"); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.EnumList).HasColumnType("integer[]"); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.TestRun).HasColumnName("test_run"); + b.Property(x => x.Values).HasColumnName("values"); + }); + } +} + +public class TestDbContextMySql : TestDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(x => x.JsonArray).AsJsonString("json"); + b.Property(x => x.JsonObject).AsJsonString("json"); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.StringEnumValue).HasColumnType("text"); + }); + } +} + +public class TestDbContextSqlServer : TestDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(x => x.JsonArray).AsJsonString(null); + b.Property(x => x.JsonObject).AsJsonString(null); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.StringEnumValue).HasColumnType("text"); + }); + } +} + +public class TestDbContextSqlite : TestDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(x => x.JsonArray).AsJsonString(null); + b.Property(x => x.JsonObject).AsJsonString(null); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.StringEnumValue).HasColumnType("text"); + }); + } +} + +public class TestDbContextOracle : TestDbContext +{ + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(x => x.JsonArray).AsJsonString(null); + b.Property(x => x.JsonObject).AsJsonString(null); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.StringEnumValue).HasColumnType("nvarchar2(255)"); + }); + } +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index 8672007..8adfc56 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -38,18 +38,21 @@ + + + diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Arrays/ArrayTestsPostgreSqlSnakeCase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Arrays/ArrayTestsPostgreSqlSnakeCase.cs new file mode 100644 index 0000000..ad18ecc --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Arrays/ArrayTestsPostgreSqlSnakeCase.cs @@ -0,0 +1,12 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Arrays; + +[Trait("Category", "PostgreSqlSnakeCase")] +[Collection(TestDbContainerPostgreSqlSnakeCaseCollection.Name)] +public class ArrayTestsPostgreSqlSnakeCase(TestDbContainerPostgreSqlSnakeCase dbContainer) : ArrayTestsBase(dbContainer) +{ +}