Skip to content

Commit

Permalink
from groupjoin
Browse files Browse the repository at this point in the history
  • Loading branch information
voronov-maxim committed Aug 5, 2018
1 parent 5b493c4 commit 34aeda7
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 30 deletions.
70 changes: 56 additions & 14 deletions test/OdataToEntity.Test/Common/IncludeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ internal sealed class IncludeVisitor : ExpressionVisitor
{
public readonly struct Include
{
public Include(PropertyInfo property, Func<IEnumerable, IList> filter, bool isSelect)
public Include(PropertyInfo property, Func<IEnumerable, IList> filter)
{
Property = property;
Filter = filter;
IsSelect = isSelect;
}

public Func<IEnumerable, IList> Filter { get; }
public bool IsSelect { get; }
public PropertyInfo Property { get; }
}

Expand Down Expand Up @@ -87,9 +85,11 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
}

private readonly List<Include> _includes;
private readonly ModelBuilder.OeEdmModelMetadataProvider _metadataProvider;

public IncludeVisitor()
public IncludeVisitor(ModelBuilder.OeEdmModelMetadataProvider metadataProvider)
{
_metadataProvider = metadataProvider;
_includes = new List<Include>();
}

Expand All @@ -114,35 +114,77 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
Type entityType = node.Method.GetGenericArguments()[0];
MethodInfo method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(entityType, visitor.Property.Type);
LambdaExpression lambda = Expression.Lambda(visitor.Property, visitor.Parameter);
node = Expression.Call(null, method, new Expression[] { node.Arguments[0], lambda });
node = Expression.Call(null, method, new Expression[] { expression, lambda });
}
else
{
Type entityType = node.Method.GetGenericArguments()[0];
Type previousPropertyType = node.Method.GetGenericArguments()[1];
MethodInfo method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(entityType, previousPropertyType, visitor.Property.Type);
LambdaExpression lambda = Expression.Lambda(visitor.Property, visitor.Parameter);
node = Expression.Call(null, method, new Expression[] { node.Arguments[0], lambda });
node = Expression.Call(null, method, new Expression[] { expression, lambda });
}
}

_includes.Add(new Include(visitor.Property.Member as PropertyInfo, visitor.Filter, false));
_includes.Add(new Include(visitor.Property.Member as PropertyInfo, visitor.Filter));
return node;
}
}
else if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.Select))
{
var visitor = new NewVisitor();
visitor.Visit(node.Arguments[1]);
_includes.AddRange(visitor.SelectProperties.Select(p => new Include(p, null, true)));
_includes.AddRange(visitor.SelectProperties.Select(p => new Include(p, null)));
}
else
else if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin))
{
var arguments = new Expression[node.Arguments.Count];
node.Arguments.CopyTo(arguments, 0);
arguments[0] = expression;
node = Expression.Call(node.Object, node.Method, arguments);
Type outerType = node.Arguments[0].Type.GetGenericArguments()[0];
Type innerType = node.Arguments[1].Type.GetGenericArguments()[0];

List<PropertyInfo> navigationProperties = outerType.GetProperties().Where(p => p.PropertyType == innerType).ToList();
if (navigationProperties.Count == 0)
{
Type collectionType = typeof(IEnumerable<>).MakeGenericType(innerType);
PropertyInfo navigationProperty = outerType.GetProperties().Where(p => collectionType.IsAssignableFrom(p.PropertyType)).Single();
_includes.Add(new Include(navigationProperty, null));
}
else if (navigationProperties.Count == 1)
{
_includes.Add(new Include(navigationProperties[0], null));
}
else
{
LambdaExpression outerKeySelector;
if (node.Arguments[2] is UnaryExpression unaryExpression)
outerKeySelector = (LambdaExpression)unaryExpression.Operand;
else
outerKeySelector = (LambdaExpression)node.Arguments[2];

if (outerKeySelector.Body is MemberExpression propertyExpression)
{
PropertyInfo navigationProperty = _metadataProvider.GetForeignKey((PropertyInfo)propertyExpression.Member).Single();
_includes.Add(new Include(navigationProperty, null));
}
else if (outerKeySelector.Body is NewExpression newExpression)
{
List<PropertyInfo> properties = newExpression.Arguments.Select(a => (PropertyInfo)((MemberExpression)a).Member).ToList();
foreach (PropertyInfo navigationProperty in navigationProperties)
{
PropertyInfo[] structuralProperties = _metadataProvider.GetForeignKey(navigationProperty);
if (!structuralProperties.Except(properties).Any())
{
_includes.Add(new Include(navigationProperty, null));
break;
}
}
}
}
}
return node;

var arguments = new Expression[node.Arguments.Count];
node.Arguments.CopyTo(arguments, 0);
arguments[0] = expression;
return Expression.Call(node.Object, node.Method, arguments);
}

public IReadOnlyList<Include> Includes => _includes;
Expand Down
28 changes: 21 additions & 7 deletions test/OdataToEntity.Test/Common/SelectTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using OdataToEntity.Test.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -317,8 +316,8 @@ public async Task ExpandExpandMany(int pageSize, bool navigationNextLink)
{
var parameters = new QueryParameters<Customer, Customer>()
{
RequestUri = "Customers?$expand=AltOrders($expand=Items),Orders($expand=Items)&$orderby=Country,Id",
Expression = t => t.Include(c => c.AltOrders).Include(c => c.Orders).ThenInclude(o => o.Items.OrderBy(i => i.Id)).OrderBy(c => c.Country).ThenBy(c => c.Id),
RequestUri = "Customers?$expand=AltOrders($expand=Items($orderby=Price desc),ShippingAddresses($orderby=Id desc)),Orders($expand=Items($orderby=Price desc),ShippingAddresses($orderby=Id desc))&$orderby=Country,Id",
Expression = t => t.Include(c => c.AltOrders).Include(c => c.Orders).ThenInclude(o => o.Items.OrderByDescending(i => i.Price)).Include(c => c.Orders).ThenInclude(o => o.ShippingAddresses.OrderByDescending(s => s.Id)).OrderBy(c => c.Country).ThenBy(c => c.Id),
NavigationNextLink = navigationNextLink,
PageSize = pageSize
};
Expand Down Expand Up @@ -365,7 +364,7 @@ public async Task ExpandExpandSkipTop(int pageSize, bool navigationNextLink)
{
var parameters = new QueryParameters<Customer, Customer>()
{
RequestUri = "Customers?$orderby=Country,Id&$skip=1&$top=3&$expand=AltOrders($expand=Items($top=1)),Orders($expand=Items($top=1))",
RequestUri = "Customers?$orderby=Country,Id&$skip=1&$top=3&$expand=AltOrders($expand=Items($orderby=Id;$top=1)),Orders($expand=Items($top=1))",
Expression = t => t.OrderBy(c => c.Country).ThenBy(c => c.Id).Skip(1).Take(3).Include(c => c.AltOrders).Include(c => c.Orders).ThenInclude(o => o.Items.Take(1)),
NavigationNextLink = navigationNextLink,
PageSize = pageSize
Expand Down Expand Up @@ -446,9 +445,24 @@ public async Task ExpandOneFilter(int pageSize, bool navigationNextLink)
IQueryable<Customer> customers = null;
var parameters = new QueryParameters<Order>()
{
RequestUri = "Orders?$expand=Customer($filter=Sex eq OdataToEntity.Test.Model.Sex'Male')",
Expression = t => t.GroupJoin(customers, o => o.CustomerCountry, c => c.Country, (o, c) => new { order = o, customer = c })
.SelectMany(z => z.customer.DefaultIfEmpty(), (o, c) => new { o.order, sex = c.Sex }).Select(a => a.order),
RequestUri = "Orders?$expand=AltCustomer($filter=Sex eq OdataToEntity.Test.Model.Sex'Male')",
Expression = t => t.GroupJoin(customers.Where(c => c.Sex == Sex.Male),
o => new { Country = o.AltCustomerCountry, Id = o.AltCustomerId },
c => new { c.Country, Id = (int?)c.Id },
(o, c) => new { Order = o, Customer = c.DefaultIfEmpty() })
.SelectMany(z => z.Customer, (o, c) => new { o.Order, Customer = c }).Select(a =>
new Order()
{
AltCustomerCountry = a.Order.AltCustomerCountry,
AltCustomerId = a.Order.AltCustomerId,
Customer = a.Customer,
CustomerCountry = a.Order.CustomerCountry,
CustomerId = a.Order.CustomerId,
Date = a.Order.Date,
Id = a.Order.Id,
Name = a.Order.Name,
Status = a.Order.Status
}),
NavigationNextLink = navigationNextLink,
PageSize = pageSize
};
Expand Down
14 changes: 7 additions & 7 deletions test/OdataToEntity.Test/Common/TestContractResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ internal sealed class TestContractResolver : DefaultContractResolver
{
private sealed class EmptyCollectionValueProvider : IValueProvider
{
private readonly TestContractResolver _contractResolver;
private readonly IValueProvider _defaultValueProvider;
private readonly Func<IEnumerable, IList> _lambda;

public EmptyCollectionValueProvider(IValueProvider defaultValueProvider, Func<IEnumerable, IList> lambda)
public EmptyCollectionValueProvider(TestContractResolver contractResolver, IValueProvider defaultValueProvider, Func<IEnumerable, IList> lambda)
{
_contractResolver = contractResolver;
_defaultValueProvider = defaultValueProvider;
_lambda = lambda;
}
Expand All @@ -26,7 +28,7 @@ public Object GetValue(Object target)
if (items == null)
return null;

if (_lambda == null)
if (_lambda == null || _contractResolver.DisableWhereOrder)
return items.GetEnumerator().MoveNext() ? items : null;

IList list = _lambda(items);
Expand Down Expand Up @@ -76,10 +78,6 @@ protected override JsonDictionaryContract CreateDictionaryContract(Type objectTy
dictionaryContract.ItemConverter = new FixDecimalValueConverter();
return dictionaryContract;
}
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
return base.CreateMemberValueProvider(member);
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> jproperties = base.CreateProperties(type, memberSerialization);
Expand All @@ -91,7 +89,7 @@ protected override IList<JsonProperty> CreateProperties(Type type, MemberSeriali
if (_includes.TryGetValue(clrProperty, out Func<IEnumerable, IList> lambda))
{
if (typeof(IEnumerable).IsAssignableFrom(clrProperty.PropertyType))
jproperty.ValueProvider = new EmptyCollectionValueProvider(jproperty.ValueProvider, lambda);
jproperty.ValueProvider = new EmptyCollectionValueProvider(this, jproperty.ValueProvider, lambda);
}
else
{
Expand All @@ -112,5 +110,7 @@ public static bool IsEntity(Type type)
return false;
return true;
}

public bool DisableWhereOrder { get; set; }
}
}
7 changes: 5 additions & 2 deletions test/OdataToEntity.Test/Common/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ protected override Expression VisitMember(MemberExpression node)

public static void Compare(IList fromDb, IList fromOe, IReadOnlyList<IncludeVisitor.Include> includes)
{
var contractResolver = new TestContractResolver(includes);
var settings = new JsonSerializerSettings()
{
ContractResolver = new TestContractResolver(includes),
ContractResolver = contractResolver,
DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff",
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
String jsonDb = JsonConvert.SerializeObject(fromDb, settings);
contractResolver.DisableWhereOrder = true;
String jsonOe = JsonConvert.SerializeObject(fromOe, settings);

Assert.Equal(jsonDb, jsonOe);
Expand All @@ -84,7 +86,8 @@ public static IList ExecuteDb<T, TResult>(Db.OeEntitySetAdapterCollection entity
var visitor = new QueryVisitor<T>(entitySetAdapters, dataContext);
Expression call = visitor.Visit(expression.Body);

var includeVisitor = new IncludeVisitor();
var metadataProvider = new OdataToEntity.EfCore.OeEfCoreEdmModelMetadataProvider(dataContext.Model);
var includeVisitor = new IncludeVisitor(metadataProvider);
call = includeVisitor.Visit(call);
includes = includeVisitor.Includes;

Expand Down

0 comments on commit 34aeda7

Please sign in to comment.