From 7510ff7ee4af36c54fc21dd582ec33f6fd2326c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Wed, 7 Feb 2024 14:37:38 +0300 Subject: [PATCH 01/26] add deps for oracle; update other deps --- ...abs.EntityFrameworkCore.Upsert.IntegrationTests.csproj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj index 4f1d494..6c25fa0 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj @@ -17,6 +17,7 @@ + @@ -25,9 +26,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + all From b6bbe3fade175221557180ff65551c0af6a69c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Wed, 7 Feb 2024 14:38:03 +0300 Subject: [PATCH 02/26] register oracle as supported driver --- .../Base/DbDriver.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/DbDriver.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/DbDriver.cs index 86ab160..ac5e5f0 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/DbDriver.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/DbDriver.cs @@ -7,5 +7,6 @@ public enum DbDriver MySQL, InMemory, Sqlite, + Oracle, } } From 0dd6b0b7278d7b757a48b96abc30160532b63dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Wed, 7 Feb 2024 14:38:25 +0300 Subject: [PATCH 03/26] write test runner for oracle (failing at this moment) --- .../DbTests_Oracle.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs new file mode 100644 index 0000000..1aa8b15 --- /dev/null +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs @@ -0,0 +1,34 @@ +using DotNet.Testcontainers.Containers; +using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; +using FlexLabs.EntityFrameworkCore.Upsert.Tests.EF; +using Microsoft.EntityFrameworkCore; +using Testcontainers.Oracle; +using Xunit; + +namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests +{ +#if !NOORACLE + public class DbTests_Oracle : DbTestsBase, IClassFixture + { + public sealed class DatabaseInitializer : DatabaseInitializerFixture + { + public override DbDriver DbDriver => DbDriver.Oracle; + + protected override IContainer BuildContainer() + => new OracleBuilder() + .Build(); + + protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) + { + var connectionString = (TestContainer as IDatabaseContainer)?.GetConnectionString(); + builder.UseOracle(connectionString); + } + } + + public DbTests_Oracle(DatabaseInitializer contexts) + : base(contexts) + { + } + } +#endif +} From b83a8f664a0a13f0bcc612a3f7a76b34fa32cfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Wed, 7 Feb 2024 14:39:56 +0300 Subject: [PATCH 04/26] register command runner --- .../Runners/DefaultRunners.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/DefaultRunners.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/DefaultRunners.cs index ddaefa8..84bc0ee 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/DefaultRunners.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/DefaultRunners.cs @@ -19,6 +19,7 @@ public static IUpsertCommandRunner[] GetRunners() new PostgreSqlUpsertCommandRunner(), new SqlServerUpsertCommandRunner(), new SqliteUpsertCommandRunner(), + new OracleUpsertCommandRunner(), }; return Runners; } From af075a65d3cf153ba62987abe2ea1aa85137347a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Wed, 7 Feb 2024 14:40:08 +0300 Subject: [PATCH 05/26] add new command runner for oracle --- .../Runners/OracleUpsertCommandRunner.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs new file mode 100644 index 0000000..4bede6e --- /dev/null +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FlexLabs.EntityFrameworkCore.Upsert.Internal; + +namespace FlexLabs.EntityFrameworkCore.Upsert.Runners; + +/// +/// Upsert command runner for the Oracle.EntityFrameworkCore provider +/// +public class OracleUpsertCommandRunner : RelationalUpsertCommandRunner +{ + /// + public override bool Supports(string providerName) => providerName == "Oracle.EntityFrameworkCore"; + + /// + public override string GenerateCommand(string tableName, + ICollection> + entities, ICollection<(string ColumnName, bool IsNullable)> joinColumns, + ICollection<(string ColumnName, IKnownValue Value)>? updateExpressions, + KnownExpression? updateCondition) + { + var result = new StringBuilder(); + + result.Append($"MERGE INTO {tableName} t USING ( SELECT"); + result.Append(string.Join(", ", + entities.Select(ec => string.Join(" AS ", ec.Select(e => new List { Parameter(e.Value.ArgumentIndex), e.ColumnName }))))); + result.Append("FROM dual) s"); + result.Append("ON ("); + result.Append(string.Join(" AND ", joinColumns.Select(c => c.IsNullable + ? $"((s.[{c.ColumnName}] IS NULL AND t.[{c.ColumnName}] IS NULL) OR (s.[{c.ColumnName}] IS NOT NULL AND t.[{c.ColumnName}] = t.[{c.ColumnName}]))" + : $"t.[{c.ColumnName}] = s.[{c.ColumnName}]"))); + result.Append(" WHEN NOT MATCHED THEN INSERT ("); + result.Append(string.Join(", ", + entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); + result.Append(") VALUES ("); + result.Append(string.Join(", ", + entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); + result.Append(')'); + if (updateExpressions != null) + { + result.Append(" WHEN MATCHED"); + if (updateCondition != null) + result.Append($" AND {ExpandExpression(updateCondition)}"); + result.Append(" THEN UPDATE SET "); + result.Append(string.Join(", ", + updateExpressions.Select((e, i) => $"{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); + } + + result.Append(';'); + return result.ToString(); + } + + /// + protected override string EscapeName(string name) => "\"" + name + "\""; + + /// + protected override string? SourcePrefix => "s."; + + /// + protected override string? TargetPrefix => "t."; +} From 158a84d7a6268a22e5d5cc1f608744b7fa676c17 Mon Sep 17 00:00:00 2001 From: Daniil Date: Fri, 9 Feb 2024 15:55:29 +0300 Subject: [PATCH 06/26] update generated query --- .../Runners/OracleUpsertCommandRunner.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index 4bede6e..cfd2e66 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -22,29 +22,32 @@ public override string GenerateCommand(string tableName, { var result = new StringBuilder(); - result.Append($"MERGE INTO {tableName} t USING ( SELECT"); - result.Append(string.Join(", ", - entities.Select(ec => string.Join(" AS ", ec.Select(e => new List { Parameter(e.Value.ArgumentIndex), e.ColumnName }))))); - result.Append("FROM dual) s"); - result.Append("ON ("); - result.Append(string.Join(" AND ", joinColumns.Select(c => c.IsNullable - ? $"((s.[{c.ColumnName}] IS NULL AND t.[{c.ColumnName}] IS NULL) OR (s.[{c.ColumnName}] IS NOT NULL AND t.[{c.ColumnName}] = t.[{c.ColumnName}]))" - : $"t.[{c.ColumnName}] = s.[{c.ColumnName}]"))); + result.Append($"MERGE INTO {tableName} t USING ("); + result.Append("SELECT "); + result.Append(string.Join("", string.Join(" ", entities.Select(ec => string.Join(", ", + ec.Select(e => string.Join(" AS ", ExpandValue(e.Value), EscapeName(e.ColumnName)))))))); + result.Append(" FROM dual ) s ON ("); + result.Append(string.Join(" AND ", + joinColumns.Select(j => $"t.{EscapeName(j.ColumnName)} = s.{EscapeName(j.ColumnName)}"))); + result.Append(") "); result.Append(" WHEN NOT MATCHED THEN INSERT ("); result.Append(string.Join(", ", entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); result.Append(") VALUES ("); result.Append(string.Join(", ", - entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); - result.Append(')'); - if (updateExpressions != null) + entities.First().Where(e => e.AllowInserts).Select(e => ExpandValue(e.Value)))); + result.Append(") "); + if (updateExpressions is not null) { - result.Append(" WHEN MATCHED"); - if (updateCondition != null) - result.Append($" AND {ExpandExpression(updateCondition)}"); - result.Append(" THEN UPDATE SET "); + result.Append("WHEN MATCHED "); + if (updateCondition is not null) + { + result.Append($" AND {ExpandExpression(updateCondition)} "); + } + + result.Append("THEN UPDATE SET "); result.Append(string.Join(", ", - updateExpressions.Select((e, i) => $"{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); + updateExpressions.Select(e => $"t.{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); } result.Append(';'); From dc64e7a1c3a53f53ab16d9cabb7144a69c61c7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 13:16:39 +0300 Subject: [PATCH 07/26] adapt to oracle requirements --- .../Runners/OracleUpsertCommandRunner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index cfd2e66..b1ac673 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using FlexLabs.EntityFrameworkCore.Upsert.Internal; @@ -50,16 +51,18 @@ public override string GenerateCommand(string tableName, updateExpressions.Select(e => $"t.{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); } - result.Append(';'); return result.ToString(); } /// - protected override string EscapeName(string name) => "\"" + name + "\""; + protected override string EscapeName([NotNull] string name) => name.ToUpperInvariant(); /// protected override string? SourcePrefix => "s."; /// protected override string? TargetPrefix => "t."; + + /// + protected override string Parameter(int index) => $":p{index}"; } From 49c31280a52d7cd3864c45492580f1c4f574f6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 14:18:37 +0300 Subject: [PATCH 08/26] reformat code --- .../DbTestsBase.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs index 35fa5b3..8d3fbf8 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs @@ -25,6 +25,7 @@ public DbTestsBase(DatabaseInitializerFixture fixture) ISO = "AU", Created = NewDateTime(1970, 1, 1), }; + readonly PageVisit _dbVisitOld = new() { UserID = 1, @@ -33,6 +34,7 @@ public DbTestsBase(DatabaseInitializerFixture fixture) FirstVisit = NewDateTime(1970, 1, 1), LastVisit = NewDateTime(1970, 1, 1), }; + readonly PageVisit _dbVisit = new() { UserID = 1, @@ -41,35 +43,43 @@ public DbTestsBase(DatabaseInitializerFixture fixture) FirstVisit = NewDateTime(1970, 1, 1), LastVisit = NewDateTime(1970, 1, 1), }; + readonly Status _dbStatus = new() { ID = 1, Name = "Created", LastChecked = NewDateTime(1970, 1, 1), }; + readonly Book _dbBook = new() { Name = "The Fellowship of the Ring", Genres = new[] { "Fantasy" }, }; + readonly NullableCompositeKey _nullableKey1 = new() { ID1 = 1, ID2 = 2, Value = "First", }; + readonly NullableCompositeKey _nullableKey2 = new() { ID1 = 1, ID2 = null, Value = "Second", }; + readonly ComputedColumn _computedColumn = new() { Num1 = 1, Num2 = 7, }; - readonly static DateTime _now = NewDateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); + + readonly static DateTime _now = NewDateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, + DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); + readonly static DateTime _today = NewDateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); readonly int _increment = 8; From 6dc1c86ad280da21a166ab3c2694159c76c72134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 14:37:07 +0300 Subject: [PATCH 09/26] add disabling schema in oracle db along with mysql --- .../Base/TestDbContext.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs index bb6f707..6bf6764 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs @@ -37,7 +37,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasIndex(b => b.Num1).IsUnique(); modelBuilder.Entity().Property(e => e.Num2).UseIdentityAlwaysColumn(); modelBuilder.Entity().HasIndex(b => b.Num1).IsUnique(); - modelBuilder.Entity().Property(e => e.Num3).HasComputedColumnSql($"{EscapeColumn(dbProvider, nameof(ComputedColumn.Num2))} + 1", stored: true); + modelBuilder.Entity().Property(e => e.Num3) + .HasComputedColumnSql($"{EscapeColumn(dbProvider, nameof(ComputedColumn.Num2))} + 1", stored: true); if (dbProvider.Name == "Npgsql.EntityFrameworkCore.PostgreSQL") { @@ -48,9 +49,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Ignore(j => j.Child); } - if (dbProvider.Name != "Pomelo.EntityFrameworkCore.MySql") // Can't have a default value on TEXT columns in MySql + + if (dbProvider.Name != + "Pomelo.EntityFrameworkCore.MySql") // Can't have a default value on TEXT columns in MySql modelBuilder.Entity().Property(e => e.Text).HasDefaultValue("B"); - if (dbProvider.Name == "Pomelo.EntityFrameworkCore.MySql") // Can't have table schemas in MySql + if (dbProvider.Name is "Pomelo.EntityFrameworkCore.MySql" + or "Oracle.EntityFrameworkCore") // Can't have table schemas in MySql and Oracle modelBuilder.Entity().Metadata.SetSchema(null); } From 741abb37270403ffb55f4b554ac6f7d2afc452e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 14:39:18 +0300 Subject: [PATCH 10/26] add rule to escape columns for oracle --- .../Base/TestDbContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs index 6bf6764..16aeb7c 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs @@ -64,6 +64,7 @@ private string EscapeColumn(IDatabaseProvider dbProvider, string columnName) "Pomelo.EntityFrameworkCore.MySql" => $"`{columnName}`", "Npgsql.EntityFrameworkCore.PostgreSQL" => $"\"{columnName}\"", "Microsoft.EntityFrameworkCore.Sqlite" => $"\"{columnName}\"", + "Oracle.EntityFrameworkCore" => columnName.ToUpper(), _ => $"[{columnName}]" }; From 5b18e6f4b662675f91ec768f1584bc6d9766cba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 14:40:11 +0300 Subject: [PATCH 11/26] add dependency to generate correct namings in oracle database --- .../DbTests_Oracle.cs | 4 +++- ...lexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs index 1aa8b15..55e7da2 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs @@ -21,7 +21,9 @@ protected override IContainer BuildContainer() protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { var connectionString = (TestContainer as IDatabaseContainer)?.GetConnectionString(); - builder.UseOracle(connectionString); + builder + .UseOracle(connectionString) + .UseUpperSnakeCaseNamingConvention(); } } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj index 6c25fa0..66e1dc0 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj @@ -12,6 +12,7 @@ + From 938f9a31076f07b16d36d5ce2d919dd6c51cdd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 14:58:50 +0300 Subject: [PATCH 12/26] fix escaping --- .../Runners/OracleUpsertCommandRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index b1ac673..13d4068 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -55,7 +55,7 @@ public override string GenerateCommand(string tableName, } /// - protected override string EscapeName([NotNull] string name) => name.ToUpperInvariant(); + protected override string EscapeName([NotNull] string name) => $"\"{name.ToUpperInvariant()}\""; /// protected override string? SourcePrefix => "s."; From 5e837e7e9f91cfd870cba1b273f6628ce4831d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 15:21:47 +0300 Subject: [PATCH 13/26] override operation generating methods to support bitwise functions --- .../Runners/OracleUpsertCommandRunner.cs | 111 +++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index 13d4068..a3a18e4 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Linq.Expressions; using System.Text; using FlexLabs.EntityFrameworkCore.Upsert.Internal; @@ -54,6 +56,113 @@ public override string GenerateCommand(string tableName, return result.ToString(); } + /// + protected override string ExpandExpression(KnownExpression expression, + Func? expandLeftColumn = null) + { + ArgumentNullException.ThrowIfNull(expression); + + switch (expression.ExpressionType) + { + case ExpressionType.Add: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.Multiply: + case ExpressionType.Subtract: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + var op = GetSimpleOperator(expression.ExpressionType); + return $"{left} {op} {right}"; + } + case ExpressionType.Equal: + case ExpressionType.NotEqual: + { + var value1Null = expression.Value1 is ConstantValue constant1 && constant1.Value == null; + var value2Null = expression.Value2 is ConstantValue constant2 && constant2.Value == null; + if (value1Null || value2Null) + { + return IsNullExpression(value2Null ? expression.Value1! : expression.Value2!, + expression.ExpressionType == ExpressionType.NotEqual); + } + + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + var op = GetSimpleOperator(expression.ExpressionType); + return $"{left} {op} {right}"; + } + + case ExpressionType.Coalesce: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"COALESCE({left}, {right})"; + } + + case ExpressionType.Conditional: + { + var ifTrue = ExpandValue(expression.Value1, expandLeftColumn); + var ifFalse = ExpandValue(expression.Value2!, expandLeftColumn); + var test = ExpandValue(expression.Value3!, expandLeftColumn); + return $"CASE WHEN {test} THEN {ifTrue} ELSE {ifFalse} END"; + } + + case ExpressionType.MemberAccess: + case ExpressionType.Constant: + { + return ExpandValue(expression.Value1, expandLeftColumn); + } + + case ExpressionType.AndAlso: + case ExpressionType.OrElse: + { + var exp = expression.ExpressionType == ExpressionType.AndAlso ? "AND" : "OR"; + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"{left} {exp} {right}"; + } + case ExpressionType.And: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"BITAND({left}, {right})"; + } + case ExpressionType.Or: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"BITOR({left}, {right})"; + } + + default: + throw new NotSupportedException("Don't know how to process operation: " + expression.ExpressionType); + } + } + + /// + protected override string GetSimpleOperator(ExpressionType expressionType) + { + return expressionType switch + { + ExpressionType.Add => "+", + ExpressionType.Divide => "/", + ExpressionType.Modulo => "%", + ExpressionType.Multiply => "*", + ExpressionType.Subtract => "-", + ExpressionType.LessThan => "<", + ExpressionType.LessThanOrEqual => "<=", + ExpressionType.GreaterThan => ">", + ExpressionType.GreaterThanOrEqual => ">=", + ExpressionType.Equal => "=", + ExpressionType.NotEqual => "!=", + _ => throw new InvalidOperationException($"{expressionType} is not a simple arithmetic operation"), + }; + } + /// protected override string EscapeName([NotNull] string name) => $"\"{name.ToUpperInvariant()}\""; From b2dfb09861af2d3c816a20e372fc583fd20e55b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 15:45:11 +0300 Subject: [PATCH 14/26] move specifying schema name to db context --- .../Base/Tables.cs | 2 -- .../Base/TestDbContext.cs | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/Tables.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/Tables.cs index 34e2e84..728262d 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/Tables.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/Tables.cs @@ -66,8 +66,6 @@ public static readonly Expression> MatchKey public DateTime FirstVisit { get; set; } public DateTime LastVisit { get; set; } } - - [Table("SchemaTable", Schema = "testsch")] public class SchemaTable { public int ID { get; set; } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs index 16aeb7c..bd2afc2 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs @@ -55,7 +55,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().Property(e => e.Text).HasDefaultValue("B"); if (dbProvider.Name is "Pomelo.EntityFrameworkCore.MySql" or "Oracle.EntityFrameworkCore") // Can't have table schemas in MySql and Oracle + { modelBuilder.Entity().Metadata.SetSchema(null); + } + else + { + modelBuilder.Entity().Metadata.SetSchema("testsch"); + } } private string EscapeColumn(IDatabaseProvider dbProvider, string columnName) From e1f9e9d042a70b35a9e11201861069d117643836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 15:45:53 +0300 Subject: [PATCH 15/26] make modulo operation call function in oracle --- .../Runners/OracleUpsertCommandRunner.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index a3a18e4..23c49bf 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -66,7 +66,6 @@ protected override string ExpandExpression(KnownExpression expression, { case ExpressionType.Add: case ExpressionType.Divide: - case ExpressionType.Modulo: case ExpressionType.Multiply: case ExpressionType.Subtract: case ExpressionType.LessThan: @@ -137,6 +136,12 @@ protected override string ExpandExpression(KnownExpression expression, var right = ExpandValue(expression.Value2!, expandLeftColumn); return $"BITOR({left}, {right})"; } + case ExpressionType.Modulo: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"MOD({left}, {right})"; + } default: throw new NotSupportedException("Don't know how to process operation: " + expression.ExpressionType); @@ -150,7 +155,6 @@ protected override string GetSimpleOperator(ExpressionType expressionType) { ExpressionType.Add => "+", ExpressionType.Divide => "/", - ExpressionType.Modulo => "%", ExpressionType.Multiply => "*", ExpressionType.Subtract => "-", ExpressionType.LessThan => "<", From 2ee8d816ee3359117187354b96d2bc15ba39b36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 17:02:54 +0300 Subject: [PATCH 16/26] disable test on oracle due to lack of support of nullable primary keys --- .../DbTestsBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs index 8d3fbf8..00738c6 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs @@ -1257,7 +1257,8 @@ public void Upsert_KeyOnly() [Fact] public void Upsert_NullableKeys() { - if (_fixture.DbDriver == DbDriver.MySQL || _fixture.DbDriver == DbDriver.Postgres || _fixture.DbDriver == DbDriver.Sqlite) + if (_fixture.DbDriver == DbDriver.MySQL || _fixture.DbDriver == DbDriver.Postgres || + _fixture.DbDriver == DbDriver.Sqlite || _fixture.DbDriver == DbDriver.Oracle) return; ResetDb(); From 38c830f4125fd0104a9605d2c6cf40943b00700d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Mon, 12 Feb 2024 17:03:35 +0300 Subject: [PATCH 17/26] reformat code --- .../DbTestsBase.cs | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs index 00738c6..ffb6c0f 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs @@ -442,7 +442,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -472,7 +473,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_FromVar() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + increment)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, + expectedVisits: _dbVisit.Visits + increment)); } [Fact] @@ -501,7 +503,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_FromField() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + _increment)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, + expectedVisits: _dbVisit.Visits + _increment)); } [Fact] @@ -530,7 +533,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_FromSource_ValueAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -559,7 +563,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_FromSource_ColumnAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + newVisit.Visits)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, + expectedVisits: _dbVisit.Visits + newVisit.Visits)); } [Fact] @@ -588,7 +593,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_Reversed() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -617,7 +623,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueSubtract() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits - 2)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits - 2)); } [Fact] @@ -646,7 +653,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueMultiply() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits * 3)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits * 3)); } [Fact] @@ -675,7 +683,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueBitwiseOr() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits | 3)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits | 3)); } [Fact] @@ -704,7 +713,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueBitwiseAnd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits & 3)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits & 3)); } [Fact] @@ -733,7 +743,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueDivide() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits / 4)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits / 4)); } [Fact] @@ -762,7 +773,8 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueModulo() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits % 4)); + visit => visit.Should() + .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits % 4)); } [Fact] @@ -799,7 +811,8 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1), + visit => visit.Should() + .MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1), visit => visit.Should().MatchModel(newVisit2)); } @@ -875,8 +888,10 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched_MultipleUpdate() .Run(); dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( - visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisitOld.Visits + 1), - visit => visit.Should().MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, + expectedVisits: _dbVisitOld.Visits + 1), + visit => visit.Should() + .MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -912,8 +927,10 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched_MultipleUpdate_FromSourc .Run(); dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( - visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisitOld.Visits + 1), - visit => visit.Should().MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, + expectedVisits: _dbVisitOld.Visits + 1), + visit => visit.Should() + .MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -1692,7 +1709,8 @@ public void Upsert_UpdateCondition_ValueCheck() [Fact] public void Upsert_UpdateCondition_ValueCheck_UpdateColumnFromCondition() { - if (BuildEnvironment.IsGitHub && _fixture.DbDriver == DbDriver.MySQL && Environment.OSVersion.Platform == PlatformID.Unix) + if (BuildEnvironment.IsGitHub && _fixture.DbDriver == DbDriver.MySQL && + Environment.OSVersion.Platform == PlatformID.Unix) { // Disabling this test on GitHub Ubuntu images - they're cursed? return; From 432f075328134ce18616c9e5eddb91475f9e4b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 08:33:12 +0300 Subject: [PATCH 18/26] fix most tests with update condition --- .../Runners/OracleUpsertCommandRunner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index 23c49bf..c3b897f 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -43,14 +43,14 @@ public override string GenerateCommand(string tableName, if (updateExpressions is not null) { result.Append("WHEN MATCHED "); - if (updateCondition is not null) - { - result.Append($" AND {ExpandExpression(updateCondition)} "); - } result.Append("THEN UPDATE SET "); result.Append(string.Join(", ", updateExpressions.Select(e => $"t.{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); + if (updateCondition is not null) + { + result.Append($" WHERE {ExpandExpression(updateCondition)} "); + } } return result.ToString(); From 95cf141b335cc1b34f8594a688b6109a19ccecd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 09:18:07 +0300 Subject: [PATCH 19/26] fixed one more test --- .../Runners/OracleUpsertCommandRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index c3b897f..cf77ce4 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -168,7 +168,7 @@ protected override string GetSimpleOperator(ExpressionType expressionType) } /// - protected override string EscapeName([NotNull] string name) => $"\"{name.ToUpperInvariant()}\""; + protected override string EscapeName([NotNull] string name) => $"\"{name}\""; /// protected override string? SourcePrefix => "s."; From 534965f2992edb4c790c11d27195f483d4c2fb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 12:53:53 +0300 Subject: [PATCH 20/26] fix selecting columns in source --- .../Runners/OracleUpsertCommandRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index cf77ce4..3742a50 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -27,8 +27,8 @@ public override string GenerateCommand(string tableName, result.Append($"MERGE INTO {tableName} t USING ("); result.Append("SELECT "); - result.Append(string.Join("", string.Join(" ", entities.Select(ec => string.Join(", ", - ec.Select(e => string.Join(" AS ", ExpandValue(e.Value), EscapeName(e.ColumnName)))))))); + result.Append(string.Join(", ", entities.First().Select(ec => string.Join(" AS ", ExpandValue(ec.Value), + EscapeName(ec.ColumnName))))); result.Append(" FROM dual ) s ON ("); result.Append(string.Join(" AND ", joinColumns.Select(j => $"t.{EscapeName(j.ColumnName)} = s.{EscapeName(j.ColumnName)}"))); From dc887c4bfd80af2f506d13d182d040b5494c4b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 17:42:13 +0300 Subject: [PATCH 21/26] update query generator to make work multiple upsert --- .../Runners/OracleUpsertCommandRunner.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index 3742a50..8258a6e 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -23,13 +23,22 @@ public override string GenerateCommand(string tableName, ICollection<(string ColumnName, IKnownValue Value)>? updateExpressions, KnownExpression? updateCondition) { + ArgumentNullException.ThrowIfNull(entities); var result = new StringBuilder(); result.Append($"MERGE INTO {tableName} t USING ("); - result.Append("SELECT "); - result.Append(string.Join(", ", entities.First().Select(ec => string.Join(" AS ", ExpandValue(ec.Value), - EscapeName(ec.ColumnName))))); - result.Append(" FROM dual ) s ON ("); + foreach (var item in entities.Select((e, ind) => new {e, ind})) + { + result.Append(" SELECT "); + result.Append(string.Join(", ", item.e.Select(ec => string.Join(" AS ", ExpandValue(ec.Value), + EscapeName(ec.ColumnName))))); + result.Append(" FROM dual"); + if (entities.Count > 1 && item.ind != entities.Count - 1) + { + result.Append(" UNION ALL "); + } + } + result.Append(") s ON ("); result.Append(string.Join(" AND ", joinColumns.Select(j => $"t.{EscapeName(j.ColumnName)} = s.{EscapeName(j.ColumnName)}"))); result.Append(") "); @@ -38,7 +47,7 @@ public override string GenerateCommand(string tableName, entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); result.Append(") VALUES ("); result.Append(string.Join(", ", - entities.First().Where(e => e.AllowInserts).Select(e => ExpandValue(e.Value)))); + entities.First().Where(e => e.AllowInserts).Select(e => $"s.{EscapeName(e.ColumnName)}"))); result.Append(") "); if (updateExpressions is not null) { @@ -178,4 +187,7 @@ protected override string GetSimpleOperator(ExpressionType expressionType) /// protected override string Parameter(int index) => $":p{index}"; + + /// + protected override int? MaxQueryParams => 1000; } From 355b7f8a5a4953a60bd3bb677f5558e95e2d37c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 17:43:48 +0300 Subject: [PATCH 22/26] add oracle mention to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8062c60..bf6659a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ CI build: [![FlexLabs.EntityFrameworkCore.Upsert on MyGet](https://img.shields.i This library adds basic support for "Upsert" operations to EF Core. -Uses `INSERT … ON CONFLICT DO UPDATE` in PostgreSQL/Sqlite, `MERGE` in SqlServer and `INSERT INTO … ON DUPLICATE KEY UPDATE` in MySQL. +Uses `INSERT … ON CONFLICT DO UPDATE` in PostgreSQL/Sqlite, `MERGE` in SqlServer & Oracle and `INSERT INTO … ON DUPLICATE KEY UPDATE` in MySQL. Also supports injecting sql command runners to add support for other providers From 07dac758df55dda3fdec3316a1d36a269471df7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D0=BB=D1=83=D0=B1=D0=B5=D0=B2=20=D0=94=2E?= =?UTF-8?q?=D0=9E=2E?= Date: Tue, 13 Feb 2024 18:44:03 +0300 Subject: [PATCH 23/26] add unit tests for oracle --- .../Runners/OracleUpsertCommandRunnerTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/FlexLabs.EntityFrameworkCore.Upsert.Tests/Runners/OracleUpsertCommandRunnerTests.cs diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.Tests/Runners/OracleUpsertCommandRunnerTests.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.Tests/Runners/OracleUpsertCommandRunnerTests.cs new file mode 100644 index 0000000..e559c77 --- /dev/null +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.Tests/Runners/OracleUpsertCommandRunnerTests.cs @@ -0,0 +1,54 @@ +using FlexLabs.EntityFrameworkCore.Upsert.Runners; + +namespace FlexLabs.EntityFrameworkCore.Upsert.Tests.Runners +{ + public class OracleUpsertCommandRunnerTests : RelationalCommandRunnerTestsBase + { + public OracleUpsertCommandRunnerTests() + : base("Oracle.EntityFrameworkCore") + { + } + + protected override string NoUpdate_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") "; + + protected override string NoUpdate_Multiple_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual UNION ALL SELECT :p4 AS \"ID\", :p5 AS \"Name\", :p6 AS \"Status\", :p7 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") "; + + protected override string NoUpdate_WithNullable_Sql => + "MERGE INTO \"TestEntityWithNullableKey\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"ID1\", :p2 AS \"ID2\", :p3 AS \"Name\", :p4 AS \"Status\", :p5 AS \"Total\" FROM dual) s ON (t.\"ID1\" = s.\"ID1\" AND t.\"ID2\" = s.\"ID2\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"ID1\", \"ID2\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"ID1\", s.\"ID2\", s.\"Name\", s.\"Status\", s.\"Total\") "; + + protected override string Update_Constant_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p4"; + + protected override string Update_Constant_Multiple_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual UNION ALL SELECT :p4 AS \"ID\", :p5 AS \"Name\", :p6 AS \"Status\", :p7 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p8"; + + protected override string Update_Source_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = s.\"Name\""; + + protected override string Update_BinaryAdd_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Total\" = ( t.\"Total\" + :p4 )"; + + protected override string Update_Coalesce_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Status\" = ( COALESCE(t.\"Status\", :p4) )"; + + protected override string Update_BinaryAddMultiply_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Total\" = ( ( t.\"Total\" + :p4 ) * s.\"Total\" )"; + + protected override string Update_BinaryAddMultiplyGroup_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Total\" = ( t.\"Total\" + ( :p4 * s.\"Total\" ) )"; + + protected override string Update_Condition_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p4 WHERE t.\"Total\" > :p5 "; + + protected override string Update_Condition_UpdateConditionColumn_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p4, t.\"Total\" = ( t.\"Total\" + :p5 ) WHERE t.\"Total\" > :p6 "; + + protected override string Update_Condition_AndCondition_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p4 WHERE ( t.\"Total\" > :p5 ) AND ( t.\"Status\" != s.\"Status\" ) "; + + protected override string Update_Condition_NullCheck_Sql => + "MERGE INTO \"TestEntity\" t USING ( SELECT :p0 AS \"ID\", :p1 AS \"Name\", :p2 AS \"Status\", :p3 AS \"Total\" FROM dual) s ON (t.\"ID\" = s.\"ID\") WHEN NOT MATCHED THEN INSERT (\"ID\", \"Name\", \"Status\", \"Total\") VALUES (s.\"ID\", s.\"Name\", s.\"Status\", s.\"Total\") WHEN MATCHED THEN UPDATE SET t.\"Name\" = :p4 WHERE t.\"Status\" IS NOT NULL "; + } +} From 855973366e1b47e58c13c14c3bbe680eab8699f1 Mon Sep 17 00:00:00 2001 From: Artiom Chilaru Date: Thu, 21 Nov 2024 21:54:02 +0000 Subject: [PATCH 24/26] Formatting changes --- .../Runners/OracleUpsertCommandRunner.cs | 246 ++++++------------ .../Base/TestDbContext.cs | 12 +- .../DbTestsBase.cs | 51 ++-- .../DbTests_Oracle.cs | 3 +- .../DbTests_Postgres.cs | 2 +- ...ameworkCore.Upsert.IntegrationTests.csproj | 14 +- 6 files changed, 110 insertions(+), 218 deletions(-) diff --git a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs index 8258a6e..788a707 100644 --- a/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs +++ b/src/FlexLabs.EntityFrameworkCore.Upsert/Runners/OracleUpsertCommandRunner.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -6,188 +6,100 @@ using System.Text; using FlexLabs.EntityFrameworkCore.Upsert.Internal; -namespace FlexLabs.EntityFrameworkCore.Upsert.Runners; - -/// -/// Upsert command runner for the Oracle.EntityFrameworkCore provider -/// -public class OracleUpsertCommandRunner : RelationalUpsertCommandRunner +namespace FlexLabs.EntityFrameworkCore.Upsert.Runners { - /// - public override bool Supports(string providerName) => providerName == "Oracle.EntityFrameworkCore"; - - /// - public override string GenerateCommand(string tableName, - ICollection> - entities, ICollection<(string ColumnName, bool IsNullable)> joinColumns, - ICollection<(string ColumnName, IKnownValue Value)>? updateExpressions, - KnownExpression? updateCondition) + /// + /// Upsert command runner for the Oracle.EntityFrameworkCore provider + /// + public class OracleUpsertCommandRunner : RelationalUpsertCommandRunner { - ArgumentNullException.ThrowIfNull(entities); - var result = new StringBuilder(); - - result.Append($"MERGE INTO {tableName} t USING ("); - foreach (var item in entities.Select((e, ind) => new {e, ind})) - { - result.Append(" SELECT "); - result.Append(string.Join(", ", item.e.Select(ec => string.Join(" AS ", ExpandValue(ec.Value), - EscapeName(ec.ColumnName))))); - result.Append(" FROM dual"); - if (entities.Count > 1 && item.ind != entities.Count - 1) - { - result.Append(" UNION ALL "); - } - } - result.Append(") s ON ("); - result.Append(string.Join(" AND ", - joinColumns.Select(j => $"t.{EscapeName(j.ColumnName)} = s.{EscapeName(j.ColumnName)}"))); - result.Append(") "); - result.Append(" WHEN NOT MATCHED THEN INSERT ("); - result.Append(string.Join(", ", - entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); - result.Append(") VALUES ("); - result.Append(string.Join(", ", - entities.First().Where(e => e.AllowInserts).Select(e => $"s.{EscapeName(e.ColumnName)}"))); - result.Append(") "); - if (updateExpressions is not null) + /// + public override bool Supports(string providerName) => providerName == "Oracle.EntityFrameworkCore"; + /// + protected override string EscapeName([NotNull] string name) => $"\"{name}\""; + /// + protected override string? SourcePrefix => "s."; + /// + protected override string? TargetPrefix => "t."; + /// + protected override string Parameter(int index) => $":p{index}"; + /// + protected override int? MaxQueryParams => 1000; + + /// + public override string GenerateCommand( + string tableName, + ICollection> entities, + ICollection<(string ColumnName, bool IsNullable)> joinColumns, + ICollection<(string ColumnName, IKnownValue Value)>? updateExpressions, + KnownExpression? updateCondition) { - result.Append("WHEN MATCHED "); + ArgumentNullException.ThrowIfNull(entities); + var result = new StringBuilder(); - result.Append("THEN UPDATE SET "); - result.Append(string.Join(", ", - updateExpressions.Select(e => $"t.{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); - if (updateCondition is not null) + result.Append($"MERGE INTO {tableName} t USING ("); + foreach (var item in entities.Select((e, ind) => new {e, ind})) { - result.Append($" WHERE {ExpandExpression(updateCondition)} "); - } - } - - return result.ToString(); - } - - /// - protected override string ExpandExpression(KnownExpression expression, - Func? expandLeftColumn = null) - { - ArgumentNullException.ThrowIfNull(expression); - - switch (expression.ExpressionType) - { - case ExpressionType.Add: - case ExpressionType.Divide: - case ExpressionType.Multiply: - case ExpressionType.Subtract: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - { - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - var op = GetSimpleOperator(expression.ExpressionType); - return $"{left} {op} {right}"; + result.Append(" SELECT "); + result.Append(string.Join(", ", item.e.Select(ec => string.Join(" AS ", ExpandValue(ec.Value), EscapeName(ec.ColumnName))))); + result.Append(" FROM dual"); + if (entities.Count > 1 && item.ind != entities.Count - 1) + { + result.Append(" UNION ALL "); + } } - case ExpressionType.Equal: - case ExpressionType.NotEqual: + result.Append(") s ON ("); + result.Append(string.Join(" AND ", joinColumns.Select(j => $"t.{EscapeName(j.ColumnName)} = s.{EscapeName(j.ColumnName)}"))); + result.Append(") "); + result.Append(" WHEN NOT MATCHED THEN INSERT ("); + result.Append(string.Join(", ", entities.First().Where(e => e.AllowInserts).Select(e => EscapeName(e.ColumnName)))); + result.Append(") VALUES ("); + result.Append(string.Join(", ", entities.First().Where(e => e.AllowInserts).Select(e => $"s.{EscapeName(e.ColumnName)}"))); + result.Append(") "); + if (updateExpressions is not null) { - var value1Null = expression.Value1 is ConstantValue constant1 && constant1.Value == null; - var value2Null = expression.Value2 is ConstantValue constant2 && constant2.Value == null; - if (value1Null || value2Null) + result.Append("WHEN MATCHED "); + + result.Append("THEN UPDATE SET "); + result.Append(string.Join(", ", updateExpressions.Select(e => $"t.{EscapeName(e.ColumnName)} = {ExpandValue(e.Value)}"))); + if (updateCondition is not null) { - return IsNullExpression(value2Null ? expression.Value1! : expression.Value2!, - expression.ExpressionType == ExpressionType.NotEqual); + result.Append($" WHERE {ExpandExpression(updateCondition)} "); } - - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - var op = GetSimpleOperator(expression.ExpressionType); - return $"{left} {op} {right}"; } - case ExpressionType.Coalesce: - { - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - return $"COALESCE({left}, {right})"; - } + return result.ToString(); + } - case ExpressionType.Conditional: - { - var ifTrue = ExpandValue(expression.Value1, expandLeftColumn); - var ifFalse = ExpandValue(expression.Value2!, expandLeftColumn); - var test = ExpandValue(expression.Value3!, expandLeftColumn); - return $"CASE WHEN {test} THEN {ifTrue} ELSE {ifFalse} END"; - } + /// + protected override string ExpandExpression(KnownExpression expression, Func? expandLeftColumn = null) + { + ArgumentNullException.ThrowIfNull(expression); - case ExpressionType.MemberAccess: - case ExpressionType.Constant: + switch (expression.ExpressionType) { - return ExpandValue(expression.Value1, expandLeftColumn); - } + case ExpressionType.And: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"BITAND({left}, {right})"; + } + case ExpressionType.Or: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"BITOR({left}, {right})"; + } + case ExpressionType.Modulo: + { + var left = ExpandValue(expression.Value1, expandLeftColumn); + var right = ExpandValue(expression.Value2!, expandLeftColumn); + return $"MOD({left}, {right})"; + } - case ExpressionType.AndAlso: - case ExpressionType.OrElse: - { - var exp = expression.ExpressionType == ExpressionType.AndAlso ? "AND" : "OR"; - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - return $"{left} {exp} {right}"; - } - case ExpressionType.And: - { - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - return $"BITAND({left}, {right})"; - } - case ExpressionType.Or: - { - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - return $"BITOR({left}, {right})"; + default: + return base.ExpandExpression(expression, expandLeftColumn); } - case ExpressionType.Modulo: - { - var left = ExpandValue(expression.Value1, expandLeftColumn); - var right = ExpandValue(expression.Value2!, expandLeftColumn); - return $"MOD({left}, {right})"; - } - - default: - throw new NotSupportedException("Don't know how to process operation: " + expression.ExpressionType); } } - - /// - protected override string GetSimpleOperator(ExpressionType expressionType) - { - return expressionType switch - { - ExpressionType.Add => "+", - ExpressionType.Divide => "/", - ExpressionType.Multiply => "*", - ExpressionType.Subtract => "-", - ExpressionType.LessThan => "<", - ExpressionType.LessThanOrEqual => "<=", - ExpressionType.GreaterThan => ">", - ExpressionType.GreaterThanOrEqual => ">=", - ExpressionType.Equal => "=", - ExpressionType.NotEqual => "!=", - _ => throw new InvalidOperationException($"{expressionType} is not a simple arithmetic operation"), - }; - } - - /// - protected override string EscapeName([NotNull] string name) => $"\"{name}\""; - - /// - protected override string? SourcePrefix => "s."; - - /// - protected override string? TargetPrefix => "t."; - - /// - protected override string Parameter(int index) => $":p{index}"; - - /// - protected override int? MaxQueryParams => 1000; } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs index bd2afc2..233e7ae 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/Base/TestDbContext.cs @@ -50,15 +50,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().Ignore(j => j.Child); } - if (dbProvider.Name != - "Pomelo.EntityFrameworkCore.MySql") // Can't have a default value on TEXT columns in MySql - modelBuilder.Entity().Property(e => e.Text).HasDefaultValue("B"); - if (dbProvider.Name is "Pomelo.EntityFrameworkCore.MySql" - or "Oracle.EntityFrameworkCore") // Can't have table schemas in MySql and Oracle + if (dbProvider.Name != "Pomelo.EntityFrameworkCore.MySql") // Can't have a default value on TEXT columns in MySql { - modelBuilder.Entity().Metadata.SetSchema(null); + modelBuilder.Entity().Property(e => e.Text).HasDefaultValue("B"); } - else + + if (dbProvider.Name != "Pomelo.EntityFrameworkCore.MySql" && + dbProvider.Name != "Oracle.EntityFrameworkCore") // Can't have table schemas in MySql and Oracle { modelBuilder.Entity().Metadata.SetSchema("testsch"); } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs index ffb6c0f..ae8a4a1 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTestsBase.cs @@ -442,8 +442,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -473,8 +472,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_FromVar() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, - expectedVisits: _dbVisit.Visits + increment)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + increment)); } [Fact] @@ -503,8 +501,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_FromField() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, - expectedVisits: _dbVisit.Visits + _increment)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + _increment)); } [Fact] @@ -533,8 +530,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_FromSource_ValueAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -563,8 +559,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_FromSource_ColumnAdd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, - expectedVisits: _dbVisit.Visits + newVisit.Visits)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + newVisit.Visits)); } [Fact] @@ -593,8 +588,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueAdd_Reversed() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -623,8 +617,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueSubtract() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits - 2)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits - 2)); } [Fact] @@ -653,8 +646,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueMultiply() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits * 3)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits * 3)); } [Fact] @@ -683,8 +675,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueBitwiseOr() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits | 3)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits | 3)); } [Fact] @@ -713,8 +704,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueBitwiseAnd() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits & 3)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits & 3)); } [Fact] @@ -743,8 +733,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueDivide() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits / 4)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits / 4)); } [Fact] @@ -773,8 +762,7 @@ public void Upsert_PageVisit_Update_On_WhenMatched_ValueModulo() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits % 4)); + visit => visit.Should().MatchModel(newVisit, compareFirstVisit: false, expectedVisits: _dbVisit.Visits % 4)); } [Fact] @@ -811,8 +799,7 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched() dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( visit => visit.Should().MatchModel(_dbVisitOld), - visit => visit.Should() - .MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1), + visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1), visit => visit.Should().MatchModel(newVisit2)); } @@ -888,10 +875,8 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched_MultipleUpdate() .Run(); dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( - visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, - expectedVisits: _dbVisitOld.Visits + 1), - visit => visit.Should() - .MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisitOld.Visits + 1), + visit => visit.Should().MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] @@ -927,10 +912,8 @@ public void UpsertRange_PageVisit_Update_On_WhenMatched_MultipleUpdate_FromSourc .Run(); dbContext.PageVisits.OrderBy(c => c.Date).Should().SatisfyRespectively( - visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, - expectedVisits: _dbVisitOld.Visits + 1), - visit => visit.Should() - .MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); + visit => visit.Should().MatchModel(newVisit1, compareFirstVisit: false, expectedVisits: _dbVisitOld.Visits + 1), + visit => visit.Should().MatchModel(newVisit2, compareFirstVisit: false, expectedVisits: _dbVisit.Visits + 1)); } [Fact] diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs index 55e7da2..7365341 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs @@ -15,8 +15,7 @@ public sealed class DatabaseInitializer : DatabaseInitializerFixture public override DbDriver DbDriver => DbDriver.Oracle; protected override IContainer BuildContainer() - => new OracleBuilder() - .Build(); + => new OracleBuilder().Build(); protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs index ee2f852..8db44b9 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs @@ -25,7 +25,7 @@ protected override void ConfigureContextOptions(DbContextOptionsBuilder - - - - - + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 6c9c03cd334ebbcc6b0d43e0ae4cff60143ebf06 Mon Sep 17 00:00:00 2001 From: Artiom Chilaru Date: Fri, 22 Nov 2024 21:33:14 +0000 Subject: [PATCH 25/26] Refactoring the db initialiser fixture --- ...ContainerisedDatabaseInitializerFixture.cs | 39 +++++++++++++++++++ .../DatabaseInitializerFixture.cs | 26 +------------ .../DbTests_MySql.cs | 9 +++-- .../DbTests_Oracle.cs | 9 +++-- .../DbTests_Postgres.cs | 7 ++-- .../DbTests_SqlServer.cs | 9 ++--- 6 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/ContainerisedDatabaseInitializerFixture.cs diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/ContainerisedDatabaseInitializerFixture.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/ContainerisedDatabaseInitializerFixture.cs new file mode 100644 index 0000000..cb90ac5 --- /dev/null +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/ContainerisedDatabaseInitializerFixture.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using DotNet.Testcontainers.Containers; + +namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests +{ + public abstract class ContainerisedDatabaseInitializerFixture : DatabaseInitializerFixture + where TContainer : IContainer, IDatabaseContainer + { + public TContainer TestContainer { get; } + + public ContainerisedDatabaseInitializerFixture() + { + if (!BuildEnvironment.UseLocalService) + { + TestContainer = BuildContainer(); + } + } + + protected abstract TContainer BuildContainer(); + + public override async Task InitializeAsync() + { + if (TestContainer is not null) + { + await TestContainer.StartAsync(); + } + + await base.InitializeAsync(); + } + + public override async Task DisposeAsync() + { + if (TestContainer is not null) + { + await TestContainer.StopAsync(); + } + } + } +} diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DatabaseInitializerFixture.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DatabaseInitializerFixture.cs index 30e5b6f..5124851 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DatabaseInitializerFixture.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DatabaseInitializerFixture.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using DotNet.Testcontainers.Containers; using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; using Microsoft.EntityFrameworkCore; using Xunit; @@ -8,29 +7,14 @@ namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests { public abstract class DatabaseInitializerFixture : IAsyncLifetime { - public IContainer TestContainer { get; } public DbContextOptions DataContextOptions { get; private set; } - public DatabaseInitializerFixture() - { - if (!BuildEnvironment.UseLocalService) - { - TestContainer = BuildContainer(); - } - } - public abstract DbDriver DbDriver { get; } - protected virtual IContainer BuildContainer() => null; protected abstract void ConfigureContextOptions(DbContextOptionsBuilder builder); - public async Task InitializeAsync() + public virtual async Task InitializeAsync() { - if (TestContainer is not null) - { - await TestContainer.StartAsync(); - } - var builder = new DbContextOptionsBuilder(); ConfigureContextOptions(builder); DataContextOptions = builder.Options; @@ -39,12 +23,6 @@ public async Task InitializeAsync() await context.Database.EnsureCreatedAsync(); } - public async Task DisposeAsync() - { - if (TestContainer is not null) - { - await TestContainer.StopAsync(); - } - } + public virtual Task DisposeAsync() => Task.CompletedTask; } } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_MySql.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_MySql.cs index 4e86104..12da175 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_MySql.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_MySql.cs @@ -1,4 +1,4 @@ -using DotNet.Testcontainers.Containers; +using System; using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; using FlexLabs.EntityFrameworkCore.Upsert.Tests.EF; using Microsoft.EntityFrameworkCore; @@ -10,16 +10,17 @@ namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests #if !NOMYSQL public class DbTests_MySql : DbTestsBase, IClassFixture { - public sealed class DatabaseInitializer : DatabaseInitializerFixture + public sealed class DatabaseInitializer : ContainerisedDatabaseInitializerFixture { public override DbDriver DbDriver => DbDriver.MySQL; - protected override IContainer BuildContainer() + protected override MySqlContainer BuildContainer() => new MySqlBuilder().Build(); protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { - var connectionString = (TestContainer as IDatabaseContainer).GetConnectionString(); + var connectionString = TestContainer?.GetConnectionString() + ?? throw new InvalidOperationException("Connection string was not initialised"); builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); } } diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs index 7365341..225af92 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Oracle.cs @@ -1,4 +1,4 @@ -using DotNet.Testcontainers.Containers; +using System; using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; using FlexLabs.EntityFrameworkCore.Upsert.Tests.EF; using Microsoft.EntityFrameworkCore; @@ -10,16 +10,17 @@ namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests #if !NOORACLE public class DbTests_Oracle : DbTestsBase, IClassFixture { - public sealed class DatabaseInitializer : DatabaseInitializerFixture + public sealed class DatabaseInitializer : ContainerisedDatabaseInitializerFixture { public override DbDriver DbDriver => DbDriver.Oracle; - protected override IContainer BuildContainer() + protected override OracleContainer BuildContainer() => new OracleBuilder().Build(); protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { - var connectionString = (TestContainer as IDatabaseContainer)?.GetConnectionString(); + var connectionString = TestContainer?.GetConnectionString() + ?? throw new InvalidOperationException("Connection string was not initialised"); builder .UseOracle(connectionString) .UseUpperSnakeCaseNamingConvention(); diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs index 8db44b9..7a7270b 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_Postgres.cs @@ -1,5 +1,4 @@ using System.Linq; -using DotNet.Testcontainers.Containers; using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; using FlexLabs.EntityFrameworkCore.Upsert.Tests.EF; using FluentAssertions; @@ -13,16 +12,16 @@ namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests #if !NOPOSTGRES public class DbTests_Postgres : DbTestsBase, IClassFixture { - public sealed class DatabaseInitializer : DatabaseInitializerFixture + public sealed class DatabaseInitializer : ContainerisedDatabaseInitializerFixture { public override DbDriver DbDriver => DbDriver.Postgres; - protected override IContainer BuildContainer() + protected override PostgreSqlContainer BuildContainer() => new PostgreSqlBuilder().Build(); protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { - var connectionString = (TestContainer as IDatabaseContainer)?.GetConnectionString() + var connectionString = TestContainer?.GetConnectionString() ?? (BuildEnvironment.IsGitHub ? "Server=localhost;Port=5432;Database=testuser;Username=postgres;Password=root" : null); builder.UseNpgsql(new NpgsqlDataSourceBuilder(connectionString) .EnableDynamicJson() diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_SqlServer.cs b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_SqlServer.cs index 3f52b45..95b3c3f 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_SqlServer.cs +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/DbTests_SqlServer.cs @@ -1,5 +1,4 @@ -using DotNet.Testcontainers.Containers; -using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; +using FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.Base; using FlexLabs.EntityFrameworkCore.Upsert.Tests.EF; using Microsoft.EntityFrameworkCore; using Testcontainers.MsSql; @@ -10,16 +9,16 @@ namespace FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests #if !NOMSSQL public class DbTests_SqlServer : DbTestsBase, IClassFixture { - public sealed class DatabaseInitializer : DatabaseInitializerFixture + public sealed class DatabaseInitializer : ContainerisedDatabaseInitializerFixture { public override DbDriver DbDriver => DbDriver.MSSQL; - protected override IContainer BuildContainer() + protected override MsSqlContainer BuildContainer() => new MsSqlBuilder().Build(); protected override void ConfigureContextOptions(DbContextOptionsBuilder builder) { - var connectionString = (TestContainer as IDatabaseContainer)?.GetConnectionString() + var connectionString = TestContainer?.GetConnectionString() ?? "Server=(localdb)\\MSSqlLocalDB;Integrated Security=SSPI;Initial Catalog=FlexLabsUpsertTests;"; builder.UseSqlServer(connectionString); } From 0a44b4ae738a518cb2db87f6b9b4ea6097378be4 Mon Sep 17 00:00:00 2001 From: Artiom Chilaru Date: Fri, 22 Nov 2024 22:02:57 +0000 Subject: [PATCH 26/26] That's why it didn't run the containers! --- .../FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj index 0d419f9..44a0f45 100644 --- a/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj +++ b/test/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests/FlexLabs.EntityFrameworkCore.Upsert.IntegrationTests.csproj @@ -8,7 +8,7 @@ - $(DefineConstants);NOMSSQL;NOMYSQL;POSTGRES_ONLY + $(DefineConstants);NOMSSQL;NOMYSQL;NOORACLE;POSTGRES_ONLY