Skip to content

Commit

Permalink
Bulk Operations Handlers (#2705)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenitoInc authored Dec 19, 2022
1 parent ea2879f commit ee73b27
Show file tree
Hide file tree
Showing 44 changed files with 7,590 additions and 42 deletions.
68 changes: 68 additions & 0 deletions src/Microsoft.AspNet.OData.Shared/Common/ODataPathHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// <copyright file="ODataPathHelper.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.UriParser;

namespace Microsoft.AspNet.OData.Common
{
/// <summary>
/// Helper methods for <see cref="ODataPath"/>.
/// </summary>
internal static class ODataPathHelper
{
/// <summary>
/// Get the keys from a <see cref="KeySegment"/>.
/// </summary>
/// <param name="keySegment">The <see cref="KeySegment"/> to extract the keys.</param>
/// <returns>Dictionary of keys.</returns>
public static Dictionary<string, object> KeySegmentAsDictionary(KeySegment keySegment)
{
if (keySegment == null)
{
throw Error.ArgumentNull(nameof(keySegment));
}

return keySegment.Keys.ToDictionary(d => d.Key, d => d.Value);
}

/// <summary>
/// Get the position of the next <see cref="KeySegment"/> in a list of <see cref="ODataPathSegment"/>.
/// </summary>
/// <param name="pathSegments">List of <see cref="ODataPathSegment"/>.</param>
/// <param name="currentPosition">Current position in the list of <see cref="ODataPathSegment"/>.</param>
/// <returns>Position of the next <see cref="KeySegment"/> if it exists, or -1 otherwise.</returns>
public static int GetNextKeySegmentPosition(IReadOnlyList<ODataPathSegment> pathSegments, int currentPosition)
{
if (pathSegments == null)
{
throw Error.ArgumentNull(nameof(pathSegments));
}

if (currentPosition < 0 || currentPosition >= pathSegments.Count)
{
return -1;
}

if (pathSegments[currentPosition] is KeySegment)
{
currentPosition++;
}

for (int i = currentPosition; i < pathSegments.Count; i++)
{
if (pathSegments[i] is KeySegment)
{
return i;
}
}

return -1;
}
}
}
192 changes: 192 additions & 0 deletions src/Microsoft.AspNet.OData.Shared/DefaultEdmODataAPIHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//-----------------------------------------------------------------------------
// <copyright file="DefaultEdmODataAPIHandler.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.OData.Edm;

namespace Microsoft.AspNet.OData
{
/// <summary>
/// This is the default patch handler for non-CLR types. This class has default get, create, delete and updateRelatedObject
/// methods that are used to patch an original collection when the collection is provided.
/// </summary>
internal class DefaultEdmODataAPIHandler : EdmODataAPIHandler
{
private IEdmEntityType entityType;
private ICollection<IEdmStructuredObject> originalList;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultEdmODataAPIHandler"/> class.
/// </summary>
/// <param name="originalList">Original collection of the type which needs to be updated.</param>
/// <param name="entityType">The Edm entity type of the collection.</param>
public DefaultEdmODataAPIHandler(ICollection<IEdmStructuredObject> originalList, IEdmEntityType entityType)
{
Debug.Assert(entityType != null, "entityType != null");

this.entityType = entityType;
this.originalList = originalList ?? new List<IEdmStructuredObject>();
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public override ODataAPIResponseStatus TryGet(IDictionary<string, object> keyValues, out IEdmStructuredObject originalObject, out string errorMessage)
{
ODataAPIResponseStatus status = ODataAPIResponseStatus.Success;
errorMessage = string.Empty;
originalObject = null;

Debug.Assert(keyValues != null, "keyValues != null");

try
{
originalObject = GetFilteredItem(keyValues);

if (originalObject == null)
{
status = ODataAPIResponseStatus.NotFound;
}
}
catch (Exception ex)
{
status = ODataAPIResponseStatus.Failure;
errorMessage = ex.Message;
}

return status;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public override ODataAPIResponseStatus TryCreate(IDictionary<string, object> keyValues, out IEdmStructuredObject createdObject, out string errorMessage)
{
createdObject = null;
errorMessage = string.Empty;

try
{
createdObject = new EdmEntityObject(entityType);
originalList.Add(createdObject);

return ODataAPIResponseStatus.Success;
}
catch (Exception ex)
{
errorMessage = ex.Message;

return ODataAPIResponseStatus.Failure;
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public override ODataAPIResponseStatus TryDelete(IDictionary<string, object> keyValues, out string errorMessage)
{
errorMessage = string.Empty;

try
{
EdmStructuredObject originalObject = GetFilteredItem(keyValues);

if (originalObject != null)
{
originalList.Remove(originalObject);
}

return ODataAPIResponseStatus.Success;
}
catch (Exception ex)
{
errorMessage = ex.Message;

return ODataAPIResponseStatus.Failure;
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public override ODataAPIResponseStatus TryAddRelatedObject(IEdmStructuredObject resource, out string errorMessage)
{
errorMessage = string.Empty;

try
{
originalList.Add(resource);

return ODataAPIResponseStatus.Success;
}
catch (Exception ex)
{
errorMessage = ex.Message;

return ODataAPIResponseStatus.Failure;
}
}

public override EdmODataAPIHandler GetNestedHandler(IEdmStructuredObject parent, string navigationPropertyName)
{
IEdmNavigationProperty navProperty = entityType.NavigationProperties().FirstOrDefault(navProp => navProp.Name == navigationPropertyName);

if (navProperty == null)
{
return null;
}

IEdmEntityType nestedEntityType = navProperty.ToEntityType();

object obj;

if (parent.TryGetPropertyValue(navigationPropertyName, out obj))
{
ICollection<IEdmStructuredObject> nestedList = obj as ICollection<IEdmStructuredObject>;

return new DefaultEdmODataAPIHandler(nestedList, nestedEntityType);
}

return null;
}

/// <summary>
/// Filter the object based on the set of keys.
/// </summary>
/// <param name="keyValues">Key-value pairs for the object keys.</param>
/// <returns>The filtered object.</returns>
/// <remarks>There will only be very few key elements usually, mostly 1, so performance wont be impacted.</remarks>
private EdmStructuredObject GetFilteredItem(IDictionary<string, object> keyValues)
{
if (originalList.Count == 0)
{
return null;
}

foreach (EdmStructuredObject item in originalList)
{
bool isMatch = true;

foreach (KeyValuePair<string, object> keyValue in keyValues)
{
object value;
if (item.TryGetPropertyValue(keyValue.Key, out value))
{
if (!Equals(value, keyValue.Value))
{
// Not a match, so try the next one
isMatch = false;
break;
}
}
}

if (isMatch)
{
return item;
}
}

return null;
}
}
}
Loading

0 comments on commit ee73b27

Please sign in to comment.