diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index ebb757b81..0f8c11242 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -13961,7 +13961,6 @@
Gets or sets the route order in conventional routing.
- By default, move the conventional routing later as much as possible.
diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/SelectExpandBinder.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/SelectExpandBinder.cs
index 1ad067ca2..e8ac55a2b 100644
--- a/src/Microsoft.AspNetCore.OData/Query/Expressions/SelectExpandBinder.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/SelectExpandBinder.cs
@@ -375,7 +375,7 @@ internal Expression ProjectElement(QueryBinderContext context, Expression source
Expression updatedExpression = null;
if (IsEfQueryProvider(context))
{
- updatedExpression = SelectExpandBinder.RemoveNonStructucalProperties(source, structuredType);
+ updatedExpression = SelectExpandBinder.RemoveNonStructucalProperties(context, source, structuredType);
}
else
{
@@ -443,8 +443,9 @@ internal Expression ProjectElement(QueryBinderContext context, Expression source
// Generates the expression
// { Instance = new Customer() {Id = $it.Id, Name= $it.Name}}
- private static Expression RemoveNonStructucalProperties(Expression source, IEdmStructuredType structuredType)
+ private static Expression RemoveNonStructucalProperties(QueryBinderContext context, Expression source, IEdmStructuredType structuredType)
{
+ IEdmModel model = context.Model;
Expression updatedSource = null;
Type elementType = source.Type;
@@ -457,7 +458,7 @@ private static Expression RemoveNonStructucalProperties(Expression source, IEdmS
{
foreach (var sp in structuralProperties)
{
- if (sp.Name == prop.Name)
+ if (model.GetClrPropertyName(sp) == prop.Name)
{
MemberExpression propertyExpression = Expression.Property(source, prop);
bindings.Add(Expression.Bind(prop, propertyExpression));
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/SelectExpandBinderTest.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/SelectExpandBinderTest.cs
index 2b3dad75a..e2558465b 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/SelectExpandBinderTest.cs
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/SelectExpandBinderTest.cs
@@ -33,21 +33,35 @@ public class SelectExpandBinderTest
private static IPropertyMapper PropertyMapper = new IdentityPropertyMapper();
private readonly SelectExpandBinder _binder;
+ private readonly SelectExpandBinder _binder_lowerCamelCased;
private readonly IQueryable _queryable;
+ private readonly IQueryable _queryable_lowerCamelCased;
private readonly ODataQueryContext _context;
+ private readonly ODataQueryContext _context_lowerCamelCased;
private readonly ODataQuerySettings _settings;
+ private readonly ODataQuerySettings _settings_lowerCamelCased;
private readonly QueryBinderContext _queryBinderContext;
+ private readonly QueryBinderContext _queryBinderContext_lowerCamelCased;
private readonly IEdmModel _model;
+ private readonly IEdmModel _model_lowerCamelCased;
private readonly IEdmEntityType _customer;
+ private readonly IEdmEntityType _customer_lowerCamelCased;
private readonly IEdmEntityType _order;
+ private readonly IEdmEntityType _order_lowerCamelCased;
private readonly IEdmEntityType _product;
+ private readonly IEdmEntityType _product_lowerCamelCased;
private readonly IEdmEntitySet _customers;
+ private readonly IEdmEntitySet _customers_lowerCamelCased;
private readonly IEdmEntitySet _orders;
+ private readonly IEdmEntitySet _orders_lowerCamelCased;
private readonly IEdmEntitySet _products;
+ private readonly IEdmEntitySet _products_lowerCamelCased;
public SelectExpandBinderTest()
{
+ #region PascalCase EdmModel
+
_model = GetEdmModel();
_customer = _model.SchemaElements.OfType().First(c => c.Name == "QueryCustomer");
_order = _model.SchemaElements.OfType().First(c => c.Name == "QueryOrder");
@@ -64,7 +78,7 @@ public SelectExpandBinderTest()
{
Orders = new List()
};
- QueryOrder order = new QueryOrder { Customer = customer };
+ QueryOrder order = new QueryOrder { Id = 42, Title = "The order", Customer = customer };
customer.Orders.Add(order);
_queryable = new[] { customer }.AsQueryable();
@@ -75,6 +89,40 @@ public SelectExpandBinderTest()
{
NavigationSource = _context.NavigationSource
};
+
+ #endregion
+
+ #region camelCase EdmModel
+
+ _model_lowerCamelCased = GetEdmModel_lowerCamelCased();
+ _customer_lowerCamelCased = _model_lowerCamelCased.SchemaElements.OfType().First(c => c.Name == "QueryCustomer");
+ _order_lowerCamelCased = _model_lowerCamelCased.SchemaElements.OfType().First(c => c.Name == "QueryOrder");
+ _product_lowerCamelCased = _model_lowerCamelCased.SchemaElements.OfType().First(c => c.Name == "QueryProduct");
+ _customers_lowerCamelCased = _model_lowerCamelCased.EntityContainer.FindEntitySet("Customers");
+ _orders_lowerCamelCased = _model_lowerCamelCased.EntityContainer.FindEntitySet("Orders");
+ _products_lowerCamelCased = _model_lowerCamelCased.EntityContainer.FindEntitySet("Products");
+
+ _settings_lowerCamelCased = new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.False };
+ _context_lowerCamelCased = new ODataQueryContext(_model_lowerCamelCased, typeof(QueryCustomer)) { RequestContainer = new MockServiceProvider() };
+ _binder_lowerCamelCased = new SelectExpandBinder(new FilterBinder(), new OrderByBinder());
+
+ QueryCustomer customer_lowerCamelCased = new QueryCustomer
+ {
+ Orders = new List()
+ };
+ QueryOrder order_lowerCamelCased = new QueryOrder { Id = 42, Title = "The order", Customer = customer_lowerCamelCased };
+ customer_lowerCamelCased.Orders.Add(order_lowerCamelCased);
+
+ _queryable_lowerCamelCased = new[] { customer_lowerCamelCased }.AsQueryable();
+
+ SelectExpandQueryOption selectExpandQueryOption_lowerCamelCased = new SelectExpandQueryOption("Orders", expand: null, context: _context_lowerCamelCased);
+
+ _queryBinderContext_lowerCamelCased = new QueryBinderContext(_model_lowerCamelCased, _settings_lowerCamelCased, selectExpandQueryOption_lowerCamelCased.Context.ElementClrType)
+ {
+ NavigationSource = _context_lowerCamelCased.NavigationSource
+ };
+
+ #endregion
}
private static SelectExpandBinder GetBinder(IEdmModel model, HandleNullPropagationOption nullPropagation = HandleNullPropagationOption.False)
@@ -237,8 +285,11 @@ public void Bind_UsingEFQueryProvider_GeneratedExpression__DoesNot_ContainExpand
// We only write structural properties to the instance.
// This means that navigation properties on the instance property will be null
- // when using any instance of EF query provider.
+ // when using any instance of EF query provider, and all structural properties
+ // will be assigned.
Assert.Null(partialOrder.Instance.Customer);
+ Assert.NotEqual(0, partialOrder.Instance.Id);
+ Assert.NotNull(partialOrder.Instance.Title);
object customer = partialOrder.Container.ToDictionary(PropertyMapper)["Customer"];
SelectExpandWrapper innerInnerCustomer = Assert.IsAssignableFrom>(customer);
@@ -246,6 +297,47 @@ public void Bind_UsingEFQueryProvider_GeneratedExpression__DoesNot_ContainExpand
Assert.Null(innerInnerCustomer.Instance.Orders);
}
+ [Theory]
+ [InlineData(HandleNullPropagationOptionHelper.EntityFrameworkQueryProviderNamespace)]
+ [InlineData(HandleNullPropagationOptionHelper.ObjectContextQueryProviderNamespaceEFCore2)]
+ [InlineData(HandleNullPropagationOptionHelper.ObjectContextQueryProviderNamespaceEF5)]
+ [InlineData(HandleNullPropagationOptionHelper.ObjectContextQueryProviderNamespaceEF6)]
+ public void Bind_UsingEFQueryProvider_LowerCamelCasedModel_GeneratedExpression__DoesNot_ContainExpandedObject(string queryProvider)
+ {
+ // Arrange
+ SelectExpandQueryOption selectExpand = new SelectExpandQueryOption("Orders", "Orders,Orders($expand=Customer)", _context_lowerCamelCased);
+
+ // Act
+ SelectExpandBinder binder = new SelectExpandBinder();
+ _queryBinderContext_lowerCamelCased.QueryProvider = queryProvider;
+ IQueryable queryable = binder.ApplyBind(_queryable_lowerCamelCased, selectExpand.SelectExpandClause, _queryBinderContext_lowerCamelCased);
+
+ // Assert
+ IEnumerator enumerator = queryable.GetEnumerator();
+ Assert.True(enumerator.MoveNext());
+ var partialCustomer = Assert.IsAssignableFrom>(enumerator.Current);
+ Assert.False(enumerator.MoveNext());
+ Assert.Null(partialCustomer.Instance);
+ Assert.Equal("Microsoft.AspNetCore.OData.Tests.Query.Expressions.QueryCustomer", partialCustomer.InstanceType);
+ IEnumerable> innerOrders = partialCustomer.Container
+ .ToDictionary(PropertyMapper)["orders"] as IEnumerable>;
+ Assert.NotNull(innerOrders);
+ SelectExpandWrapper partialOrder = innerOrders.Single();
+
+ // We only write structural properties to the instance.
+ // This means that navigation properties on the instance property will be null
+ // when using any instance of EF query provider, and all structural properties
+ // will be assigned.
+ Assert.Null(partialOrder.Instance.Customer);
+ Assert.NotEqual(0, partialOrder.Instance.Id);
+ Assert.NotNull(partialOrder.Instance.Title);
+
+ object customer = partialOrder.Container.ToDictionary(PropertyMapper)["customer"];
+ SelectExpandWrapper innerInnerCustomer = Assert.IsAssignableFrom>(customer);
+
+ Assert.Null(innerInnerCustomer.Instance.Orders);
+ }
+
[Fact]
public void Bind_GeneratedExpression_CheckNullObjectWithinChainProjectionByKey()
{
@@ -1991,6 +2083,19 @@ public static IEdmModel GetEdmModel()
return builder.GetEdmModel();
}
+ public static IEdmModel GetEdmModel_lowerCamelCased()
+ {
+ var builder = new ODataConventionModelBuilder();
+ var customer = builder.EntitySet("Customers").EntityType;
+ builder.EntitySet("Orders");
+ builder.EntitySet("Cities");
+ builder.EntitySet("Products");
+
+ customer.Collection.Function("IsUpgraded").Returns().Namespace="NS";
+ customer.Collection.Action("UpgradeAll").Namespace = "NS";
+ builder.EnableLowerCamelCase();
+ return builder.GetEdmModel();
+ }
public static SelectExpandClause ParseSelectExpand(string select, string expand, IEdmModel model, IEdmType edmType, IEdmNavigationSource navigationSource)
{
return new ODataQueryOptionParser(model, edmType, navigationSource,