diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 210ed6d..838af5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 9.x + dotnet-version: 10.x - name: Set Variables run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b46613..647bba8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 9.x + dotnet-version: 10.x - name: Set Variables run: | diff --git a/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj b/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj index 27cfdce..a5e4b80 100644 --- a/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj +++ b/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj @@ -1,12 +1,12 @@  - net8.0;net9.0 + net8.0;net9.0;net10.0 AutoMapper.AspNet.OData AutoMapper.AspNetCore.OData.EF6 Creates LINQ expressions from ODataQueryOptions and executes the query. false - Supporting AutoMapper v15 (EF Core only). + Supporting AutoMapper v16 (EF Core only). linq expressions odata efcore icon.png https://github.com/AutoMapper/AutoMapper.Extensions.OData @@ -62,7 +62,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj index 2566219..6f299d5 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj +++ b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj @@ -1,12 +1,12 @@  - net8.0;net9.0 + net8.0;net9.0;net10.0 AutoMapper.AspNet.OData AutoMapper.AspNetCore.OData.EFCore Creates LINQ expressions from ODataQueryOptions and executes the query. false - Supporting AutoMapper v15 (EF Core only). + Supporting AutoMapper v16 (EF Core only). linq expressions odata efcore icon.png https://github.com/AutoMapper/AutoMapper.Extensions.OData @@ -30,9 +30,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -47,6 +47,10 @@ + + + + diff --git a/AutoMapper.AspNetCore.OData.EFCore/FilterHelper.cs b/AutoMapper.AspNetCore.OData.EFCore/FilterHelper.cs index 0be9bef..c967ba1 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/FilterHelper.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/FilterHelper.cs @@ -241,10 +241,13 @@ private IExpressionPart GetIsOdFilterPart(List arguments) if (!(arguments[0] is SingleValueNode sourceNode)) throw new ArgumentException("Expected SingleValueNode for source node."); - if (!(arguments[1] is ConstantNode typeNode)) - throw new ArgumentException("Expected ConstantNode for type node."); + if (arguments[1] is ConstantNode typeNode) + return IsOf(GetCastType(typeNode)); - return IsOf(GetCastType(typeNode)); + if (arguments[1] is SingleResourceCastNode singleResourceCastNode) + return IsOf(GetCastType(singleResourceCastNode)); + + throw new ArgumentException("Expected ConstantNode or SingleResourceCastNode for type node."); IExpressionPart IsOf(Type conversionType) => new IsOfOperator(GetFilterPart(sourceNode), conversionType); @@ -255,14 +258,25 @@ private IExpressionPart GetCastResourceFilterPart(List arguments) if (!(arguments[0] is SingleValueNode sourceNode)) throw new ArgumentException("Expected SingleValueNode for source node."); - if (!(arguments[1] is ConstantNode typeNode)) - throw new ArgumentException("Expected ConstantNode for type node."); + if (arguments[1] is ConstantNode typeNode) + { + return Convert + ( + GetClrType(sourceNode.TypeReference), + GetCastType(typeNode) + ); + } - return Convert - ( - GetClrType(sourceNode.TypeReference), - GetCastType(typeNode) - ); + if (arguments[1] is SingleResourceCastNode singleResourceCastNode) + { + return Convert + ( + GetClrType(sourceNode.TypeReference), + GetCastType(singleResourceCastNode) + ); + } + + throw new ArgumentException("Expected ConstantNode or SingleResourceCastNode for type node."); IExpressionPart Convert(Type operandType, Type conversionType) { @@ -291,23 +305,36 @@ private IExpressionPart GetCastFilterPart(List arguments) if (!(arguments[0] is SingleValueNode sourceNode)) throw new ArgumentException("Expected SingleValueNode for source node."); - if (!(arguments[1] is ConstantNode typeNode)) - throw new ArgumentException("Expected ConstantNode for type node."); + if (arguments[1] is ConstantNode typeNode) + { + return Convert + ( + GetClrType(sourceNode.TypeReference), + GetCastType(typeNode), + typeNode.TypeReference + ); + } - return Convert - ( - GetClrType(sourceNode.TypeReference), - GetCastType(typeNode) - ); + if (arguments[1] is SingleResourceCastNode singleResourceCastNode) + { + return Convert + ( + GetClrType(sourceNode.TypeReference), + GetCastType(singleResourceCastNode), + singleResourceCastNode.TypeReference + ); + } - IExpressionPart Convert(Type operandType, Type conversionType) + throw new ArgumentException("Expected ConstantNode or SingleResourceCastNode for type node."); + + IExpressionPart Convert(Type operandType, Type conversionType, IEdmTypeReference edmTypeReference) { if (OperandIsNullConstant(sourceNode) || operandType == conversionType) return GetFilterPart(sourceNode); if (ShouldConvertTypes(operandType, conversionType, sourceNode)) { - if ((!typeNode.TypeReference.IsPrimitive() && !typeNode.TypeReference.IsEnum()) + if ((!edmTypeReference.IsPrimitive() && !edmTypeReference.IsEnum()) || (!operandType.IsLiteralType() && !operandType.ToNullableUnderlyingType().IsEnum)) return new ConstantOperator(null); @@ -345,6 +372,9 @@ IExpressionPart Convert(Type operandType, Type conversionType) private Type GetCastType(ConstantNode constantNode) => TypeExtensions.GetClrType((string)constantNode.Value, false, typesCache); + private Type GetCastType(SingleResourceCastNode singleResourceCastNode) + => TypeExtensions.GetClrType(singleResourceCastNode.TypeReference, typesCache); + private IExpressionPart GetCustomMehodFilterPart(string functionName, SingleValueNode[] arguments) { MethodInfo methodInfo = CustomMethodCache.GetCachedCustomMethod(functionName, arguments.Select(p => GetClrType(p.TypeReference))); diff --git a/AutoMapper.Extensions.OData.sln b/AutoMapper.Extensions.OData.sln index f603fae..052ff2f 100644 --- a/AutoMapper.Extensions.OData.sln +++ b/AutoMapper.Extensions.OData.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.AspNetCore.OData.EFCore", "AutoMapper.AspNetCore.OData.EFCore\AutoMapper.AspNetCore.OData.EFCore.csproj", "{8E4C661E-C58A-4515-9CBF-F58D9D6E941D}" EndProject diff --git a/AutoMapper.OData.EF6.Tests/AutoMapper.OData.EF6.Tests.csproj b/AutoMapper.OData.EF6.Tests/AutoMapper.OData.EF6.Tests.csproj index 904cf66..9fc9996 100644 --- a/AutoMapper.OData.EF6.Tests/AutoMapper.OData.EF6.Tests.csproj +++ b/AutoMapper.OData.EF6.Tests/AutoMapper.OData.EF6.Tests.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 false diff --git a/AutoMapper.OData.EFCore.Tests/AutoMapper.OData.EFCore.Tests.csproj b/AutoMapper.OData.EFCore.Tests/AutoMapper.OData.EFCore.Tests.csproj index b595d35..5c26fae 100644 --- a/AutoMapper.OData.EFCore.Tests/AutoMapper.OData.EFCore.Tests.csproj +++ b/AutoMapper.OData.EFCore.Tests/AutoMapper.OData.EFCore.Tests.csproj @@ -1,13 +1,13 @@  - net9.0 + net10.0 false - + diff --git a/DAL.EFCore/DAL.EFCore.csproj b/DAL.EFCore/DAL.EFCore.csproj index 69f8e47..46498ce 100644 --- a/DAL.EFCore/DAL.EFCore.csproj +++ b/DAL.EFCore/DAL.EFCore.csproj @@ -1,17 +1,9 @@  - net8.0;net9.0 + net8.0;net9.0;net10.0 - - - - - - - - @@ -20,4 +12,8 @@ + + + + diff --git a/ExpressionBuilder.Tests/ExpressionBuilder.Tests.csproj b/ExpressionBuilder.Tests/ExpressionBuilder.Tests.csproj index 4d3f7f2..b060ff2 100644 --- a/ExpressionBuilder.Tests/ExpressionBuilder.Tests.csproj +++ b/ExpressionBuilder.Tests/ExpressionBuilder.Tests.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 false diff --git a/ExpressionBuilder.Tests/FilterTests.cs b/ExpressionBuilder.Tests/FilterTests.cs index 201ba7b..6304663 100644 --- a/ExpressionBuilder.Tests/FilterTests.cs +++ b/ExpressionBuilder.Tests/FilterTests.cs @@ -2359,10 +2359,9 @@ public void Cast_UnsupportedTarget_ReturnsNull_Non_Default_Number(string filterS [Theory] [InlineData("cast(null,ExpressionBuilder.Tests.Data.Address) ne null", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.Address' is not assignable from 'ExpressionBuilder.Tests.Data.DataTypes'.")] + "Encountered invalid type cast. 'ExpressionBuilder.Tests.Data.Address' is not assignable from ''.")] [InlineData("cast(null,ExpressionBuilder.Tests.Data.DataTypes) ne null", - "Cast or IsOf Function must have a type in its arguments.")] + "Encountered invalid type cast. 'ExpressionBuilder.Tests.Data.DataTypes' is not assignable from ''.")] public void Cast_NonPrimitiveTarget_ThrowsODataException(string filterString, string expectErrorMessage) { //assert @@ -2502,22 +2501,22 @@ public static List CastToUnquotedComplexType { return new List { - new [] { "cast(ExpressionBuilder.Tests.Data.Address) eq null" }, - new [] { "cast(null, ExpressionBuilder.Tests.Data.Address) eq null" }, - new [] { "cast('', ExpressionBuilder.Tests.Data.Address) eq null" }, - new [] { "cast(SupplierAddress, ExpressionBuilder.Tests.Data.Address) eq null" }, + new [] { "cast(ExpressionBuilder.Tests.Data.Address) eq null", "ExpressionBuilder.Tests.Data.Address", "ExpressionBuilder.Tests.Data.Product" }, + new [] { "cast(null, ExpressionBuilder.Tests.Data.Address) eq null", "ExpressionBuilder.Tests.Data.Address", "" }, + new [] { "cast('', ExpressionBuilder.Tests.Data.Address) eq null", "ExpressionBuilder.Tests.Data.Address", "Edm.String" }, + new [] { "cast(null, ExpressionBuilder.Tests.Data.DerivedCategory)/DerivedCategoryName eq null", "ExpressionBuilder.Tests.Data.DerivedCategory", "" }, }; } } [Theory] [MemberData(nameof(CastToUnquotedComplexType))] - public void CastToUnquotedComplexType_ThrowsODataException(string filterString) + public void CastToUnquotedComplexType_ThrowsODataException(string filterString, string propertyFullQualifiedName, string assignableFrom) { //arrange var expectedErrorMessage = "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.Address' is not assignable from 'ExpressionBuilder.Tests.Data.Product'."; + $"'{propertyFullQualifiedName}' is not assignable from '{assignableFrom}'."; //assert var exception = Assert.Throws(() => GetFilter(filterString)); @@ -2560,41 +2559,27 @@ public void CastToQuotedComplexType_Succeeds(string filterString) Assert.True(result); } - public static List CastToUnquotedEntityType + [Theory] + [InlineData("cast(SupplierAddress, ExpressionBuilder.Tests.Data.Address) eq null")] + [InlineData("cast(ExpressionBuilder.Tests.Data.DerivedProduct)/DerivedProductName eq null")] + [InlineData("cast(Category, ExpressionBuilder.Tests.Data.DerivedCategory)/DerivedCategoryName eq null")] + public void CastToRelatedUnquotedEntityType_DoesNotThrowODataException(string filterString) { - get - { - return new List - { - new [] { - "cast(ExpressionBuilder.Tests.Data.DerivedProduct)/DerivedProductName eq null", - "Cast or IsOf Function must have a type in its arguments." - }, - new [] { - "cast(null, ExpressionBuilder.Tests.Data.DerivedCategory)/DerivedCategoryName eq null", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - new [] { - "cast(Category, ExpressionBuilder.Tests.Data.DerivedCategory)/DerivedCategoryName eq null", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - }; - } + // Arrange & Act & Assert + var exception = Record.Exception(() => GetFilter(filterString)); + Assert.Null(exception); } [Theory] - [MemberData(nameof(CastToUnquotedEntityType))] - public void CastToUnquotedEntityType_ThrowsODataException(string filterString, string expectedMessage) + [InlineData("isof(SupplierAddress,ExpressionBuilder.Tests.Data.Address)")] + [InlineData("isof(SupplierAddress, ExpressionBuilder.Tests.Data.Address)")] + public void IsOfUnquotedTypeParameter_RelatedToComplexTypeProperty_DoNotThrowODataException(string filterString) { - //assert - var exception = Assert.Throws(() => GetFilter(filterString)); - Assert.Equal - ( - expectedMessage, - exception.Message - ); + // Arrange + var exception = Record.Exception(() => GetFilter(filterString)); + + // Act & Assert + Assert.Null(exception); } [Theory] @@ -2807,23 +2792,21 @@ public static List IsOfUnquotedComplexType { return new List { - new [] { "isof(ExpressionBuilder.Tests.Data.Address)" }, - new [] { "isof(null,ExpressionBuilder.Tests.Data.Address)" }, - new [] { "isof(null, ExpressionBuilder.Tests.Data.Address)" }, - new [] { "isof(SupplierAddress,ExpressionBuilder.Tests.Data.Address)" }, - new [] { "isof(SupplierAddress, ExpressionBuilder.Tests.Data.Address)" }, + new [] { "isof(ExpressionBuilder.Tests.Data.Address)", "ExpressionBuilder.Tests.Data.Address", "ExpressionBuilder.Tests.Data.Product" }, + new [] { "isof(null,ExpressionBuilder.Tests.Data.Address)", "ExpressionBuilder.Tests.Data.Address", "" }, + new [] { "isof(null, ExpressionBuilder.Tests.Data.Address)", "ExpressionBuilder.Tests.Data.Address", "" }, + new [] { "isof(null, ExpressionBuilder.Tests.Data.DerivedCategory)", "ExpressionBuilder.Tests.Data.DerivedCategory", "" }, + new [] { "isof(null, ExpressionBuilder.Tests.Data.DerivedCategory)", "ExpressionBuilder.Tests.Data.DerivedCategory", "" }, }; } } [Theory] [MemberData(nameof(IsOfUnquotedComplexType))] - public void IsOfUnquotedComplexType_ThrowsODataException(string filterString) + public void IsOfUnquotedComplexType_ThrowsODataException(string filterString, string source, string assignableFrom) { //arrange - var expectedMessage = - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.Address' is not assignable from 'ExpressionBuilder.Tests.Data.Product'."; + var expectedMessage = $"Encountered invalid type cast. '{source}' is not assignable from '{assignableFrom}'."; //assert var exception = Assert.Throws(() => GetFilter(filterString)); @@ -2834,51 +2817,20 @@ public void IsOfUnquotedComplexType_ThrowsODataException(string filterString) ); } - public static List IsOfUnquotedEntityType - { - get - { - return new List - { - new [] { - "isof(ExpressionBuilder.Tests.Data.DerivedProduct)", - "Cast or IsOf Function must have a type in its arguments." - }, - new [] { - "isof(null,ExpressionBuilder.Tests.Data.DerivedCategory)", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - new [] { - "isof(null, ExpressionBuilder.Tests.Data.DerivedCategory)", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - new [] { - "isof(Category,ExpressionBuilder.Tests.Data.DerivedCategory)", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - new [] { - "isof(Category, ExpressionBuilder.Tests.Data.DerivedCategory)", - "Encountered invalid type cast. " + - "'ExpressionBuilder.Tests.Data.DerivedCategory' is not assignable from 'ExpressionBuilder.Tests.Data.Product'." - }, - }; - } - } - [Theory] - [MemberData(nameof(IsOfUnquotedEntityType))] - public void IsOfUnquotedEntityType_ThrowsODataException(string filterString, string expectedMessage) + [InlineData("isof(ExpressionBuilder.Tests.Data.DerivedProduct)")] + [InlineData("isof('ExpressionBuilder.Tests.Data.DerivedProduct')")] + [InlineData("isof(Category,ExpressionBuilder.Tests.Data.DerivedCategory)")] + [InlineData("isof(Category, ExpressionBuilder.Tests.Data.DerivedCategory)")] + [InlineData("isof(Category, 'ExpressionBuilder.Tests.Data.DerivedCategory')")] + [InlineData("isof(Category, ExpressionBuilder.Tests.Data.DerivedCategory)")] + [InlineData("isof(Category, 'ExpressionBuilder.Tests.Data.DerivedCategory')")] + public void IsOfUnquotedTypeParameter_RelatedToEntityTypeProperty_DoNotThrowODataException(string filterString) { - //assert - var exception = Assert.Throws(() => GetFilter(filterString)); - Assert.Equal - ( - expectedMessage, - exception.Message - ); + // Arrange & Act & Assert + var exception = Record.Exception(() => GetFilter(filterString)); + + Assert.Null(exception); } public static List IsOfQuotedNonPrimitiveType diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index e1b333c..954fcf3 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -2,15 +2,15 @@ Exe - net9.0 + net10.0 - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/SeedDatabase/SeedDatabase.csproj b/SeedDatabase/SeedDatabase.csproj index 6452e58..8d08bc8 100644 --- a/SeedDatabase/SeedDatabase.csproj +++ b/SeedDatabase/SeedDatabase.csproj @@ -2,13 +2,13 @@ Exe - net9.0 + net10.0 - - - + + + diff --git a/WebAPI.OData.EF6/WebAPI.OData.EF6.csproj b/WebAPI.OData.EF6/WebAPI.OData.EF6.csproj index 79001ee..bb03059 100644 --- a/WebAPI.OData.EF6/WebAPI.OData.EF6.csproj +++ b/WebAPI.OData.EF6/WebAPI.OData.EF6.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 diff --git a/WebAPI.OData.EFCore/WebAPI.OData.EFCore.csproj b/WebAPI.OData.EFCore/WebAPI.OData.EFCore.csproj index 922bd36..314e498 100644 --- a/WebAPI.OData.EFCore/WebAPI.OData.EFCore.csproj +++ b/WebAPI.OData.EFCore/WebAPI.OData.EFCore.csproj @@ -1,13 +1,13 @@  - net9.0 + net10.0 - - - + + +