Skip to content
This repository has been archived by the owner on Dec 29, 2020. It is now read-only.

Commit

Permalink
For #15
Browse files Browse the repository at this point in the history
Implemented recursive $select and $expand
* $select=CompanyName,ContactName,AccountManager/Forename,AccountManager/Surname,AccountManager/EmailAddress&$expand=AccountManager/Manager
  • Loading branch information
TrevorPilley committed Jun 1, 2020
1 parent 4b1d53f commit 1a8af10
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 40 deletions.
62 changes: 62 additions & 0 deletions Net.Http.OData.Tests/Linq/ODataQueryOptionsExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ namespace Net.Http.OData.Tests.Linq
public class ODataQueryOptionsExtensionsTests
{
private readonly IList<Category> _categories;
private readonly IList<Customer> _customers;
private readonly IList<Employee> _employees;
private readonly IList<Manager> _managers;
private readonly IList<Product> _products;

public ODataQueryOptionsExtensionsTests()
Expand All @@ -39,6 +42,23 @@ public ODataQueryOptionsExtensionsTests()
new Product { Category = _categories[1], Colour = Colour.Red, Description = "iPhone SE Silicone Case - Red", Name = "iPhone SE Silicone Case - Red", Price = 39.00M, ProductId = 11, Rating = 3.24f, ReleaseDate = new DateTime(2020, 4, 24) },
new Product { Category = _categories[1], Colour = Colour.Green, Description = "iPhone SE Silicone Case - Green", Name = "iPhone SE Silicone Case - Green", Price = 39.00M, ProductId = 12, Rating = 3.87f, ReleaseDate = new DateTime(2020, 4, 24) },
};

_managers = new[]
{
new Manager { AccessLevel = AccessLevel.Delete, AnnualBudget = 5000000.00M, BirthDate = new DateTime(1971, 5, 16), EmailAddress = "[email protected]", Forename="Bob", Id = "Bob.Jones", JoiningDate = new DateTime(2007, 3, 8), Surname = "Jones", Title = "Mr" },
new Manager { AccessLevel = AccessLevel.Delete, AnnualBudget = 8500000.00M, BirthDate = new DateTime(1982, 6, 21), EmailAddress = "[email protected]", Forename="Jess", Id = "Jess.Smith", JoiningDate = new DateTime(2006, 7, 15), Surname = "Smith", Title = "Mrs" },
};

_employees = new List<Employee>(_managers)
{
new Employee { AccessLevel = AccessLevel.Write, BirthDate = new DateTime(1989, 4, 21), EmailAddress = "[email protected]", Forename="Alice", Id = "Alice.Rake", JoiningDate = new DateTime(2012, 12, 6), Manager = _managers[0], Surname = "Rake", Title = "Miss" },
new Employee { AccessLevel = AccessLevel.Write, BirthDate = new DateTime(1992, 7, 2), EmailAddress = "[email protected]", Forename="Mark", Id = "Mark.Strong", JoiningDate = new DateTime(2012, 7, 23), Manager = _managers[1], Surname = "Strong", Title = "Mr" },
};

_customers = new[]
{
new Customer { AccountManager = _employees[_managers.Count + 1], Address = "Some Street", City = "Star City", CompanyName = "Target", ContactName = "Geoff Jr", Country = "USA", LegacyId = 8763, Phone = "555-4202", PostalCode = "76542" },
};
}

[Fact]
Expand Down Expand Up @@ -133,6 +153,48 @@ public void ApplyTo_OrderBy_SingleProperty_Descending()
Assert.Equal(_products.Min(x => x.Rating), ((dynamic)results[results.Count - 1]).Rating);
}

[Fact]
public void ApplyTo_Select_Expand()
{
TestHelper.EnsureEDM();

var queryOptions = new ODataQueryOptions(
"?$select=CompanyName,ContactName,AccountManager/Forename,AccountManager/Surname,AccountManager/EmailAddress&$expand=AccountManager/Manager",
EntityDataModel.Current.EntitySets["Customers"],
Mock.Of<IODataQueryOptionsValidator>());

IList<ExpandoObject> results = queryOptions.ApplyTo(_customers.AsQueryable()).ToList();

Assert.Equal(_customers.Count, results.Count);

for (int i = 0; i < _customers.Count; i++)
{
Assert.Equal(3, ((IDictionary<string, object>)results[i]).Count);
Assert.Equal(_customers[i].CompanyName, ((dynamic)results[i]).CompanyName);
Assert.Equal(_customers[i].ContactName, ((dynamic)results[i]).ContactName);

dynamic accountManager = ((dynamic)results[i]).AccountManager;
Assert.Equal(4, ((IDictionary<string, object>)accountManager).Count);
Assert.Equal(_customers[i].AccountManager.Forename, ((dynamic)accountManager).Forename);
Assert.Equal(_customers[i].AccountManager.Surname, ((dynamic)accountManager).Surname);
Assert.Equal(_customers[i].AccountManager.EmailAddress, ((dynamic)accountManager).EmailAddress);

dynamic manager = ((dynamic)accountManager).Manager;
Assert.Equal(11, ((IDictionary<string, object>)manager).Count);
Assert.Equal(_customers[i].AccountManager.Manager.AccessLevel, ((dynamic)manager).AccessLevel);
Assert.Equal(_customers[i].AccountManager.Manager.AnnualBudget, ((dynamic)manager).AnnualBudget);
Assert.Equal(_customers[i].AccountManager.Manager.BirthDate, ((dynamic)manager).BirthDate);
Assert.Equal(_customers[i].AccountManager.Manager.EmailAddress, ((dynamic)manager).EmailAddress);
Assert.Equal(_customers[i].AccountManager.Manager.Forename, ((dynamic)manager).Forename);
Assert.Equal(_customers[i].AccountManager.Manager.Id, ((dynamic)manager).Id);
Assert.Equal(_customers[i].AccountManager.Manager.ImageData, ((dynamic)manager).ImageData);
Assert.Equal(_customers[i].AccountManager.Manager.JoiningDate, ((dynamic)manager).JoiningDate);
Assert.Equal(_customers[i].AccountManager.Manager.LeavingDate, ((dynamic)manager).LeavingDate);
Assert.Equal(_customers[i].AccountManager.Manager.Surname, ((dynamic)manager).Surname);
Assert.Equal(_customers[i].AccountManager.Manager.Title, ((dynamic)manager).Title);
}
}

[Fact]
public void ApplyTo_Select_NotDeclared()
{
Expand Down
109 changes: 69 additions & 40 deletions Net.Http.OData/Linq/ODataQueryOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using Net.Http.OData.Model;
using Net.Http.OData.Query;
using Net.Http.OData.Query.Expressions;

Expand Down Expand Up @@ -48,7 +49,15 @@ public static IEnumerable<ExpandoObject> ApplyTo(this ODataQueryOptions queryOpt
throw new InvalidOperationException();
}

return ApplyToImpl(queryOptions, queryable);
return Apply(queryOptions, queryable);
}

private static IEnumerable<ExpandoObject> Apply(ODataQueryOptions queryOptions, IQueryable queryable)
{
foreach (object entity in queryable.ApplyOrder(queryOptions).ApplySkip(queryOptions).ApplyTop(queryOptions))
{
yield return ApplySelect(entity, queryOptions);
}
}

private static IQueryable ApplyOrder(this IQueryable queryable, ODataQueryOptions queryOptions)
Expand Down Expand Up @@ -100,6 +109,65 @@ private static IQueryable ApplyOrder(this IQueryable queryable, ODataQueryOption
return q;
}

private static ExpandoObject ApplySelect(object entity, ODataQueryOptions queryOptions)
{
IEnumerable<PropertyPath> propertyPaths = queryOptions.Select?.PropertyPaths ?? queryOptions.EntitySet.EdmType.Properties.Where(p => !p.IsNavigable).Select(PropertyPath.For);

if (queryOptions.Expand != null)
{
propertyPaths = propertyPaths.Concat(queryOptions.Expand.PropertyPaths);
}

var expandoObject = new ExpandoObject();

foreach (PropertyPath propertyPath in propertyPaths)
{
PropertyPath path = propertyPath;
var dictionary = (IDictionary<string, object>)expandoObject;
object obj = entity;

while (path.Next != null)
{
if (!dictionary.ContainsKey(path.Property.Name))
{
dictionary[path.Property.Name] = new ExpandoObject();
}

dictionary = (IDictionary<string, object>)dictionary[path.Property.Name];
obj = path.Property.ClrProperty.GetValue(obj);
path = path.Next;
}

if (path.Property.IsNavigable)
{
dictionary[path.Property.Name] = new ExpandoObject();
dictionary = (IDictionary<string, object>)dictionary[path.Property.Name];
obj = path.Property.ClrProperty.GetValue(obj);

var edmComplexType = path.Property.PropertyType as EdmComplexType;

while (edmComplexType != null)
{
foreach (EdmProperty edmProperty in edmComplexType.Properties)
{
if (!edmProperty.IsNavigable)
{
dictionary[edmProperty.Name] = edmProperty.ClrProperty.GetValue(obj);
}
}

edmComplexType = edmComplexType.BaseType as EdmComplexType;
}
}
else
{
dictionary[path.Property.Name] = path.Property.ClrProperty.GetValue(obj);
}
}

return expandoObject;
}

private static IQueryable ApplySkip(this IQueryable queryable, ODataQueryOptions queryOptions)
{
if (queryOptions.Skip.HasValue)
Expand All @@ -117,19 +185,6 @@ private static IQueryable ApplySkip(this IQueryable queryable, ODataQueryOptions
return queryable;
}

private static IEnumerable<ExpandoObject> ApplyToImpl(ODataQueryOptions queryOptions, IQueryable queryable)
{
IQueryable q = queryable
.ApplyOrder(queryOptions)
.ApplySkip(queryOptions)
.ApplyTop(queryOptions);

foreach (object entity in q)
{
yield return BuildExpando(queryOptions, entity);
}
}

private static IQueryable ApplyTop(this IQueryable queryable, ODataQueryOptions queryOptions)
{
if (queryOptions.Top.HasValue)
Expand All @@ -147,32 +202,6 @@ private static IQueryable ApplyTop(this IQueryable queryable, ODataQueryOptions
return queryable;
}

private static ExpandoObject BuildExpando(ODataQueryOptions queryOptions, object entity)
{
IEnumerable<PropertyPath> propertyPaths = queryOptions.Select?.PropertyPaths ?? queryOptions.EntitySet.EdmType.Properties.Where(p => !p.IsNavigable).Select(PropertyPath.For);

var expandoObject = new ExpandoObject();

foreach (PropertyPath propertyPath in propertyPaths)
{
PropertyPath path = propertyPath;
var dictionary = (IDictionary<string, object>)expandoObject;
object obj = entity;

while (path.Next != null)
{
dictionary[path.Property.Name] = new ExpandoObject();
dictionary = (IDictionary<string, object>)dictionary[path.Property.Name];
obj = path.Property.ClrProperty.GetValue(entity);
path = path.Next;
}

dictionary[path.Property.Name] = path.Property.ClrProperty.GetValue(obj);
}

return expandoObject;
}

private static string OrderByMethodName(OrderByDirection direction, int index)
{
if (direction == OrderByDirection.Ascending)
Expand Down

0 comments on commit 1a8af10

Please sign in to comment.