Skip to content

Commit

Permalink
add support nested $cound
Browse files Browse the repository at this point in the history
fix nested $select
  • Loading branch information
voronov-maxim committed Aug 18, 2018
1 parent 00fa3a6 commit 35e1f2a
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 88 deletions.
2 changes: 1 addition & 1 deletion source/OdataToEntity.AspNetCore/ODataResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private IEnumerable<KeyValuePair<String, Object>> GetKeys(T entity)
Expression<Func<T, Object>> getValueLambda = Expression.Lambda<Func<T, Object>>(body, visitor.Parameter);
Object value = getValueLambda.Compile()(entity);

yield return new KeyValuePair<String, Object>(OeSkipTokenParser.GetPropertyName(propertyExpression), value);
yield return new KeyValuePair<String, Object>(OeSkipTokenParser.GetPropertyName((PropertyInfo)propertyExpression.Member), value);

orderByClause = orderByClause.ThenBy;
}
Expand Down
38 changes: 37 additions & 1 deletion source/OdataToEntity/Parsers/OeEntryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,28 @@ namespace OdataToEntity.Parsers
{
public sealed class OeEntryFactory
{
private sealed class AccessorByNameComparer : IComparer<OePropertyAccessor>
{
public static readonly AccessorByNameComparer Instance = new AccessorByNameComparer();

private AccessorByNameComparer()
{
}

public int Compare(OePropertyAccessor x, OePropertyAccessor y)
{
return String.CompareOrdinal(x.EdmProperty.Name, y.EdmProperty.Name);
}
}

private readonly OePropertyAccessor[] _allAccessors;
private readonly String _typeName;

private OeEntryFactory(IEdmEntitySet entitySet, OePropertyAccessor[] accessors)
{
Array.Sort(accessors, AccessorByNameComparer.Instance);
EntitySet = entitySet;
_allAccessors = accessors;
Accessors = GetAccessorsWithoutSkiptoken(accessors);

EntityType = entitySet.EntityType();
Expand Down Expand Up @@ -73,6 +90,25 @@ public static OeEntryFactory CreateEntryFactoryNested(IEdmEntitySet entitySet, O
{
return new OeEntryFactory(entitySet, accessors, resourceInfo, navigationLinks, linkAccessor);
}
public ref OePropertyAccessor GetAccessorByName(String propertyName)
{
int left = 0;
int right = _allAccessors.Length - 1;
while (left <= right)
{
int middle = left + ((right - left) >> 1);
int i = String.Compare(_allAccessors[middle].EdmProperty.Name, propertyName, StringComparison.OrdinalIgnoreCase);
if (i == 0)
return ref _allAccessors[middle];

if (i < 0)
left = middle + 1;
else
right = middle - 1;
}

throw new InvalidOperationException("Proeprty " + propertyName + " not found in accessors");
}
private static OePropertyAccessor[] GetAccessorsWithoutSkiptoken(OePropertyAccessor[] accessors)
{
int skiptokenCount = 0;
Expand All @@ -97,7 +133,7 @@ private static List<MemberExpression> GetKeyExpressions(IEdmEntitySet entitySet,
{
bool found = false;
foreach (OePropertyAccessor accessor in accessors)
if (accessor.EdmProperty == key)
if (String.CompareOrdinal(accessor.EdmProperty.Name, key.Name) == 0)
{
propertyExpressions.Add(accessor.PropertyExpression);
found = true;
Expand Down
2 changes: 1 addition & 1 deletion source/OdataToEntity/Parsers/OeExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public Expression ApplySelect(Expression source, OeQueryContext queryContext)
if (queryContext.ODataUri.Path.LastSegment is CountSegment)
return source;

var selectTranslator = new Translators.OeSelectTranslator(_joinBuilder, queryContext.ODataUri.Path);
var selectTranslator = new Translators.OeSelectTranslator(_joinBuilder, queryContext.ODataUri.Path, queryContext.MetadataLevel);
source = selectTranslator.Build(source, queryContext);
_entryFactory = selectTranslator.EntryFactory;

Expand Down
9 changes: 2 additions & 7 deletions source/OdataToEntity/Parsers/OeSkipTokenParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,9 @@ private static bool GetIsKey(IEdmEntityType edmType, IEdmStructuralProperty[] ed
}
return true;
}
public static String GetPropertyName(Expression expression)
public static String GetPropertyName(PropertyInfo clrProperty)
{
MemberExpression propertyExpression;
if (expression is UnaryExpression unaryExpression)
propertyExpression = (MemberExpression)unaryExpression.Operand;
else
propertyExpression = (MemberExpression)expression;
return propertyExpression.Member.DeclaringType.Name + "_" + propertyExpression.Member.Name;
return clrProperty.DeclaringType.Name + "_" + clrProperty.Name;
}
public static String GetPropertyName(IEdmProperty edmProperty)
{
Expand Down
16 changes: 9 additions & 7 deletions source/OdataToEntity/Parsers/Translators/OeSelectTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ protected override Expression VisitParameter(ParameterExpression node)
}
}

private OeEntryFactory _entryFactory;
private readonly OeJoinBuilder _joinBuilder;
private readonly OeMetadataLevel _metadataLevel;
private readonly OeSelectItem _navigationItem;
private readonly OeQueryNodeVisitor _visitor;
private OeEntryFactory _entryFactory;

public OeSelectTranslator(OeJoinBuilder joinBuilder, ODataPath path) :
public OeSelectTranslator(OeJoinBuilder joinBuilder, ODataPath path, OeMetadataLevel metadataLevel) :
this(joinBuilder, new OeSelectItem(path))
{
_metadataLevel = metadataLevel;
}
internal OeSelectTranslator(OeJoinBuilder joinBuilder, OeSelectItem navigationItem)
{
Expand All @@ -58,9 +60,9 @@ internal OeSelectTranslator(OeJoinBuilder joinBuilder, OeSelectItem navigationIt
_visitor = joinBuilder.Visitor;
}

private void AddKey(IEdmEntityType edmEntityType, bool skipToken)
private void AddKey(bool skipToken)
{
foreach (IEdmStructuralProperty keyProperty in edmEntityType.Key())
foreach (IEdmStructuralProperty keyProperty in _navigationItem.EntitySet.EntityType().Key())
_navigationItem.AddSelectItem(new OeSelectItem(keyProperty, skipToken));
}
private static bool HasSelectItems(SelectExpandClause selectExpandClause)
Expand All @@ -85,9 +87,6 @@ public Expression Build(Expression source, OeQueryContext queryContext)
{
isBuild = true;
source = BuildSelect(queryContext.ODataUri.SelectAndExpand, source, queryContext.NavigationNextLink, false);

if (_navigationItem.SelectItems.Count > 0)
AddKey(queryContext.EntitySet.EntityType(), queryContext.MetadataLevel != OeMetadataLevel.Full);
}

if (queryContext.ODataUri.Compute != null)
Expand Down Expand Up @@ -203,6 +202,9 @@ public Expression BuildSelect(SelectExpandClause selectClause, Expression source
}
}

if (_navigationItem.SelectItems.Count > 0)
AddKey(_metadataLevel != OeMetadataLevel.Full);

return source;
}
private static Expression BuildSkipTakeSource(Expression source, OeQueryContext queryContext, OeSelectItem navigationItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private OrderProperty[] CreateOrderProperies(Expression source, IReadOnlyList<Oe
else
{
parameterExpression = Expression.Constant(skipTokenNameValues[i].Value, propertyExpression.Type);
_visitor.AddSkipTokenConstant(parameterExpression, OeSkipTokenParser.GetPropertyName(propertyExpression));
_visitor.AddSkipTokenConstant(parameterExpression, OeSkipTokenParser.GetPropertyName((PropertyInfo)propertyExpression.Member));
}

orderProperties[i] = new OrderProperty(propertyNode, orderBy.Direction, propertyExpression, parameterExpression);
Expand Down
122 changes: 67 additions & 55 deletions source/OdataToEntity/Writers/OeGetWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public GetWriter(OeQueryContext queryContex, ODataWriter writer)
_writer = writer;
}

private static Uri BuildNavigationNextPageLink(ODataResource entry, ExpandedNavigationSelectItem expandedNavigationSelectItem)
private static Uri BuildNavigationNextPageLink(OeEntryFactory entryFactory, ExpandedNavigationSelectItem expandedNavigationSelectItem, Object value)
{
var segment = (NavigationPropertySegment)expandedNavigationSelectItem.PathToNavigationProperty.LastSegment;
IEdmNavigationProperty navigationProperty = segment.NavigationProperty;
Expand All @@ -32,13 +32,11 @@ private static Uri BuildNavigationNextPageLink(ODataResource entry, ExpandedNavi
var keys = new List<KeyValuePair<IEdmStructuralProperty, Object>>();
IEnumerator<IEdmStructuralProperty> dependentProperties = navigationProperty.DependentProperties().GetEnumerator();
foreach (IEdmStructuralProperty key in navigationProperty.PrincipalProperties())
foreach (ODataProperty property in entry.Properties)
if (property.Name == key.Name)
{
dependentProperties.MoveNext();
keys.Add(new KeyValuePair<IEdmStructuralProperty, Object>(dependentProperties.Current, property.Value));
break;
}
{
dependentProperties.MoveNext();
Object keyValue = entryFactory.GetAccessorByName(key.Name).GetValue(value);
keys.Add(new KeyValuePair<IEdmStructuralProperty, Object>(dependentProperties.Current, keyValue));
}

ResourceRangeVariableReferenceNode refNode = OeEdmClrHelper.CreateRangeVariableReferenceNode((IEdmEntitySet)segment.NavigationSource);
BinaryOperatorNode filterExpression = OeGetParser.CreateFilterExpression(refNode, keys);
Expand Down Expand Up @@ -89,19 +87,8 @@ public async Task SerializeAsync(OeEntryFactory entryFactory, Db.OeAsyncEnumerat
while (await dbEnumerator.MoveNextAsync().ConfigureAwait(false))
{
Object value = dbEnumerator.Current;
ODataResource entry = CreateEntry(dbEnumerator.EntryFactory, value);
_writer.WriteStart(entry);

foreach (OeEntryFactory navigationLink in dbEnumerator.EntryFactory.NavigationLinks)
await WriteNavigationLink(dbEnumerator.CreateChild(navigationLink));

if (_queryContext.NavigationNextLink)
foreach (ExpandedNavigationSelectItem item in _queryContext.GetExpandedNavigationSelectItems())
WriteNavigationNextLink(entry, item);

_writer.WriteEnd();
await WriteEntry(dbEnumerator, value, _queryContext.NavigationNextLink).ConfigureAwait(false);
count++;

buffer = dbEnumerator.ClearBuffer();
}

Expand All @@ -110,49 +97,63 @@ public async Task SerializeAsync(OeEntryFactory entryFactory, Db.OeAsyncEnumerat

_writer.WriteEnd();
}
private async Task WriteNavigationLink(Db.OeDbEnumerator dbEnumerator)
private async Task WriteEagerNestedCollection(Db.OeDbEnumerator dbEnumerator)
{
_writer.WriteStart(dbEnumerator.EntryFactory.ResourceInfo);

if (dbEnumerator.EntryFactory.ResourceInfo.IsCollection.GetValueOrDefault())
var items = new List<Object>();
do
{
_writer.WriteStart(new ODataResourceSet());
do
{
Object value = dbEnumerator.Current;
if (value != null)
{
ODataResource navigationEntry = CreateEntry(dbEnumerator.EntryFactory, value);
_writer.WriteStart(navigationEntry);
foreach (OeEntryFactory navigationLink in dbEnumerator.EntryFactory.NavigationLinks)
await WriteNavigationLink(dbEnumerator.CreateChild(navigationLink)).ConfigureAwait(false);
_writer.WriteEnd();
}
}
while (await dbEnumerator.MoveNextAsync().ConfigureAwait(false));
_writer.WriteEnd();
Object value = dbEnumerator.Current;
if (value != null)
items.Add(value);
}
else
while (await dbEnumerator.MoveNextAsync().ConfigureAwait(false));

_writer.WriteStart(new ODataResourceSet() { Count = items.Count });
for (int i = 0; i < items.Count; i++)
await WriteEntry(dbEnumerator, items[i], false).ConfigureAwait(false);
_writer.WriteEnd();
}
private async Task WriteEntry(Db.OeDbEnumerator dbEnumerator, Object value, bool navigationNextLink)
{
OeEntryFactory entryFactory = dbEnumerator.EntryFactory;
ODataResource entry = CreateEntry(entryFactory, value);
_writer.WriteStart(entry);
for (int i = 0; i < entryFactory.NavigationLinks.Count; i++)
await WriteNavigationLink(dbEnumerator.CreateChild(entryFactory.NavigationLinks[i])).ConfigureAwait(false);

if (navigationNextLink)
foreach (ExpandedNavigationSelectItem item in _queryContext.GetExpandedNavigationSelectItems())
WriteNavigationNextLink(entryFactory, item, value);

_writer.WriteEnd();
}
private async Task WriteLazyNestedCollection(Db.OeDbEnumerator dbEnumerator)
{
_writer.WriteStart(new ODataResourceSet());
do
{
Object value = dbEnumerator.Current;
if (value == null)
{
_writer.WriteStart((ODataResource)null);
_writer.WriteEnd();
}
if (value != null)
await WriteEntry(dbEnumerator, value, false).ConfigureAwait(false);
}
while (await dbEnumerator.MoveNextAsync().ConfigureAwait(false));
_writer.WriteEnd();
}
private async Task WriteNavigationLink(Db.OeDbEnumerator dbEnumerator)
{
_writer.WriteStart(dbEnumerator.EntryFactory.ResourceInfo);
if (dbEnumerator.EntryFactory.ResourceInfo.IsCollection.GetValueOrDefault())
{
if (dbEnumerator.EntryFactory.CountOption.GetValueOrDefault())
await WriteEagerNestedCollection(dbEnumerator).ConfigureAwait(false);
else
{
ODataResource navigationEntry = CreateEntry(dbEnumerator.EntryFactory, value);
_writer.WriteStart(navigationEntry);
foreach (OeEntryFactory navigationLink in dbEnumerator.EntryFactory.NavigationLinks)
await WriteNavigationLink(dbEnumerator.CreateChild(navigationLink)).ConfigureAwait(false);
_writer.WriteEnd();
}
await WriteLazyNestedCollection(dbEnumerator).ConfigureAwait(false);
}

else
await WriteNestedItem(dbEnumerator);
_writer.WriteEnd();
}
private void WriteNavigationNextLink(ODataResource parentEntry, ExpandedNavigationSelectItem item)
private void WriteNavigationNextLink(OeEntryFactory entryFactory, ExpandedNavigationSelectItem item, Object value)
{
var segment = (NavigationPropertySegment)item.PathToNavigationProperty.LastSegment;
var resourceInfo = new ODataNestedResourceInfo()
Expand All @@ -163,12 +164,23 @@ private void WriteNavigationNextLink(ODataResource parentEntry, ExpandedNavigati

_writer.WriteStart(resourceInfo);

var resourceSet = new ODataResourceSet() { NextPageLink = BuildNavigationNextPageLink(parentEntry, item) };
var resourceSet = new ODataResourceSet() { NextPageLink = BuildNavigationNextPageLink(entryFactory, item, value) };
_writer.WriteStart(resourceSet);
_writer.WriteEnd();

_writer.WriteEnd();
}
private async Task WriteNestedItem(Db.OeDbEnumerator dbEnumerator)
{
Object value = dbEnumerator.Current;
if (value == null)
{
_writer.WriteStart((ODataResource)null);
_writer.WriteEnd();
}
else
await WriteEntry(dbEnumerator, value, false).ConfigureAwait(false);
}
}

public static async Task SerializeAsync(OeQueryContext queryContext, Db.OeAsyncEnumerator asyncEnumerator, String contentType, Stream stream)
Expand Down
16 changes: 16 additions & 0 deletions test/OdataToEntity.Test/Common/SelectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,22 @@ public async Task SelectName(int pageSize)
await Fixture.Execute(parameters).ConfigureAwait(false);
}
[Theory]
[InlineData(0, false)]
[InlineData(1, false)]
[InlineData(0, true)]
[InlineData(1, true)]
public async Task SelectNestedName(int pageSize, bool navigationNextLink)
{
var parameters = new QueryParameters<Order, Object>()
{
RequestUri = "Orders?$expand=Items($filter=OrderId eq 1;$select=Product)&$select=Name",
Expression = t => t.Include(o => o.Items).Select(o => new { o.Name, Items = o.Items.Where(i => i.OrderId == 1).Select(i => new { i.Product }) }),
NavigationNextLink = navigationNextLink,
PageSize = pageSize
};
await Fixture.Execute(parameters).ConfigureAwait(false);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
public async Task Table(int pageSize)
Expand Down
2 changes: 1 addition & 1 deletion test/OdataToEntity.Test/Common/SelectTest2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace OdataToEntity.Test
{
public partial class SelectTest
{
//[Fact]
[Fact]
internal async Task CountExpandNested()
{
String request = "Orders?$expand=Items($count=true)&orderby=Id";
Expand Down
Loading

0 comments on commit 35e1f2a

Please sign in to comment.