microsoft / PowerPlatform-DataverseServiceClient

Code Replica for Microsoft.PowerPlatform.Dataverse.Client and supporting nuget packages.
MIT License
279 stars 50 forks source link

Update and Retrieve in a single request #348

Open Misiu opened 1 year ago

Misiu commented 1 year ago

Currently, to update the record and get the updated record from dynamics we must call two methods: Update(Async) and Retrieve(Async). Ideally, we should be able to same thing with only a single request, similar to CreateAndReturnAsync (which isn't implemented btw). The idea behind this FR is to be able to minimize the number of requests than need to be done to get the result.

This is available via WebApi (https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/mt607664(v=crm.8)#update-with-data-returned), so ideally it should be possible to do with the library

fowl2 commented 1 year ago

CreateAndReturnAsync is the solution, but as a workaround perhaps it'd be possible to Create (with a client generated GUID) and Retrieve inside a ExecuteMultiple call?

Misiu commented 1 year ago

I want to UpdateAndReturnAsync, but right now I must call Update and Retrieve, so ideally, this should be possible to do with just a single call. BTW CreateAndReturnAsync (implementation of it) would be useful too.

MattB-msft commented 1 year ago

To clarify a few things... The Dataverse WebAPI is implemented on the Organization API. The Organization API is what is directly exposed in this client.

In the webAPI when Prefer: return=representation is used, the WebAPI interface is making multiple calls to the organization api. In the case of a create it does the create, gets the id, then internally calls retrieve with all columns, its the same behavior for all operations that utilize it.

That operation a very inefficient and expensive operation, specifically in the case of 'non-create' as the size of the existing record can be quite large (and time consuming in relative terms). Thus, we wanted to encourage a user that needed that functionality to explicitly think about it, ideally filtering the return set of information, which you can do in the webAPI, though its rarely used.

Based on our telemetry of API operations, In most cases when an update occurs, it's a point update (1 or more fields) and the resulting data in the record, that data that was not touched in the update, is less important to the caller.

That said, we have considered adding an extension to the client for that purpose (Update and return record), requiring the caller to provide a column set to be returned to discourage the bad habit of "all columns". It has been on our 'to-do list' of items we have for extensions to the client for a while.

Misiu commented 1 year ago

@MattB-msft thanks for the update. I agree that both operations should look like this (should require ColumnSet):

Task<Entity> CreateAndReturnAsync(Entity entity, ColumnSet columnSet, CancellationToken cancellationToken);
Task<Entity> UpdateAndReturnAsync(Entity entity, ColumnSet columnSet, CancellationToken cancellationToken);

This will be added to https://github.com/microsoft/PowerPlatform-DataverseServiceClient/blob/master/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs? Or maybe another interface should be created to ensure backward compatibility.

Also, https://github.com/microsoft/PowerPlatform-DataverseServiceClient/blob/master/src/GeneralTools/DataverseClient/Client/Interfaces/IOrganizationServiceAsync2.cs#L42 should be marked as deprecated, so the users will always use version with ColumnSet

In the webAPI when Prefer: return=representation is used, the WebAPI interface is making multiple calls to the organization api. In the case of a create it does the create, gets the id, then internally calls retrieve with all columns, its the same behavior for all operations that utilize it.

Didn't know that, but the client will do a single web request to the API? I'm using Fiddler to catch all requests going from my app to dynamics, so the end goal is to do a single request (with Prefer: return=representation) and get the updated/created entity instead of doing two requests. My use case: I connect to Dynamics from .NET 7 WebAPI and I'd like to minimize traffic (time of the operation) between my API and Dynamics (on-premise)

MattB-msft commented 1 year ago

Should we implement the Update behavior, we will have a discussion internally as to building it out as an IOrganziationService*3, or an extension to OrganizationService*

The create is less interesting to add a column set too as creates typically do not do a lot , we will discuss it though.

Misiu commented 1 year ago

@MattB-msft any updates?

MattB-msft commented 1 year ago

The next drop will include the implementation for CreateAndReturnAsync with the existing contract, (there is not a sync version of it right now). that will be part of the last release of the 1.0.x version stream.

We will look at picking up variations of it and an update variation in the 1.1.x release versions which will come after the next drop.

Misiu commented 1 year ago

Any updates?

Misiu commented 1 month ago

@MattB-msft any updates on this?

fowl2 commented 1 week ago

At the very least for optimistic concurrency to work without races, rowversion needs to be returned. Does the WebAPI implement this on top of ExecuteTransaction?