From 40431c166cb90201dcac9712c0fb94df99c604d6 Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 19:05:11 +0100 Subject: [PATCH 1/6] feat: EF 8.0 release --- BlazarTech.QueryableValues.sln | 20 ++++++++++++++ Version.xml | 1 + .../QueryableValues.SqlServer.EFCore8.csproj | 15 +++++++++++ src/SharedProjectProperties.xml | 1 + ...yableValues.SqlServer.Tests.EFCore8.csproj | 27 +++++++++++++++++++ .../Integration/MyDbContext.cs | 2 ++ 6 files changed, 66 insertions(+) create mode 100644 src/QueryableValues.SqlServer.EFCore8/QueryableValues.SqlServer.EFCore8.csproj create mode 100644 tests/QueryableValues.SqlServer.Tests.EFCore8/QueryableValues.SqlServer.Tests.EFCore8.csproj diff --git a/BlazarTech.QueryableValues.sln b/BlazarTech.QueryableValues.sln index eb19b89..f328f6b 100644 --- a/BlazarTech.QueryableValues.sln +++ b/BlazarTech.QueryableValues.sln @@ -32,6 +32,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.Benchmarks", "benchmarks\QueryableValues.SqlServer.Benchmarks\QueryableValues.SqlServer.Benchmarks.csproj", "{99FE31A0-BC7E-412C-82E2-DA19E8B68613}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.EFCore8", "src\QueryableValues.SqlServer.EFCore8\QueryableValues.SqlServer.EFCore8.csproj", "{03A17C43-5E66-4A8F-B650-7F611E73BD0B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryableValues.SqlServer.Tests.EFCore8", "tests\QueryableValues.SqlServer.Tests.EFCore8\QueryableValues.SqlServer.Tests.EFCore8.csproj", "{9387545B-CABC-4C63-A163-4F64C65A370F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -118,6 +122,22 @@ Global {99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.ActiveCfg = Release|Any CPU {99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test_All|Any CPU.Build.0 = Release|Any CPU {99FE31A0-BC7E-412C-82E2-DA19E8B68613}.Test|Any CPU.ActiveCfg = Release|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Release|Any CPU.Build.0 = Release|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Test_All|Any CPU.ActiveCfg = Test|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Test_All|Any CPU.Build.0 = Test|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Test|Any CPU.ActiveCfg = Test|Any CPU + {03A17C43-5E66-4A8F-B650-7F611E73BD0B}.Test|Any CPU.Build.0 = Test|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Release|Any CPU.Build.0 = Release|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Test_All|Any CPU.ActiveCfg = Test_All|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Test_All|Any CPU.Build.0 = Test_All|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Test|Any CPU.ActiveCfg = Test|Any CPU + {9387545B-CABC-4C63-A163-4F64C65A370F}.Test|Any CPU.Build.0 = Test|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Version.xml b/Version.xml index 583758b..82db01e 100644 --- a/Version.xml +++ b/Version.xml @@ -4,5 +4,6 @@ 5.9.0 6.9.0 7.4.0 + 8.0.0 \ No newline at end of file diff --git a/src/QueryableValues.SqlServer.EFCore8/QueryableValues.SqlServer.EFCore8.csproj b/src/QueryableValues.SqlServer.EFCore8/QueryableValues.SqlServer.EFCore8.csproj new file mode 100644 index 0000000..1a11106 --- /dev/null +++ b/src/QueryableValues.SqlServer.EFCore8/QueryableValues.SqlServer.EFCore8.csproj @@ -0,0 +1,15 @@ + + + + + + $(VersionEFCore8) + net8.0 + Debug;Release;Test + $(DefineConstants);EFCORE;EFCORE8 + + + + + + diff --git a/src/SharedProjectProperties.xml b/src/SharedProjectProperties.xml index b539465..93b357b 100644 --- a/src/SharedProjectProperties.xml +++ b/src/SharedProjectProperties.xml @@ -60,5 +60,6 @@ + \ No newline at end of file diff --git a/tests/QueryableValues.SqlServer.Tests.EFCore8/QueryableValues.SqlServer.Tests.EFCore8.csproj b/tests/QueryableValues.SqlServer.Tests.EFCore8/QueryableValues.SqlServer.Tests.EFCore8.csproj new file mode 100644 index 0000000..78f3d3f --- /dev/null +++ b/tests/QueryableValues.SqlServer.Tests.EFCore8/QueryableValues.SqlServer.Tests.EFCore8.csproj @@ -0,0 +1,27 @@ + + + + + net8.0 + BlazarTech.QueryableValues.SqlServer.Tests.EFCore8 + $(DefineConstants);TESTS;EFCORE8 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + \ No newline at end of file diff --git a/tests/QueryableValues.SqlServer.Tests/Integration/MyDbContext.cs b/tests/QueryableValues.SqlServer.Tests/Integration/MyDbContext.cs index e609287..13927fa 100644 --- a/tests/QueryableValues.SqlServer.Tests/Integration/MyDbContext.cs +++ b/tests/QueryableValues.SqlServer.Tests/Integration/MyDbContext.cs @@ -14,6 +14,8 @@ internal static class DatabaseName public const string Name = "QueryableValuesTestsEFCore6"; #elif EFCORE7 public const string Name = "QueryableValuesTestsEFCore7"; +#elif EFCORE8 + public const string Name = "QueryableValuesTestsEFCore8"; #endif } From 7cbbe27b41ab38942872128f8479b39bf83e1837 Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 19:11:11 +0100 Subject: [PATCH 2/6] refactor: simplify loops --- .../SqlServer/JsonQueryableFactory.cs | 7 +++---- .../SqlServer/XmlQueryableFactory.cs | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs b/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs index cd705b8..e1bcd85 100644 --- a/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs +++ b/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs @@ -38,17 +38,16 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti sb.Append(" [").Append(QueryableValuesEntity.IndexPropertyName).Append(']'); - for (var i = 0; i < mappings.Count; i++) + foreach (var mapping in mappings) { - sb.Append(", [").Append(mappings[i].Target.Name).Append(']'); + sb.Append(", [").Append(mapping.Target.Name).Append(']'); } sb.AppendLine(); sb.Append("FROM OPENJSON({0}) WITH ([").Append(QueryableValuesEntity.IndexPropertyName).Append("] int"); - for (var i = 0; i < mappings.Count; i++) + foreach (var mapping in mappings) { - var mapping = mappings[i]; var propertyOptions = entityOptions.GetPropertyOptions(mapping.Source); sb.Append(", "); diff --git a/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs b/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs index 77de03e..870f3ba 100644 --- a/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs +++ b/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs @@ -45,9 +45,8 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti .Append(QueryableValuesEntity.IndexPropertyName) .Append(']'); - for (int i = 0; i < mappings.Count; i++) + foreach (var mapping in mappings) { - var mapping = mappings[i]; var propertyOptions = entityOptions.GetPropertyOptions(mapping.Source); sb.Append(',').AppendLine(); From 23c10b0a013996408f1fee001901571782d3f070 Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 20:53:28 +0100 Subject: [PATCH 3/6] fix: EF 7+ bug around projection composition --- .../QueryableValuesEntity.cs | 42 +++++++++++++++++++ .../SqlServer/JsonQueryableFactory.cs | 5 +++ .../SqlServer/XmlQueryableFactory.cs | 26 +++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/QueryableValues.SqlServer/QueryableValuesEntity.cs b/src/QueryableValues.SqlServer/QueryableValuesEntity.cs index 6bb0401..e2fed56 100644 --- a/src/QueryableValues.SqlServer/QueryableValuesEntity.cs +++ b/src/QueryableValues.SqlServer/QueryableValuesEntity.cs @@ -1,11 +1,20 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace BlazarTech.QueryableValues { internal class QueryableValuesEntity { + private static readonly IReadOnlyList DataPropertyNames; + public const string IndexPropertyName = nameof(X); + static QueryableValuesEntity() + { + DataPropertyNames = GetDataPropertyNames(); + } + public int X { get; set; } public int? I { get; set; } @@ -150,5 +159,38 @@ internal class QueryableValuesEntity public char? C7 { get; set; } public char? C8 { get; set; } public char? C9 { get; set; } + + private static List GetDataPropertyNames() + { + var properties = typeof(QueryableValuesEntity).GetProperties(); + var result = new List(properties.Length); + + foreach (var property in properties) + { + if (property.Name is IndexPropertyName) + { + continue; + } + + result.Add(property.Name); + } + + return result; + } + + internal static IEnumerable GetUnmappedPropertyNames(IReadOnlyList mappings) + { + var targetProperties = new HashSet(mappings.Select(i => i.Target.Name)); + + foreach (var propertyName in DataPropertyNames) + { + if (targetProperties.Contains(propertyName)) + { + continue; + } + + yield return propertyName; + } + } } } \ No newline at end of file diff --git a/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs b/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs index e1bcd85..1e4ae05 100644 --- a/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs +++ b/src/QueryableValues.SqlServer/SqlServer/JsonQueryableFactory.cs @@ -43,6 +43,11 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti sb.Append(", [").Append(mapping.Target.Name).Append(']'); } + foreach (var unmappedPropertyName in QueryableValuesEntity.GetUnmappedPropertyNames(mappings)) + { + sb.Append(", NULL[").Append(unmappedPropertyName).Append(']'); + } + sb.AppendLine(); sb.Append("FROM OPENJSON({0}) WITH ([").Append(QueryableValuesEntity.IndexPropertyName).Append("] int"); diff --git a/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs b/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs index 870f3ba..2b3f008 100644 --- a/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs +++ b/src/QueryableValues.SqlServer/SqlServer/XmlQueryableFactory.cs @@ -41,7 +41,7 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti sb .Append("\tI.value('@") .Append(QueryableValuesEntity.IndexPropertyName) - .Append(" cast as xs:integer?', 'int') AS [") + .Append(" cast as xs:integer?', 'int') [") .Append(QueryableValuesEntity.IndexPropertyName) .Append(']'); @@ -117,9 +117,11 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti throw new NotImplementedException(mapping.TypeName.ToString()); } - sb.Append(") AS [").Append(targetName).Append(']'); + sb.Append(") [").Append(targetName).Append(']'); } + AppendUnmappedProperties(sb, mappings); + sb.AppendLine(); sb.Append("FROM {0}.nodes('/R[1]/V') N(I)").AppendLine(); sb.Append("ORDER BY [").Append(QueryableValuesEntity.IndexPropertyName).Append(']'); @@ -130,6 +132,26 @@ protected override string GetSqlForComplexTypes(IEntityOptionsBuilder entityOpti { StringBuilderPool.Return(sb); } + + static void AppendUnmappedProperties(System.Text.StringBuilder sb, IReadOnlyList< EntityPropertyMapping> mappings) + { + var hasUnmappedProperty = false; + + foreach (var unmappedPropertyName in QueryableValuesEntity.GetUnmappedPropertyNames(mappings)) + { + if (hasUnmappedProperty) + { + sb.Append(','); + } + else + { + hasUnmappedProperty = true; + sb.Append(',').AppendLine().Append('\t'); + } + + sb.Append("NULL[").Append(unmappedPropertyName).Append(']'); + } + } } } } From b32f2a73c9a83121b35d5eaa889cf46132f8827f Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 20:54:59 +0100 Subject: [PATCH 4/6] test: join with projection --- .../Integration/ComplexTypeTests.cs | 31 +++++++++++++++++-- .../Integration/SimpleTypeTests.cs | 21 +++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/tests/QueryableValues.SqlServer.Tests/Integration/ComplexTypeTests.cs b/tests/QueryableValues.SqlServer.Tests/Integration/ComplexTypeTests.cs index 8d92cfe..df6f7b0 100644 --- a/tests/QueryableValues.SqlServer.Tests/Integration/ComplexTypeTests.cs +++ b/tests/QueryableValues.SqlServer.Tests/Integration/ComplexTypeTests.cs @@ -897,7 +897,7 @@ orderby td.Id Assert.Equal(2, actual[0].ChildEntity.Count); Assert.Equal(3, actual[1].Id); - Assert.Equal(1, actual[1].ChildEntity.Count); + Assert.Single(actual[1].ChildEntity); } [Fact] @@ -920,7 +920,7 @@ orderby td.Id Assert.Single(actual); Assert.Equal(3, actual[0].Id); - Assert.Equal(1, actual[0].ChildEntity.Count); + Assert.Single(actual[0].ChildEntity); } [Fact] @@ -945,6 +945,33 @@ orderby td.Id Assert.Equal(1, actual[0].Id); Assert.Equal(2, actual[0].ChildEntity.Count); } + + [Fact] + public async Task JoinWithProjection() + { + var values = new[] + { + new { Id = 1, Value = Guid.Empty }, + new { Id = 3, Value = Guid.Parse("f6379213-750f-42df-91b9-73756f28c4b6") } + }; + + var expected = new[] { 1, 3 }; + + var query = + from td in _db.TestData + join v in _db.AsQueryableValues(values) on new { td.Id, Value = td.GuidValue } equals new { v.Id, v.Value } + select td.Id; + + var query2 = + from td in _db.TestData + join v in query on td.Id equals v + orderby td.Id + select td.Id; + + var actual = await query2.ToListAsync(); + + Assert.Equal(expected, actual); + } } [Collection("DbContext")] diff --git a/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs b/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs index 46caaa4..1923bd2 100644 --- a/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs +++ b/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs @@ -362,6 +362,27 @@ public async Task MustMatchSequenceOfGuid() Assert.Equal(expected, actual); } + [Fact] + public async Task JoinWithProjection() + { + var values = new[] { 1, 3 }; + + var query = + from td in _db.TestData + join v in _db.AsQueryableValues(values) on td.Id equals v + select td.Id; + + var query2 = + from td in _db.TestData + join v in query on td.Id equals v + orderby td.Id + select td.Id; + + var actual = await query2.ToListAsync(); + + Assert.Equal(values, actual); + } + enum ByteEnum : byte { None, From 1af0964ab2747c5ece93cc55302036b2abdf2fc6 Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 21:00:14 +0100 Subject: [PATCH 5/6] chore: version bump --- Version.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Version.xml b/Version.xml index 82db01e..cddd0fa 100644 --- a/Version.xml +++ b/Version.xml @@ -1,9 +1,9 @@  - 3.9.0 - 5.9.0 - 6.9.0 - 7.4.0 + 3.9.1 + 5.9.1 + 6.9.1 + 7.4.1 8.0.0 \ No newline at end of file From f94f2077c64132414bd78e4b43e987b05d05ade3 Mon Sep 17 00:00:00 2001 From: yv989c Date: Sat, 25 Nov 2023 21:20:29 +0100 Subject: [PATCH 6/6] test: target net8.0 across the board --- .../QueryableValues.SqlServer.Tests.EFCore3.csproj | 2 +- .../QueryableValues.SqlServer.Tests.EFCore5.csproj | 2 +- .../QueryableValues.SqlServer.Tests.EFCore6.csproj | 2 +- .../QueryableValues.SqlServer.Tests.EFCore7.csproj | 2 +- .../Integration/SimpleTypeTests.cs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/QueryableValues.SqlServer.Tests.EFCore3/QueryableValues.SqlServer.Tests.EFCore3.csproj b/tests/QueryableValues.SqlServer.Tests.EFCore3/QueryableValues.SqlServer.Tests.EFCore3.csproj index 436db97..8dc63ed 100644 --- a/tests/QueryableValues.SqlServer.Tests.EFCore3/QueryableValues.SqlServer.Tests.EFCore3.csproj +++ b/tests/QueryableValues.SqlServer.Tests.EFCore3/QueryableValues.SqlServer.Tests.EFCore3.csproj @@ -2,7 +2,7 @@ - netcoreapp3.1;net6.0 + net8.0 BlazarTech.QueryableValues.SqlServer.Tests.EFCore3 $(DefineConstants);TESTS;EFCORE3 diff --git a/tests/QueryableValues.SqlServer.Tests.EFCore5/QueryableValues.SqlServer.Tests.EFCore5.csproj b/tests/QueryableValues.SqlServer.Tests.EFCore5/QueryableValues.SqlServer.Tests.EFCore5.csproj index 381f066..e7bd076 100644 --- a/tests/QueryableValues.SqlServer.Tests.EFCore5/QueryableValues.SqlServer.Tests.EFCore5.csproj +++ b/tests/QueryableValues.SqlServer.Tests.EFCore5/QueryableValues.SqlServer.Tests.EFCore5.csproj @@ -2,7 +2,7 @@ - netcoreapp3.1;net6.0 + net8.0 BlazarTech.QueryableValues.SqlServer.Tests.EFCore5 $(DefineConstants);TESTS;EFCORE5 diff --git a/tests/QueryableValues.SqlServer.Tests.EFCore6/QueryableValues.SqlServer.Tests.EFCore6.csproj b/tests/QueryableValues.SqlServer.Tests.EFCore6/QueryableValues.SqlServer.Tests.EFCore6.csproj index d5a05ac..571414e 100644 --- a/tests/QueryableValues.SqlServer.Tests.EFCore6/QueryableValues.SqlServer.Tests.EFCore6.csproj +++ b/tests/QueryableValues.SqlServer.Tests.EFCore6/QueryableValues.SqlServer.Tests.EFCore6.csproj @@ -2,7 +2,7 @@ - net6.0 + net8.0 BlazarTech.QueryableValues.SqlServer.Tests.EFCore6 $(DefineConstants);TESTS;EFCORE6 diff --git a/tests/QueryableValues.SqlServer.Tests.EFCore7/QueryableValues.SqlServer.Tests.EFCore7.csproj b/tests/QueryableValues.SqlServer.Tests.EFCore7/QueryableValues.SqlServer.Tests.EFCore7.csproj index a502dde..917b794 100644 --- a/tests/QueryableValues.SqlServer.Tests.EFCore7/QueryableValues.SqlServer.Tests.EFCore7.csproj +++ b/tests/QueryableValues.SqlServer.Tests.EFCore7/QueryableValues.SqlServer.Tests.EFCore7.csproj @@ -2,7 +2,7 @@ - net6.0 + net8.0 BlazarTech.QueryableValues.SqlServer.Tests.EFCore7 $(DefineConstants);TESTS;EFCORE7 diff --git a/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs b/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs index 1923bd2..feb1d95 100644 --- a/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs +++ b/tests/QueryableValues.SqlServer.Tests/Integration/SimpleTypeTests.cs @@ -957,7 +957,7 @@ orderby td.Id Assert.Equal(2, actual[0].ChildEntity.Count); Assert.Equal(3, actual[1].Id); - Assert.Equal(1, actual[1].ChildEntity.Count); + Assert.Single(actual[1].ChildEntity); } [Fact] @@ -980,7 +980,7 @@ orderby td.Id Assert.Single(actual); Assert.Equal(3, actual[0].Id); - Assert.Equal(1, actual[0].ChildEntity.Count); + Assert.Single(actual[0].ChildEntity); } [Fact]