/// <summary> /// Submits the specified <see cref="EntityChangeSet"/> to the DomainService asynchronously. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to submit to the DomainService.</param> /// <param name="cancellationToken"><see cref="CancellationToken"/> which may be used to request cancellation</param> /// <returns>The results returned by the submit request.</returns> public Task <SubmitCompletedResult> SubmitAsync(EntityChangeSet changeSet, CancellationToken cancellationToken) { if (changeSet == null) { throw new ArgumentNullException(nameof(changeSet)); } if (changeSet.IsEmpty) { throw new InvalidOperationException(OpenRiaServices.DomainServices.Client.Resource.DomainClient_EmptyChangeSet); } // call the actual implementation var submitTask = SubmitAsyncCore(changeSet, cancellationToken); return(submitTask.ContinueWith(res => { var submitResults = res.GetAwaiter().GetResult(); // correlate the operation results back to their actual client entity references Dictionary <int, Entity> submittedEntities = submitResults.ChangeSet.GetChangeSetEntries().ToDictionary(p => p.Id, p => p.Entity); foreach (ChangeSetEntry op in submitResults.Results) { op.ClientEntity = submittedEntities[op.Id]; } return submitResults; } , CancellationToken.None , TaskContinuationOptions.NotOnCanceled , TaskScheduler.Default)); }
/// <summary> /// Submit the specified <see cref="EntityChangeSet"/> to the DomainService, with the results of the operation /// being returned on the SubmitCompleted event args. /// </summary> /// <param name="changeSet">The changeset to submit. If the changeset is empty, an <see cref="InvalidOperationException"/> will /// be thrown.</param> /// <param name="cancellationToken"><see cref="CancellationToken"/> to be used for requesting cancellation</param> /// <returns>The results returned by the submit request.</returns> /// <exception cref="InvalidOperationException">The changeset is empty.</exception> protected override Task <SubmitCompletedResult> SubmitAsyncCore(EntityChangeSet changeSet, CancellationToken cancellationToken) { IEnumerable <ChangeSetEntry> submitOperations = changeSet.GetChangeSetEntries(); TContract channel = this.ChannelFactory.CreateChannel(); return(CallServiceOperation <SubmitCompletedResult>(channel, "SubmitChanges", new Dictionary <string, object>() { { "changeSet", submitOperations } }, (state, asyncResult) => { try { var returnValue = (IEnumerable <ChangeSetEntry>)EndServiceOperationCall(state, asyncResult); return new SubmitCompletedResult(changeSet, returnValue ?? Enumerable.Empty <ChangeSetEntry>()); } catch (FaultException <DomainServiceFault> fe) { throw WebDomainClient <TContract> .GetExceptionFromServiceFault(fe.Detail); } }, cancellationToken)); }
/// <summary> /// Gets the results of a submit. /// </summary> /// <param name="asyncResult">An asynchronous result that identifies a submit.</param> /// <returns>The results returned by the submit.</returns> protected sealed override SubmitCompletedResult EndSubmitCore(IAsyncResult asyncResult) { WebDomainClientAsyncResult <TContract> wcfAsyncResult = this.EndAsyncResult(asyncResult, AsyncOperationType.Submit, /* cancel */ false); MethodInfo endSubmitMethod = wcfAsyncResult.EndOperationMethod; EntityChangeSet changeSet = wcfAsyncResult.EntityChangeSet; IEnumerable <ChangeSetEntry> returnValue; try { try { returnValue = (IEnumerable <ChangeSetEntry>)endSubmitMethod.Invoke(wcfAsyncResult.Channel, new object[] { wcfAsyncResult.InnerAsyncResult }); } catch (TargetInvocationException tie) { if (tie.InnerException != null) { throw tie.InnerException; } throw; } finally { ((IChannel)wcfAsyncResult.Channel).Close(); } } catch (FaultException <DomainServiceFault> fe) { throw WebDomainClient <TContract> .GetExceptionFromServiceFault(fe.Detail); } return(new SubmitCompletedResult(changeSet, returnValue ?? Enumerable.Empty <ChangeSetEntry>())); }
/// <summary> /// Returns an <see cref="EntityChangeSet"/> containing the current set of pending changes /// </summary> /// <returns>The current set of pending changes</returns> public EntityChangeSet GetChanges() { List <Entity> addedEntities = new List <Entity>(); List <Entity> modifiedEntities = new List <Entity>(); List <Entity> removedEntities = new List <Entity>(); foreach (KeyValuePair <Type, EntitySet> entitySetItem in this._entitySets) { EntitySet set = entitySetItem.Value; foreach (Entity entity in set.InterestingEntities) { if (entity.EntityState == EntityState.New) { addedEntities.Add(entity); } else if (entity.EntityState == EntityState.Modified) { modifiedEntities.Add(entity); } else if (entity.EntityState == EntityState.Deleted) { removedEntities.Add(entity); } } } EntityChangeSet changeSet = new EntityChangeSet(addedEntities.AsReadOnly(), modifiedEntities.AsReadOnly(), removedEntities.AsReadOnly()); Debug.Assert(this.HasChanges == !changeSet.IsEmpty, "Invariant : these states should be in sync"); return(changeSet); }
/// <summary> /// Submits the specified <see cref="EntityChangeSet"/> to the DomainService asynchronously. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to submit to the DomainService.</param> /// <param name="callback">The callback to invoke when the submit has been executed.</param> /// <param name="userState">Optional user state associated with this operation.</param> /// <returns>An asynchronous result that identifies this submit request.</returns> public IAsyncResult BeginSubmit(EntityChangeSet changeSet, AsyncCallback callback, object userState) { if (callback == null) { throw new ArgumentNullException("callback"); } if (changeSet == null) { throw new ArgumentNullException("changeSet"); } if (changeSet.IsEmpty) { throw new InvalidOperationException(OpenRiaServices.DomainServices.Client.Resource.DomainClient_EmptyChangeSet); } DomainClientAsyncResult domainClientResult = DomainClientAsyncResult.CreateSubmitResult(this, changeSet, callback, userState); // call the actual implementation asynchronously domainClientResult.InnerAsyncResult = this.BeginSubmitCore( changeSet, delegate(IAsyncResult result) { DomainClientAsyncResult clientResult = (DomainClientAsyncResult)result.AsyncState; clientResult.InnerAsyncResult = result; clientResult.Complete(); }, domainClientResult); return(domainClientResult); }
/// <summary> /// Private constructor used by all public constructors. /// </summary> /// <param name="changeSet">the changeset being submitted</param> /// <param name="message">The localized error message</param> /// <param name="innerException">Optional inner exception.</param> /// <param name="status">status of the exception</param> /// <param name="errorCode">custom error code</param> /// <param name="stackTrace">stack trace of the exception</param> private SubmitOperationException(EntityChangeSet changeSet, string message, Exception innerException, OperationErrorStatus status, int errorCode, string stackTrace) : base(message, innerException, status, errorCode, stackTrace, GetValidationResults(changeSet)) { _changeSet = changeSet; _entitiesInError = new ReadOnlyCollection <Entity>(changeSet .Where(p => p.EntityConflict != null || p.HasValidationErrors) .ToList()); }
/// <summary> /// Initializes a new instance of the <see cref="SubmitOperation"/> class. /// </summary> /// <param name="changeSet">The changeset being submitted.</param> /// <param name="completeAction">Optional action to invoke when the operation completes.</param> /// <param name="userState">Optional user state to associate with the operation.</param> /// <param name="supportCancellation"><c>true</c> to enable <see cref="OperationBase.CancellationToken"/> to be cancelled when <see cref="OperationBase.Cancel"/> is called</param> internal SubmitOperation(EntityChangeSet changeSet, Action <SubmitOperation> completeAction, object userState, bool supportCancellation) : base(userState, supportCancellation) { if (changeSet == null) { throw new ArgumentNullException(nameof(changeSet)); } this._completeAction = completeAction; this._changeSet = changeSet; }
/// <summary> /// Initializes a new <see cref="DomainClientAsyncResult"/> instance used for Submit operations. /// </summary> /// <param name="domainClient">The associated <see cref="DomainClient"/>.</param> /// <param name="entityChangeSet">The Submit operation <see cref="EntityChangeSet"/>.</param> /// <param name="callback">Optional <see cref="AsyncCallback"/> to invoke upon completion.</param> /// <param name="asyncState">Optional user state information that will be passed to the <paramref name="callback"/>.</param> /// <exception cref="ArgumentNullException">if <paramref name="domainClient"/> is null.</exception> protected DomainClientAsyncResult(DomainClient domainClient, EntityChangeSet entityChangeSet, AsyncCallback callback, object asyncState) : base(callback, asyncState) { if (domainClient == null) { throw new ArgumentNullException("domainClient"); } this._asyncOperationType = AsyncOperationType.Submit; this._domainClient = domainClient; this._entityChangeSet = entityChangeSet; }
/// <summary> /// Initializes a new instance of the <see cref="SubmitOperation"/> class. /// </summary> /// <param name="changeSet">The changeset being submitted.</param> /// <param name="completeAction">Optional action to invoke when the operation completes.</param> /// <param name="userState">Optional user state to associate with the operation.</param> /// <param name="cancelAction">Optional action to invoke when the operation is canceled. If null, cancellation will not be supported.</param> internal SubmitOperation(EntityChangeSet changeSet, Action <SubmitOperation> completeAction, object userState, Action <SubmitOperation> cancelAction) : base(userState) { if (changeSet == null) { throw new ArgumentNullException("changeSet"); } this._cancelAction = cancelAction; this._completeAction = completeAction; this._changeSet = changeSet; }
/// <summary> /// Initializes a new instance of the SubmitCompletedResult class /// </summary> /// <param name="changeSet">The changeset that was submitted.</param> /// <param name="operationResults">The <see cref="ChangeSetEntry"/> results sent back from the /// DomainService for the submit operation.</param> public SubmitCompletedResult(EntityChangeSet changeSet, IEnumerable <ChangeSetEntry> operationResults) { if (changeSet == null) { throw new ArgumentNullException("changeSet"); } if (operationResults == null) { throw new ArgumentNullException("operationResults"); } this._changeSet = changeSet; this._operationResults = new ReadOnlyCollection <ChangeSetEntry>(operationResults.ToList()); }
/// <summary> /// Builds an operation list for the specified <see cref="EntityChangeSet"/>. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/>.</param> /// <returns>The list of <see cref="ChangeSetEntry"/> for the specified <see cref="EntityChangeSet"/>.</returns> public static List <ChangeSetEntry> Build(EntityChangeSet changeSet) { CheckForInvalidUpdates(changeSet); // translate to an operation list List <ChangeSetEntry> operations = BuildOperations(changeSet); // recursively visit all composition relationships in the // changeset and add operations for unmodified children. UnmodifiedOperationAdder.Add(operations); // set the association maps for all operations in the changeset AssociationMapBuilder.Build(operations); return(operations); }
/// <summary> /// Submit the specified <see cref="EntityChangeSet"/> to the DomainService, with the results of the operation /// being returned on the SubmitCompleted event args. /// </summary> /// <param name="changeSet">The changeset to submit. If the changeset is empty, an <see cref="InvalidOperationException"/> will /// be thrown.</param> /// <param name="callback">The callback to invoke when the submit has been executed.</param> /// <param name="userState">Optional state that will flow through to the SubmitCompleted event</param> /// <returns>An asynchronous result that identifies this submit.</returns> /// <exception cref="InvalidOperationException">The changeset is empty.</exception> /// <exception cref="InvalidOperationException">The specified query does not exist.</exception> protected sealed override IAsyncResult BeginSubmitCore(EntityChangeSet changeSet, AsyncCallback callback, object userState) { MethodInfo beginSubmitMethod = WebDomainClient <TContract> .ResolveBeginMethod("SubmitChanges"); MethodInfo endSubmitMethod = WebDomainClient <TContract> .ResolveEndMethod("SubmitChanges"); IEnumerable <ChangeSetEntry> submitOperations = changeSet.GetChangeSetEntries(); TContract channel = this.ChannelFactory.CreateChannel(); WebDomainClientAsyncResult <TContract> wcfAsyncResult = WebDomainClientAsyncResult <TContract> .CreateSubmitResult(this, channel, endSubmitMethod, changeSet, submitOperations.ToList(), callback, userState); object[] parameters = { submitOperations, new AsyncCallback(delegate(IAsyncResult asyncResponseResult) { wcfAsyncResult.InnerAsyncResult = asyncResponseResult; wcfAsyncResult.Complete(); }), userState }; IAsyncResult asyncResult; try { asyncResult = (IAsyncResult)beginSubmitMethod.Invoke(channel, parameters); } catch (TargetInvocationException tie) { if (tie.InnerException != null) { throw tie.InnerException; } throw; } if (!asyncResult.CompletedSynchronously) { wcfAsyncResult.InnerAsyncResult = asyncResult; } return(wcfAsyncResult); }
/// <summary> /// Verify that all update operations in the specified <see cref="EntityChangeSet"/> are permitted. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to check.</param> internal static void CheckForInvalidUpdates(EntityChangeSet changeSet) { AssociationUpdateChecker associationChecker = new AssociationUpdateChecker(); foreach (Entity entity in changeSet) { if (entity.EntityState == EntityState.Modified) { foreach (MetaMember member in entity.ModifiedProperties) { if (member.IsKeyMember) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.Entity_KeyMembersCannotBeChanged, member.Name, entity.GetType().Name)); } } } // search associated entities for any invalid changes associationChecker.Visit(entity); } }
/// <summary> /// Builds the list of submit operations from the current <see cref="EntityChangeSet"/>. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to process.</param> /// <returns>The list of <see cref="ChangeSetEntry"/> for the specified <see cref="EntityChangeSet"/>.</returns> private static List <ChangeSetEntry> BuildOperations(EntityChangeSet changeSet) { List <ChangeSetEntry> operations = new List <ChangeSetEntry>(); int clientID = 0; EntityOperationType operationType = EntityOperationType.None; foreach (Entity entity in changeSet) { switch (entity.EntityState) { case EntityState.New: operationType = EntityOperationType.Insert; break; case EntityState.Modified: operationType = EntityOperationType.Update; break; case EntityState.Deleted: operationType = EntityOperationType.Delete; break; default: continue; } // create the operation and apply any original values ChangeSetEntry changeSetEntry = new ChangeSetEntry(entity, clientID++, operationType); if (entity.OriginalValues != null) { if (entity.MetaType.ShouldRoundtripOriginal && entity.OriginalValues != null) { changeSetEntry.OriginalEntity = GetRoundtripEntity(entity); } else { // In cases where the entity is modified but we're not sending // an original we need to flag the entity as having changes. // For example, this happens in Timestamp scenarios. changeSetEntry.HasMemberChanges = true; } } // add any custom method invocations var entityActions = (ICollection <EntityAction>)entity.EntityActions; foreach (EntityAction customInvokation in entityActions) { if (string.IsNullOrEmpty(customInvokation.Name)) { throw new ArgumentException(Resource.DomainClient_InvocationNameCannotBeNullOrEmpty); } if (changeSetEntry.EntityActions == null) { changeSetEntry.EntityActions = new List <Serialization.KeyValue <string, object[]> >(); } changeSetEntry.EntityActions.Add( new Serialization.KeyValue <string, object[]>(customInvokation.Name, customInvokation.Parameters.ToArray())); } operations.Add(changeSetEntry); } return(operations); }
/// <summary> /// Create a IEnumerable which iterates the changeset and returns all validation errors /// </summary> /// <param name="changeSet">ChangeSet to read validation errors from</param> /// <returns></returns> private static IEnumerable <ValidationResult> GetValidationResults(EntityChangeSet changeSet) { return(changeSet.Where(p => p.EntityConflict != null || p.HasValidationErrors) .SelectMany(p => p.ValidationErrors)); }
/// <summary> /// Private constructor used by all public constructors. /// </summary> /// <param name="changeSet">the changeset being submitted</param> /// <param name="message">The localized error message</param> /// <param name="innerException">Optional inner exception.</param> /// <param name="status">status of the exception</param> /// <param name="errorCode">custom error code</param> /// <param name="stackTrace">stack trace of the exception</param> private SubmitOperationException(EntityChangeSet changeSet, string message, Exception innerException, OperationErrorStatus status, int errorCode, string stackTrace) : base(message, innerException, status, errorCode, stackTrace, GetValidationResults(changeSet)) { _changeSet = changeSet; }
/// <summary> /// Internal "copy" constructor. /// </summary> /// <param name="changeSet">the changeset being submitted</param> /// <param name="message">The new error message to use</param> /// <param name="exception">The exception to copy</param> internal SubmitOperationException(EntityChangeSet changeSet, string message, DomainOperationException exception) : this(changeSet, message, exception.InnerException, exception.Status, exception.ErrorCode, exception.StackTrace) { _changeSet = changeSet; }
/// <summary> /// Creates a new <see cref="WebDomainClientAsyncResult<TContract>"/> used for Submit operations. /// </summary> /// <param name="domainClient">The <see cref="WebDomainClient<TContract>"/> associated with this result.</param> /// <param name="channel">The channel used to communicate with the server.</param> /// <param name="endOperationMethod">The method that completes an asynchronous operation.</param> /// <param name="entityChangeSet">The Submit operation <see cref="EntityChangeSet"/>.</param> /// <param name="changeSetEntries">The collection of <see cref="ChangeSetEntry"/>s to submit.</param> /// <param name="callback">The <see cref="AsyncCallback"/> to invoke upon completion.</param> /// <param name="asyncState">Optional user state information that will be passed to the <paramref name="callback"/>.</param> /// <returns>A <see cref="WebDomainClientAsyncResult<TContract>"/> used for Submit operations</returns> public static WebDomainClientAsyncResult <TContract> CreateSubmitResult(WebDomainClient <TContract> domainClient, TContract channel, MethodInfo endOperationMethod, EntityChangeSet entityChangeSet, IEnumerable <ChangeSetEntry> changeSetEntries, AsyncCallback callback, object asyncState) { return(new WebDomainClientAsyncResult <TContract>(domainClient, channel, endOperationMethod, entityChangeSet, changeSetEntries, callback, asyncState)); }
/// <summary> /// Constructor that accepts a localized exception message and status /// </summary> /// <param name="changeSet">the changeset being submitted</param> /// <param name="message">The localized exception message</param> /// <param name="status">The status of the exception</param> public SubmitOperationException(EntityChangeSet changeSet, string message, OperationErrorStatus status) : this(changeSet, message, /*innerException*/ null, status, /*errorCode*/ 0, /*stackTrace*/ null) { _changeSet = changeSet; }
/// <summary> /// Initializes a new instance of the <see cref="SubmitResult"/> class. /// </summary> /// <param name="changeSet">The changeset which was submitted.</param> public SubmitResult(EntityChangeSet changeSet) { _changeSet = changeSet; }
/// <summary> /// Method called by the framework to asynchronously process the specified <see cref="EntityChangeSet"/>. /// Overrides should not call the base method. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to submit to the DomainService.</param> /// <param name="callback">The callback to invoke when the submit has been executed.</param> /// <param name="userState">Optional user state associated with this operation.</param> /// <returns>An asynchronous result that identifies this submit request.</returns> protected virtual IAsyncResult BeginSubmitCore(EntityChangeSet changeSet, AsyncCallback callback, object userState) { throw new NotSupportedException(); }
/// <summary> /// Method called by the framework to asynchronously process the specified <see cref="EntityChangeSet"/>. /// Overrides should not call the base method. /// </summary> /// <param name="changeSet">The <see cref="EntityChangeSet"/> to submit to the DomainService.</param> /// <param name="cancellationToken"><see cref="CancellationToken"/> which may be used to request cancellation</param> /// <returns>The results returned by the submit request.</returns> protected virtual Task <SubmitCompletedResult> SubmitAsyncCore(EntityChangeSet changeSet, CancellationToken cancellationToken) { throw new NotSupportedException(); }
/// <summary> /// Creates a new <see cref="DomainClientAsyncResult"/> used for Submit operations. /// </summary> /// <param name="domainClient">The associated <see cref="DomainClient"/>.</param> /// <param name="entityChangeSet">The Submit operation <see cref="EntityChangeSet"/>.</param> /// <param name="callback">The <see cref="AsyncCallback"/> to invoke upon completion.</param> /// <param name="asyncState">Optional user state information that will be passed to the <paramref name="callback"/>.</param> /// <returns>A <see cref="DomainClientAsyncResult"/> used for Submit operations</returns> public static DomainClientAsyncResult CreateSubmitResult(DomainClient domainClient, EntityChangeSet entityChangeSet, AsyncCallback callback, object asyncState) { return(new DomainClientAsyncResult(domainClient, entityChangeSet, callback, asyncState)); }
/// <summary> /// Constructor that accepts a localized exception message and status /// </summary> /// <param name="changeSet">the changeset being submitted</param> /// <param name="message">The localized exception message</param> /// <param name="innerException">inner exception.</param> public SubmitOperationException(EntityChangeSet changeSet, string message, Exception innerException) : this(changeSet, message, innerException, /*status*/ OperationErrorStatus.ServerError, /*errorCode*/ 0, /*stackTrace*/ null) { _changeSet = changeSet; }
/// <summary> /// Initializes a new <see cref="WebDomainClientAsyncResult<TContract>"/> instance used for Submit operations. /// </summary> /// <param name="domainClient">The <see cref="WebDomainClient<TContract>"/> associated with this result.</param> /// <param name="channel">The channel used to communicate with the server.</param> /// <param name="endOperationMethod">The method that completes an asynchronous operation.</param> /// <param name="entityChangeSet">The Submit operation <see cref="EntityChangeSet"/>.</param> /// <param name="changeSetEntries">The collection of <see cref="ChangeSetEntry"/>s to submit.</param> /// <param name="callback">Optional <see cref="AsyncCallback"/> to invoke upon completion.</param> /// <param name="asyncState">Optional user state information that will be passed to the <paramref name="callback"/>.</param> /// <exception cref="ArgumentNullException">if <paramref name="domainClient"/> is null.</exception> /// <exception cref="ArgumentNullException">if <paramref name="endOperationMethod"/> is null.</exception> private WebDomainClientAsyncResult(WebDomainClient <TContract> domainClient, TContract channel, MethodInfo endOperationMethod, EntityChangeSet entityChangeSet, IEnumerable <ChangeSetEntry> changeSetEntries, AsyncCallback callback, object asyncState) : base(domainClient, entityChangeSet, callback, asyncState) { if (channel == null) { throw new ArgumentNullException("channel"); } if (endOperationMethod == null) { throw new ArgumentNullException("endOperationMethod"); } this._endOperationMethod = endOperationMethod; this._channel = channel; this._changeSetEntries = changeSetEntries; }