Skip to content

Commit

Permalink
fix deltaset serialization issues (#915)
Browse files Browse the repository at this point in the history
* fix deltaset serialization issues

* update based on review comments

* update based on review comments

* update based on review comments

* update based on review comments
  • Loading branch information
ElizabethOkerio authored May 23, 2023
1 parent c103a30 commit ea4e79c
Show file tree
Hide file tree
Showing 17 changed files with 460 additions and 27 deletions.
5 changes: 5 additions & 0 deletions src/Microsoft.AspNetCore.OData/Deltas/Delta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,10 @@ public override bool TryGetMember(GetMemberBinder binder, out object result)
/// enumeration of Property Names
/// </summary>
public abstract IEnumerable<string> GetUnchangedPropertyNames();

/// <summary>
/// Gets the nested resources changed at this level.
/// </summary>
public abstract IDictionary<string, object> GetDeltaNestedNavigationProperties();
}
}
8 changes: 8 additions & 0 deletions src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ public override IEnumerable<string> GetUnchangedPropertyNames()
return _updatableProperties.Intersect(_allProperties.Keys).Except(GetChangedPropertyNames());
}

/// <summary>
/// Gets the nested resources changed at this level.
/// </summary>
public override IDictionary<string, object> GetDeltaNestedNavigationProperties()
{
return _deltaNestedResources;
}

/// <summary>
/// Copies the changed property values from the underlying entity (accessible via <see cref="GetInstance()" />)
/// to the <paramref name="original"/> entity recursively.
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.AspNetCore.OData/Deltas/IDelta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface IDelta : IDeltaSetItem
/// </summary>
IEnumerable<string> GetUnchangedPropertyNames();

/// <summary>
/// Gets the nested resources changed at this level.
/// </summary>
IDictionary<string, object> GetDeltaNestedNavigationProperties();

/// <summary>
/// Attempts to set the Property called <paramref name="name"/> to the <paramref name="value"/> specified.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,17 @@ internal static async Task WriteToStreamAsync(

ODataMessageWriterSettings writerSettings = request.GetWriterSettings();
writerSettings.BaseUri = baseAddress;
writerSettings.Version = version;

//use v401 to write delta payloads.
if (serializer.ODataPayloadKind == ODataPayloadKind.Delta)
{
writerSettings.Version = ODataVersion.V401;
}
else
{
writerSettings.Version = version;
}

writerSettings.Validations = writerSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType;

string metadataLink = request.CreateODataLink(MetadataSegment.Instance);
Expand Down Expand Up @@ -142,6 +152,7 @@ internal static async Task WriteToStreamAsync(
writeContext.MetadataLevel = metadataLevel;
writeContext.QueryOptions = queryOptions;
writeContext.SetComputedProperties(queryOptions?.Compute?.ComputeClause);
writeContext.Type = type;

//Set the SelectExpandClause on the context if it was explicitly specified.
if (selectExpandDifferentFromQueryOptions != null)
Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.AspNetCore.OData/Formatter/ResourceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Diagnostics.Contracts;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.OData.Common;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Edm;
using Microsoft.AspNetCore.OData.Formatter.Deserialization;
using Microsoft.AspNetCore.OData.Formatter.Serialization;
Expand Down Expand Up @@ -159,7 +160,11 @@ public object GetPropertyValue(string propertyName)
throw Error.InvalidOperation(SRResources.EdmObjectNull, typeof(ResourceContext).Name);
}

object value;
if (SerializerContext.IsDeltaOfT && ResourceInstance is IDelta delta && delta.TryGetPropertyValue(propertyName, out object value))
{
return value;
}

if (EdmObject.TryGetPropertyValue(propertyName, out value))
{
return value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.AspNetCore.OData.Common;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OData.Deltas;
using System.Xml.Linq;

namespace Microsoft.AspNetCore.OData.Formatter.Serialization
{
Expand Down Expand Up @@ -142,13 +143,7 @@ private async Task WriteDeltaResourceAsync(object graph, ODataWriter writer, ODa
{
await writer.WriteStartAsync(resource).ConfigureAwait(false);
await WriteDeltaComplexPropertiesAsync(selectExpandNode, resourceContext, writer).ConfigureAwait(false);
//TODO: Need to add support to write Navigation Links, etc. using Delta Writer
//https://github.com/OData/odata.net/issues/155
//CLEANUP: merge delta logic with regular logic; requires common base between ODataWriter and ODataDeltaWriter
//WriteDynamicComplexProperties(resourceContext, writer);
//WriteNavigationLinks(selectExpandNode.SelectedNavigationProperties, resourceContext, writer);
//WriteExpandedNavigationProperties(selectExpandNode.ExpandedNavigationProperties, resourceContext, writer);

await WriteDeltaNavigationPropertiesAsync(selectExpandNode, resourceContext, writer);
await writer.WriteEndAsync().ConfigureAwait(false);
}
}
Expand All @@ -168,10 +163,11 @@ private async Task WriteDeltaComplexPropertiesAsync(SelectExpandNode selectExpan

if (null != resourceContext.EdmObject && resourceContext.EdmObject.IsDeltaResource())
{
IDelta deltaObject = resourceContext.EdmObject as IDelta;
IEnumerable<string> changedProperties = deltaObject.GetChangedPropertyNames();

complexProperties = complexProperties.Where(p => changedProperties.Contains(p.Name));
if (resourceContext.EdmObject is TypedEdmEntityObject obj && obj.Instance is IDelta deltaObject)
{
IEnumerable<string> changedProperties = deltaObject.GetChangedPropertyNames();
complexProperties = complexProperties.Where(p => changedProperties.Contains(p.Name));
}
}

foreach (IEdmStructuralProperty complexProperty in complexProperties)
Expand All @@ -189,8 +185,12 @@ await WriteDeltaComplexAndExpandedNavigationPropertyAsync(complexProperty, null,
}
}

private async Task WriteDeltaComplexAndExpandedNavigationPropertyAsync(IEdmProperty edmProperty, SelectExpandClause selectExpandClause,
ResourceContext resourceContext, ODataWriter writer)
private async Task WriteDeltaComplexAndExpandedNavigationPropertyAsync(
IEdmProperty edmProperty,
SelectExpandClause selectExpandClause,
ResourceContext resourceContext,
ODataWriter writer,
Type navigationPropertyType = null)
{
Contract.Assert(edmProperty != null);
Contract.Assert(resourceContext != null);
Expand Down Expand Up @@ -223,7 +223,7 @@ await writer.WriteStartAsync(new ODataResourceSet
{
// create the serializer context for the complex and expanded item.
ODataSerializerContext nestedWriteContext = new ODataSerializerContext(resourceContext, selectExpandClause, edmProperty);

nestedWriteContext.Type = navigationPropertyType;
// write object.

// TODO: enable overriding serializer based on type. Currently requires serializer supports WriteDeltaObjectinline, because it takes an ODataDeltaWriter
Expand All @@ -236,14 +236,79 @@ await writer.WriteStartAsync(new ODataResourceSet
if (edmProperty.Type.IsCollection())
{
ODataDeltaResourceSetSerializer serializer = new ODataDeltaResourceSetSerializer(SerializerProvider);
await serializer.WriteObjectInlineAsync(propertyValue, edmProperty.Type, writer, nestedWriteContext)
.ConfigureAwait(false);
await serializer.WriteObjectInlineAsync(propertyValue, edmProperty.Type, writer, nestedWriteContext).ConfigureAwait(false);
}
else
{
ODataResourceSerializer serializer = new ODataResourceSerializer(SerializerProvider);
await serializer.WriteDeltaObjectInlineAsync(propertyValue, edmProperty.Type, writer, nestedWriteContext)
.ConfigureAwait(false);
await serializer.WriteDeltaObjectInlineAsync(propertyValue, edmProperty.Type, writer, nestedWriteContext).ConfigureAwait(false);
}
}
}

/// <summary>
/// Writes delta navigation properties asynchronously.
/// </summary>
/// <param name="selectExpandNode">Contains the set of properties and actions to use to select and expand while writing an entity.</param>
/// <param name="resourceContext">The resource context for the resource being written.</param>
/// <param name="writer">The ODataWriter.</param>
/// <returns>A task that represents the asynchronous write operation</returns>
internal async Task WriteDeltaNavigationPropertiesAsync(SelectExpandNode selectExpandNode, ResourceContext resourceContext, ODataWriter writer)
{
Contract.Assert(resourceContext != null, "The ResourceContext cannot be null");
Contract.Assert(writer != null, "The ODataWriter cannot be null");

IEnumerable<KeyValuePair<IEdmNavigationProperty, Type>> navigationProperties = GetNavigationPropertiesToWrite(selectExpandNode, resourceContext);

foreach (KeyValuePair<IEdmNavigationProperty, Type> navigationProperty in navigationProperties)
{
ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo
{
IsCollection = navigationProperty.Key.Type.IsCollection(),
Name = navigationProperty.Key.Name
};

await writer.WriteStartAsync(nestedResourceInfo).ConfigureAwait(false);
await WriteDeltaComplexAndExpandedNavigationPropertyAsync(navigationProperty.Key, null, resourceContext, writer, navigationProperty.Value).ConfigureAwait(false);
await writer.WriteEndAsync().ConfigureAwait(false);
}
}

private IEnumerable<KeyValuePair<IEdmNavigationProperty, Type>> GetNavigationPropertiesToWrite(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
ISet<IEdmNavigationProperty> navigationProperties = selectExpandNode.SelectedNavigationProperties;

if (navigationProperties == null)
{
yield break;
}

if (resourceContext.EdmObject is IDelta changedObject)
{
IEnumerable<string> changedProperties = changedObject.GetChangedPropertyNames();

foreach (IEdmNavigationProperty navigationProperty in navigationProperties)
{
if (changedProperties != null && changedProperties.Contains(navigationProperty.Name))
{
yield return new KeyValuePair<IEdmNavigationProperty, Type>(navigationProperty, typeof(IEdmChangedObject));
}
}
}
else if (resourceContext.ResourceInstance is IDelta deltaObject)
{
IEnumerable<string> changedProperties = deltaObject.GetChangedPropertyNames();
IDictionary<string, object> deltaNestedProperties = deltaObject.GetDeltaNestedNavigationProperties();

foreach (IEdmNavigationProperty navigationProperty in navigationProperties)
{
if (changedProperties != null && changedProperties.Contains(navigationProperty.Name) && deltaNestedProperties.TryGetValue(navigationProperty.Name, out object obj))
{
if (obj != null)
{
yield return new KeyValuePair<IEdmNavigationProperty, Type>(navigationProperty, obj.GetType());
}
}
}
}
}
Expand Down Expand Up @@ -1148,9 +1213,11 @@ private IEnumerable<ODataProperty> CreateStructuralPropertyBag(SelectExpandNode

if (null != resourceContext.EdmObject && resourceContext.EdmObject.IsDeltaResource())
{
IDelta deltaObject = resourceContext.EdmObject as IDelta;
IEnumerable<string> changedProperties = deltaObject.GetChangedPropertyNames();
structuralProperties = structuralProperties.Where(p => changedProperties.Contains(p.Name));
if (resourceContext.EdmObject is TypedEdmEntityObject obj && obj.Instance is IDelta deltaObject)
{
IEnumerable<string> changedProperties = deltaObject.GetChangedPropertyNames();
structuralProperties = structuralProperties.Where(p => changedProperties.Contains(p.Name));
}
}

foreach (IEdmStructuralProperty structuralProperty in structuralProperties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.AspNetCore.OData.Edm;
using Microsoft.AspNetCore.OData.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Common;

namespace Microsoft.AspNetCore.OData.Formatter.Serialization
Expand All @@ -29,6 +30,8 @@ public class ODataSerializerContext
private ODataQueryContext _queryContext;
private SelectExpandClause _selectExpandClause;
private bool _isSelectExpandClauseSet;
internal Type Type { get; set; }
private bool? _isDeltaOfT;

/// <summary>
/// Initializes a new instance of the <see cref="ODataSerializerContext"/> class.
Expand Down Expand Up @@ -81,6 +84,7 @@ internal ODataSerializerContext(ResourceContext resource, IEdmProperty edmProper
Items = context.Items;
ExpandReference = context.ExpandReference;
TimeZone = context.TimeZone;
Type = context.Type;

QueryContext = queryContext;

Expand Down Expand Up @@ -290,6 +294,20 @@ public SelectExpandClause SelectExpandClause
}
}

internal bool IsDeltaOfT
{
get
{
if (_isDeltaOfT == null)
{
_isDeltaOfT = Type != null && Type.IsGenericType && (Type.GetGenericTypeDefinition() == typeof(Delta<>) ||
Type.GetGenericTypeDefinition() == typeof(DeltaSet<>) || Type.GetGenericTypeDefinition() == typeof(DeltaDeletedResource<>));
}

return _isDeltaOfT.Value;
}
}

/// <summary>
/// Gets or sets the <see cref="ExpandedNavigationSelectItem"/>.
/// </summary>
Expand Down Expand Up @@ -328,6 +346,11 @@ internal IEdmTypeReference GetEdmType(object instance, Type type, bool isUntyped
}
else
{
if (typeof(IDeltaSet).IsAssignableFrom(type))
{
return Model.GetEdmTypeReference(type);
}

if (Model == null && !isUntyped)
{
throw Error.InvalidOperation(SRResources.RequestMustHaveModel);
Expand All @@ -339,7 +362,11 @@ internal IEdmTypeReference GetEdmType(object instance, Type type, bool isUntyped

if (edmType == null)
{
if (instance != null)
if (instance is ITypedDelta delta)
{
edmType = Model.GetEdmTypeReference(delta.ExpectedClrType);
}
else
{
edmType = Model.GetEdmTypeReference(instance.GetType());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ public override IEnumerable<string> GetChangedPropertyNames()
return _setProperties;
}

/// <inheritdoc/>
public override IDictionary<string, object> GetDeltaNestedNavigationProperties()
{
return default;
}

/// <inheritdoc/>
public override IEnumerable<string> GetUnchangedPropertyNames()
{
Expand Down
27 changes: 27 additions & 0 deletions src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,11 @@
enumeration of Property Names
</summary>
</member>
<member name="M:Microsoft.AspNetCore.OData.Deltas.Delta.GetDeltaNestedNavigationProperties">
<summary>
Gets the nested resources changed at this level.
</summary>
</member>
<member name="T:Microsoft.AspNetCore.OData.Deltas.DeltaDeletedLink`1">
<summary>
<see cref="T:Microsoft.AspNetCore.OData.Deltas.DeltaDeletedLink`1" /> allows and tracks changes to delta deleted link.
Expand Down Expand Up @@ -1694,6 +1699,11 @@
properties.
</summary>
</member>
<member name="M:Microsoft.AspNetCore.OData.Deltas.Delta`1.GetDeltaNestedNavigationProperties">
<summary>
Gets the nested resources changed at this level.
</summary>
</member>
<member name="M:Microsoft.AspNetCore.OData.Deltas.Delta`1.CopyChangedValues(`0)">
<summary>
Copies the changed property values from the underlying entity (accessible via <see cref="M:Microsoft.AspNetCore.OData.Deltas.Delta`1.GetInstance" />)
Expand Down Expand Up @@ -1763,6 +1773,11 @@
enumerable of Property Names
</summary>
</member>
<member name="M:Microsoft.AspNetCore.OData.Deltas.IDelta.GetDeltaNestedNavigationProperties">
<summary>
Gets the nested resources changed at this level.
</summary>
</member>
<member name="M:Microsoft.AspNetCore.OData.Deltas.IDelta.TrySetPropertyValue(System.String,System.Object)">
<summary>
Attempts to set the Property called <paramref name="name"/> to the <paramref name="value"/> specified.
Expand Down Expand Up @@ -4684,6 +4699,15 @@
<param name="writer">The <see cref="T:Microsoft.OData.ODataDeltaWriter" /> to be used for writing.</param>
<param name="writeContext">The <see cref="T:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext"/>.</param>
</member>
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteDeltaNavigationPropertiesAsync(Microsoft.AspNetCore.OData.Formatter.Serialization.SelectExpandNode,Microsoft.AspNetCore.OData.Formatter.ResourceContext,Microsoft.OData.ODataWriter)">
<summary>
Writes delta navigation properties asynchronously.
</summary>
<param name="selectExpandNode">Contains the set of properties and actions to use to select and expand while writing an entity.</param>
<param name="resourceContext">The resource context for the resource being written.</param>
<param name="writer">The ODataWriter.</param>
<returns>A task that represents the asynchronous write operation</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.CreateSelectExpandNode(Microsoft.AspNetCore.OData.Formatter.ResourceContext)">
<summary>
Creates the <see cref="T:Microsoft.AspNetCore.OData.Formatter.Serialization.SelectExpandNode"/> that describes the set of properties and actions to select and expand while writing this entity.
Expand Down Expand Up @@ -5759,6 +5783,9 @@
<member name="M:Microsoft.AspNetCore.OData.Formatter.Value.EdmStructuredObject.GetChangedPropertyNames">
<inheritdoc/>
</member>
<member name="M:Microsoft.AspNetCore.OData.Formatter.Value.EdmStructuredObject.GetDeltaNestedNavigationProperties">
<inheritdoc/>
</member>
<member name="M:Microsoft.AspNetCore.OData.Formatter.Value.EdmStructuredObject.GetUnchangedPropertyNames">
<inheritdoc/>
</member>
Expand Down
Loading

0 comments on commit ea4e79c

Please sign in to comment.