Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/deep insert request #2653

Merged
merged 17 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Microsoft.OData.Client/BulkUpdateSaveResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal class BulkUpdateSaveResult : BaseSaveResult
private ODataWriterWrapper writer;

/// <summary>
/// Instance of the BulkUpdateGraph that contains descriptors with their
/// Instance of the BulkUpdateGraph that contains descriptors with their
/// related descriptors to be used in writing bulk update requests.
/// </summary>
private readonly BulkUpdateGraph bulkUpdateGraph;
Expand Down Expand Up @@ -81,7 +81,7 @@ internal BulkUpdateSaveResult(DataServiceContext context, string method, SaveCha
}

/// <summary>
/// Returns an instance of the <see cref="BulkUpdateGraph"/>
/// Returns an instance of the <see cref="Client.BulkUpdateGraph"/>
/// </summary>
internal BulkUpdateGraph BulkUpdateGraph
{
Expand Down Expand Up @@ -236,7 +236,7 @@ internal void BuildDescriptorGraph<T>(IEnumerable<Descriptor> descriptors, bool
foreach (object obj in objects)
{
EntityDescriptor topLevelDescriptor = this.RequestInfo.EntityTracker.GetEntityDescriptor(obj);

// If we do not find an EntityDescriptor in the entitytracker
// for any of the provided objects then we throw an exception.
if (topLevelDescriptor == null)
Expand All @@ -248,7 +248,7 @@ internal void BuildDescriptorGraph<T>(IEnumerable<Descriptor> descriptors, bool
// meaning that it was sent as top-level object by the user
// then we add it to graph's top-level objects.
if (isRootObject)
{
{
bulkUpdateGraph.AddTopLevelDescriptor(topLevelDescriptor);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Client/ClientEdmModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ private IEdmProperty CreateEdmProperty(IEdmStructuredType declaringType, Propert
{
if (declaringType as IEdmEntityType == null && declaringType as IEdmComplexType == null)
{
throw c.Error.InvalidOperation(c.Strings.ClientTypeCache_NonEntityTypeOrNonComplexTypeCannotContainEntityProperties(propertyInfo.Name, propertyInfo.DeclaringType.ToString()));
throw c.Error.InvalidOperation(c.Strings.ClientTypeCache_NonEntityTypeCannotContainEntityProperties(propertyInfo.Name, propertyInfo.DeclaringType.ToString()));
}

// Create a navigation property representing one side of an association.
Expand Down
146 changes: 146 additions & 0 deletions src/Microsoft.OData.Client/DataServiceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,72 @@ public virtual void SetLink(object source, string sourceProperty, object target)
}
}

/// <summary>Adds the specified link to the set of objects the <see cref="Microsoft.OData.Client.DataServiceContext" /> is tracking.</summary>
/// <param name="source">The source object for the new link.</param>
/// <param name="sourceProperty">The name of the navigation property on the source object that returns the related object.</param>
/// <param name="target">he object related to the source object by the new link.</param>
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
/// <exception cref="System.ArgumentNullException">When <paramref name="source" />, <paramref name="sourceProperty" /> or <paramref name="target" /> are null.</exception>
/// <exception cref="System.InvalidOperationException">When the specified link already exists.-or-When the objects supplied as <paramref name="source" /> or <paramref name="target" /> are in the <see cref="Microsoft.OData.Client.EntityStates.Detached" /> or <see cref="Microsoft.OData.Client.EntityStates.Deleted" /> state.-or-When <paramref name="sourceProperty" /> is not a navigation property that defines a reference to a single related object.</exception>
/// <remarks>
/// The sourceProperty MUST be a single-value navigation property.
/// </remarks>
public virtual void SetRelatedObjectLink(object source, string sourceProperty, object target)
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
{
Util.CheckArgumentNull(source, "source");
Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty");
Util.CheckArgumentNull(target, "target");
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved

// Validate that the source is an entity and is already tracked by the context.
ValidateEntityType(source, this.Model);

EntityDescriptor sourceResource = this.entityTracker.GetEntityDescriptor(source);

// Check for deleted source entity
if (sourceResource.State == EntityStates.Deleted)
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
{
throw Error.InvalidOperation(Strings.Context_SetRelatedObjectLinkSourceDeleted);
}

// Validate that the property is valid and exists on the source
ClientTypeAnnotation type = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType()));
Debug.Assert(type.IsEntityType, "should be an entity type");

// will throw InvalidOperationException if property doesn't exist
ClientPropertyAnnotation property = type.GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException);

if (property.IsKnownType || property.IsEntityCollection)
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
{
throw Error.InvalidOperation(Strings.Context_SetRelatedObjectLinkNonCollectionOnly);
}

// if (property.IsEntityCollection) then property.PropertyType is the collection elementType
// either way you can only have a relation ship between keyed objects
type = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(property.EntityCollectionItemType ?? property.PropertyType));
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
Debug.Assert(type.IsEntityType, "should be enforced by just adding an object");
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved

if ((target != null) && !type.ElementType.IsInstanceOfType(target))
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
{
// target is not of the correct type
throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
}

LinkDescriptor descriptor = new LinkDescriptor(source, sourceProperty, target, this.model);
this.entityTracker.AddLink(descriptor);
descriptor.State = EntityStates.Added;
this.entityTracker.IncrementChange(descriptor);

/*LinkDescriptor relation = this.entityTracker.DetachReferenceLink(source, sourceProperty, target, MergeOption.NoTracking);

if (relation == null)
{
relation = new LinkDescriptor(source, sourceProperty, target, this.model);
this.entityTracker.AddLink(relation);
}

relation.State = EntityStates.Added;
this.entityTracker.IncrementChange(relation);*/
}

#endregion

#region AddObject, AttachTo, DeleteObject, Detach, TryGetEntity, TryGetUri
Expand Down Expand Up @@ -2573,6 +2639,70 @@ public virtual void AddRelatedObject(object source, string sourceProperty, objec
this.entityTracker.IncrementChange(targetResource);
}

/// <summary>Adds a related object to the context and creates the link that defines the relationship between the two objects in a single request.</summary>
/// <param name="source">The parent object that is being tracked by the context.</param>
/// <param name="sourceProperty">The name of the navigation property that returns the related object based on an association between the two entities.</param>
/// <param name="target">The related object that is being added.</param>
/// /// <remarks>
/// The sourceProperty MUST be a single-value object, not a collection.
/// </remarks>
public virtual void SetRelatedObject(object source, string sourceProperty, object target)
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
{
Util.CheckArgumentNull(source, "source");
Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty");
Util.CheckArgumentNull(target, "target");

// Validate that the source is an entity and is already tracked by the context.
ValidateEntityType(source, this.Model);

EntityDescriptor sourceResource = this.entityTracker.GetEntityDescriptor(source);

// Check for deleted source entity
if (sourceResource.State == EntityStates.Deleted)
{
throw Error.InvalidOperation(Strings.Context_SetRelatedObjectSourceDeleted);
}

// Validate that the property is valid and exists on the source
ClientTypeAnnotation parentType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType()));
ClientPropertyAnnotation property = parentType.GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException);

if (property.IsKnownType || property.IsEntityCollection)
{
throw Error.InvalidOperation(Strings.Context_SetRelatedObjectNonCollectionOnly);
}

// Validate that the target is an entity
ClientTypeAnnotation childType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(target.GetType()));
ValidateEntityType(target, this.Model);

// Validate that the property type matches with the target type
ClientTypeAnnotation propertyElementType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(property.PropertyType));

if (!propertyElementType.ElementType.IsAssignableFrom(childType.ElementType))
{
// target is not of the correct type
throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
}

var targetResource = new EntityDescriptor(this.model)
{
Entity = target,
State = EntityStates.Added
};

targetResource.SetParentForInsert(sourceResource, sourceProperty);

this.EntityTracker.AddEntityDescriptor(targetResource);

// Add the link in the added state.
LinkDescriptor end = targetResource.GetRelatedEnd();
end.State = EntityStates.Added;
this.entityTracker.AddLink(end);

this.entityTracker.IncrementChange(targetResource);
}

/// <summary>Notifies the <see cref="Microsoft.OData.Client.DataServiceContext" /> to start tracking the specified resource and supplies the location of the resource within the specified resource set.</summary>
/// <param name="entitySetName">The name of the set that contains the resource.</param>
/// <param name="entity">The resource to be tracked by the <see cref="Microsoft.OData.Client.DataServiceContext" />. The resource is attached in the Unchanged state.</param>
Expand Down Expand Up @@ -2852,6 +2982,22 @@ public virtual bool TryGetUri(object entity, out Uri identity)

return identity != null;
}

/// <summary>
/// Processes deep insert requests.
KenitoInc marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <typeparam name="T">The type of top-level object to be deep inserted.</typeparam>
/// <param name="resource">The top-level object of the type to be deep inserted.</param>
internal virtual void DeepInsert<T>(T resource)
{
if (resource == null)
{
throw Error.ArgumentNull(nameof(resource));
}

DeepInsertSaveResult result = new DeepInsertSaveResult(this, Util.SaveChangesMethodName, SaveChangesOptions.DeepInsert, callback: null, state: null);
result.DeepInsertRequest(resource);
}

#endregion

Expand Down
Loading