diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs
new file mode 100644
index 000000000..0c7bd983e
--- /dev/null
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs
@@ -0,0 +1,96 @@
+using Microsoft.FullyQualified.NS;
+using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
+
+namespace Microsoft.AspNetCore.OData.Tests.Query.Expressions
+{
+ internal static class EdmModelBuilder
+ {
+ private static readonly IEdmModel Model = BuildAndGetEdmModel();
+
+ public static IEdmModel TestModel
+ {
+ get { return Model; }
+ }
+
+ public static IEdmEntityType GetEntityType(string entityQualifiedName)
+ {
+ return TestModel.FindDeclaredType(entityQualifiedName) as IEdmEntityType;
+ }
+
+ public static IEdmComplexType GetEdmComplexType(string complexTypeQualifiedName)
+ {
+ return TestModel.FindDeclaredType(complexTypeQualifiedName) as IEdmComplexType;
+ }
+
+ public static IEdmEntityTypeReference GetEntityTypeReference(IEdmEntityType entityType)
+ {
+ return new EdmEntityTypeReference(entityType, false);
+ }
+
+ public static IEdmComplexTypeReference GetComplexTypeReference(IEdmComplexType complexType)
+ {
+ // Create a complex type reference using the EdmCoreModel
+ return new EdmComplexTypeReference(complexType, isNullable: false);
+ }
+
+ public static IEdmProperty GetPersonLocationProperty()
+ {
+ return GetEntityType("Microsoft.FullyQualified.NS.Person").FindProperty("Location");
+ }
+
+ ///
+ /// Get the entity set from the model
+ ///
+ /// People Set
+ public static IEdmEntitySet GetPeopleSet()
+ {
+ return TestModel.EntityContainer.FindEntitySet("People");
+ }
+
+ public static IEdmModel BuildAndGetEdmModel()
+ {
+ var builder = new ODataConventionModelBuilder();
+ builder.Namespace = "Microsoft.FullyQualified.NS";
+ builder.EntitySet("People");
+ builder.ComplexType();
+ builder.ComplexType();
+ builder.EntitySet("Employees");
+
+ return builder.GetEdmModel();
+ }
+ }
+}
+
+namespace Microsoft.FullyQualified.NS
+{
+ public class Person
+ {
+ public int Id { get; set; }
+ public string FullName { get; set; }
+ public MyAddress Location { get; set; }
+ }
+
+ public class Employee : Person
+ {
+ public string EmployeeNumber { get; set; }
+ }
+
+ public class MyAddress
+ {
+ public string Street { get; set; }
+ public AddressType AddressType { get; set; }
+ }
+
+ public class WorkAddress : MyAddress
+ {
+ public string OfficeNumber { get; set; }
+ }
+
+ public enum AddressType
+ {
+ Home,
+ Work,
+ Other
+ }
+}
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs
new file mode 100644
index 000000000..d9b2732e1
--- /dev/null
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs
@@ -0,0 +1,74 @@
+using Microsoft.OData.Edm;
+using Microsoft.OData.UriParser;
+
+namespace Microsoft.AspNetCore.OData.Tests.Query.Expressions
+{
+ public class FakeSingleEntityNode : SingleEntityNode
+ {
+ private readonly IEdmEntityTypeReference typeReference;
+ private readonly IEdmEntitySetBase set;
+
+ public FakeSingleEntityNode(IEdmEntityTypeReference type, IEdmEntitySetBase set)
+ {
+ this.typeReference = type;
+ this.set = set;
+ }
+
+ public override IEdmTypeReference TypeReference
+ {
+ get { return this.typeReference; }
+ }
+
+ public override IEdmNavigationSource NavigationSource
+ {
+ get { return this.set; }
+ }
+
+ public override IEdmStructuredTypeReference StructuredTypeReference
+ {
+ get { return this.typeReference; }
+ }
+
+ public override IEdmEntityTypeReference EntityTypeReference
+ {
+ get { return this.typeReference; }
+ }
+
+ public static FakeSingleEntityNode CreateFakeNodeForPerson()
+ {
+ var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person");
+ return new FakeSingleEntityNode(EdmModelBuilder.GetEntityTypeReference(personType), EdmModelBuilder.GetPeopleSet());
+ }
+ }
+
+ public class FakeCollectionResourceNode : CollectionResourceNode
+ {
+ private readonly IEdmStructuredTypeReference _typeReference;
+ private readonly IEdmNavigationSource _source;
+ private readonly IEdmTypeReference _itemType;
+ private readonly IEdmCollectionTypeReference _collectionType;
+
+ public FakeCollectionResourceNode(IEdmStructuredTypeReference type, IEdmNavigationSource source, IEdmTypeReference itemType, IEdmCollectionTypeReference collectionType)
+ {
+ _typeReference = type;
+ _source = source;
+ _itemType = itemType;
+ _collectionType = collectionType;
+ }
+
+ public override IEdmStructuredTypeReference ItemStructuredType => _typeReference;
+
+ public override IEdmNavigationSource NavigationSource => _source;
+
+ public override IEdmTypeReference ItemType => _itemType;
+
+ public override IEdmCollectionTypeReference CollectionType => _collectionType;
+
+ public static FakeCollectionResourceNode CreateFakeNodeForPerson()
+ {
+ var singleEntityNode = FakeSingleEntityNode.CreateFakeNodeForPerson();
+ return new FakeCollectionResourceNode(
+ singleEntityNode.EntityTypeReference, singleEntityNode.NavigationSource, singleEntityNode.EntityTypeReference, singleEntityNode.EntityTypeReference.AsCollection());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs
index e580c2f1f..c53663194 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs
@@ -6,13 +6,18 @@
//------------------------------------------------------------------------------
using System;
+using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
+using Microsoft.AspNetCore.OData.Edm;
+using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Query.Expressions;
using Microsoft.AspNetCore.OData.Query.Wrapper;
using Microsoft.AspNetCore.OData.Tests.Commons;
using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
using Microsoft.OData.UriParser;
+using Microsoft.FullyQualified.NS;
using Moq;
using Xunit;
@@ -100,6 +105,134 @@ public void BindSingleResourceFunctionCallNode_ThrowsNotSupported_ForNotAcceptNo
"Unknown function 'anyUnknown'.");
}
+ [Theory]
+ [InlineData(typeof(ConstantNode))]
+ [InlineData(typeof(SingleResourceCastNode))]
+ public void BindSingleResourceFunctionCallNode_CastingEntityType_ReturnsExpression(Type queryNodeType)
+ {
+ // Arrange
+ var binder = new MyQueryBinder();
+
+ var model = EdmModelBuilder.BuildAndGetEdmModel();
+
+ // Create the type reference and navigation source
+ var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person");
+ var personTypeRef = EdmModelBuilder.GetEntityTypeReference(personType);
+ var collectionNode = FakeCollectionResourceNode.CreateFakeNodeForPerson();
+
+ var employeeType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Employee");
+
+ // Create a ResourceRangeVariableReferenceNode for the Person entity
+ var rangeVariable = new ResourceRangeVariable("$it", personTypeRef, collectionNode);
+ var personNode = new ResourceRangeVariableReferenceNode(rangeVariable.Name, rangeVariable) as SingleValueNode;
+
+ // Create the parameters list
+ int capacity = 2;
+ var parameters = new List(capacity)
+ {
+ personNode // First parameter is the Person entity
+ };
+
+ if (queryNodeType == typeof(SingleResourceCastNode))
+ {
+ // Create a SingleResourceCastNode to cast Person to NS.Employee
+ var singleResourceCastNode = new SingleResourceCastNode(personNode as SingleResourceNode, employeeType);
+ parameters.Add(singleResourceCastNode); // Second parameter is the SingleResourceCastNode
+ }
+ else if (queryNodeType == typeof(ConstantNode))
+ {
+ // Create a ConstantNode to cast Person to NS.Employee
+ var constantNode = new ConstantNode("Microsoft.FullyQualified.NS.Employee");
+ parameters.Add(constantNode); // Second parameter is the ConstantNode
+ }
+
+ // Create the SingleResourceFunctionCallNode
+ var node = new SingleResourceFunctionCallNode("cast", parameters, collectionNode.ItemStructuredType, collectionNode.NavigationSource);
+
+ // Create an instance of QueryBinderContext using the real model and settings
+ Type clrType = model.GetClrType(personType);
+ var context = new QueryBinderContext(model, new ODataQuerySettings(), clrType);
+
+ // Act
+ Expression expression = binder.BindSingleResourceFunctionCallNode(node, context);
+
+ // Assert
+ Assert.NotNull(expression);
+ Assert.Equal("($it As Employee)", expression.ToString());
+ Assert.Equal("Microsoft.FullyQualified.NS.Employee", expression.Type.ToString());
+ Assert.Equal(typeof(Employee), expression.Type);
+ Assert.Equal(ExpressionType.TypeAs, expression.NodeType);
+ Assert.Equal(personType.FullName(), (expression as UnaryExpression).Operand.Type.FullName);
+ Assert.Equal(employeeType.FullName(), (expression as UnaryExpression).Type.FullName);
+ }
+
+ [Theory]
+ [InlineData(typeof(ConstantNode))]
+ [InlineData(typeof(SingleResourceCastNode))]
+ public void BindSingleResourceFunctionCallNode_PropertyCasting_ReturnsExpression(Type queryNodeType)
+ {
+ // Arrange
+ var binder = new MyQueryBinder();
+
+ var model = EdmModelBuilder.BuildAndGetEdmModel();
+
+ // Create the type reference and navigation source
+ var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person");
+ var personTypeRef = EdmModelBuilder.GetEntityTypeReference(personType);
+ var collectionNode = FakeCollectionResourceNode.CreateFakeNodeForPerson();
+
+ var myAddressType = EdmModelBuilder.GetEdmComplexType("Microsoft.FullyQualified.NS.MyAddress");
+ var workAddressType = EdmModelBuilder.GetEdmComplexType("Microsoft.FullyQualified.NS.WorkAddress");
+
+ // Create a ResourceRangeVariableReferenceNode for the Person entity
+ var rangeVariable = new ResourceRangeVariable("$it", personTypeRef, collectionNode);
+ var personNode = new ResourceRangeVariableReferenceNode(rangeVariable.Name, rangeVariable) as SingleValueNode;
+
+ // Create a SingleComplexNode for the Location property of the Person entity
+ var locationProperty = EdmModelBuilder.GetPersonLocationProperty();
+ var locationNode = new SingleComplexNode(personNode as SingleResourceNode, locationProperty);
+
+ // Create the parameters list
+ int capacity = 2;
+ var parameters = new List(capacity)
+ {
+ locationNode // First parameter is the Location property
+ };
+
+ if(queryNodeType == typeof(SingleResourceCastNode))
+ {
+ // Create a SingleResourceCastNode to cast Location to NS.WorkAddress
+ var singleResourceCastNode = new SingleResourceCastNode(locationNode, workAddressType);
+ parameters.Add(singleResourceCastNode); // Second parameter is the SingleResourceCastNode
+ }
+ else if (queryNodeType == typeof(ConstantNode))
+ {
+ // Create a ConstantNode to cast Location to NS.WorkAddress
+ var constantNode = new ConstantNode("Microsoft.FullyQualified.NS.WorkAddress");
+ parameters.Add(constantNode); // Second parameter is the ConstantNode
+ }
+
+ // Create the SingleResourceFunctionCallNode
+ var node = new SingleResourceFunctionCallNode("cast", parameters, collectionNode.ItemStructuredType, collectionNode.NavigationSource);
+
+ // Create an instance of QueryBinderContext using the real model and settings
+ Type clrType = model.GetClrType(personType);
+ var context = new QueryBinderContext(model, new ODataQuerySettings(), clrType);
+
+ // Act
+ Expression expression = binder.BindSingleResourceFunctionCallNode(node, context);
+
+ // Assert
+ Assert.NotNull(expression);
+ Assert.Equal("($it.Location As WorkAddress)", expression.ToString());
+ Assert.Equal("Microsoft.FullyQualified.NS.WorkAddress", expression.Type.ToString());
+ Assert.Equal("Location", ((expression as UnaryExpression).Operand as MemberExpression).Member.Name);
+ Assert.Equal(typeof(WorkAddress), expression.Type);
+ Assert.Equal(ExpressionType.TypeAs, expression.NodeType);
+ Assert.Equal(workAddressType.FullName(), (expression as UnaryExpression).Type.FullName);
+ Assert.Equal(myAddressType.FullName(), (expression as UnaryExpression).Operand.Type.FullName);
+ }
+
[Fact]
public void BindSingleValueFunctionCallNode_ThrowsArgumentNull_ForInputs()
{