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

support $batch operations #47

Open
aappddeevv opened this issue Sep 26, 2017 · 12 comments
Open

support $batch operations #47

aappddeevv opened this issue Sep 26, 2017 · 12 comments

Comments

@aappddeevv
Copy link

Support $batch requests through the API.

@derekfinlinson
Copy link

Until David can get this implemented, feel free to try out my library: xrm-webapi.

@aappddeevv
Copy link
Author

aappddeevv commented Sep 30, 2017 via email

@davidyack
Copy link
Owner

@aappddeevv is there a particular batch operation you are looking for ? We do support it for Create in the C#, I'm assuming you are looking for it in the JS?

@derekfinlinson how are you handling parsing the response?

@aappddeevv
Copy link
Author

aappddeevv commented Sep 30, 2017 via email

@derekfinlinson
Copy link

derekfinlinson commented Oct 1, 2017

@davidyack I'm not parsing the response at all. Just returning it as JSON.

@aappddeevv That's exactly the reason I added it to my library in the first place :)

@aappddeevv
Copy link
Author

I built a scala.js based CLI tool for Dynamics (swiss army knife type of thing) and with that client I had to restructure the client so that one could create "requests" independently of the actual call e.g. make a create request as a separate function that is called inside the actual client.Create(...) method. Then backout a hacked parser to read each section. The batch calls are hard to hack well quickly. If a lib does not expose request making and body parsing then doing batch request/response processing is really hard.

@davidyack
Copy link
Owner

So for your Fetch need, couldn't we just dynamically switch to Post for long FetchXML queries?

@aappddeevv
Copy link
Author

I don't think you can issue a Post using the entitySet url and the real issue is URL length. FetchXml can be quite large and exceed the server's 2,000 char limit on the URL. If the fetchxml is in a GET inside a batch (with a POST) then the URL limit is not hit.

@NaveenGaneshe
Copy link

NaveenGaneshe commented Jan 22, 2019

@davidyack , we are using your CRMWebApi for our project and i really appreciate your approach.As we are trying to implement $batch request for the large fetchxml to get records by post request in Dotnet Core but unable to get the expected result. Please let us know the right approach to achieve the correct result.
Here is the sample code:
`internal async Task GetList(string fetchXML)
{
try
{
HttpClient httpClient = new HttpClient();

            var OrganizationAPI = _configuration.GetSection("appSettings").GetSection("OrganizationWebApi").Value;
            var OrganizationUrl = _configuration.GetSection("appSettings").GetSection("OrganizationUrl").Value;
            var AccessToken = _cache.Get<string>("AccessToken");

            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);

            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");

            var body = "--batch_accountFetch" + Environment.NewLine;
            body += "Content-Type: application/http" + Environment.NewLine;
            body += "Content-Transfer-Encoding: binary" + Environment.NewLine;
            body += Environment.NewLine;
            body += "GET " + OrganizationUrl + OrganizationAPI + "accounts?fetchXml=" + fetchXML + " HTTP/1.1" + Environment.NewLine;
            body += "Content-Type: application/json" + Environment.NewLine;
            body += "OData-Version: 4.0" + Environment.NewLine;
            body += "OData-MaxVersion: 4.0" + Environment.NewLine;
            body += Environment.NewLine;
            body += "--batch_accountFetch--";

            var myContent = JsonConvert.SerializeObject(body);
            var buffer = Encoding.UTF8.GetBytes(myContent);
            var byteContent = new ByteArrayContent(buffer);
            byteContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed;boundary=batch_accountFetch");

            var response = await httpClient.PostAsync(OrganizationUrl + OrganizationAPI + "$batch", byteContent);

            string result = await response.Content.ReadAsStringAsync();

            return result;
        }
        catch (Exception Ex)
        {
            throw new Exception(Ex.Message);
        }
    }

`

@davidyack
Copy link
Owner

I think your issue is your trying to json convert the body which needs to be just posted as data. look at the https://pastebin.com/BudCssGe that @aappddeevv posted before - it's JS but should give you some insight. I do still plan on adding support for transparently switching to post on the Fetch if it's too big - just haven't had the time to implement it. I also think in many cases I've seen people should simplify their fetches :)

@NaveenGaneshe
Copy link

NaveenGaneshe commented Feb 5, 2019

Thank you for your reply @davidyack . You are right it was json parsing issue. Below code is the implementation of executing large fetch in Dotnet core app:

`HttpClient httpClient = new HttpClient();

            var OrganizationAPI = _configuration.GetSection("appSettings").GetSection("OrganizationWebApi").Value;
            var OrganizationUrl = _configuration.GetSection("appSettings").GetSection("OrganizationUrl").Value;
            var AccessToken = _cache.Get<string>("AccessToken");

            httpClient.BaseAddress = new Uri(OrganizationUrl + OrganizationAPI + "$batch");

            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);

            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");

            var body = "--batch_accountFetch" + Environment.NewLine;
            body += "Content-Type: application/http" + Environment.NewLine;
            body += "Content-Transfer-Encoding: binary" + Environment.NewLine;
            body += Environment.NewLine;
            body += "GET " + OrganizationUrl + OrganizationAPI + "accounts?fetchXml=" + QueryOptions.FetchXml + " HTTP/1.1" + Environment.NewLine;
            body += "Prefer:odata.include-annotations=OData.Community.Display.V1.FormattedValue" + Environment.NewLine;
            body += "Content-Type: application/json" + Environment.NewLine;
            body += "OData-Version: 4.0" + Environment.NewLine;
            body += "OData-MaxVersion: 4.0" + Environment.NewLine;
            body += Environment.NewLine;
            body += "--batch_accountFetch--";

            var multipartContent = new MultipartContent("mixed");
            multipartContent.Headers.Remove("Content-Type");
            multipartContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=batch_accountFetch");

            var myContent = JsonConvert.SerializeObject(body);
            var stringContent = new StringContent(body, Encoding.UTF8, "application/json");
            multipartContent.Add(stringContent);

            var response = await httpClient.PostAsync(OrganizationUrl + OrganizationAPI + "$batch", multipartContent);

            response.EnsureSuccessStatusCode();

            var data = await response.Content.ReadAsStringAsync();
            CRMGetListResult<ExpandoObject> resultList = new CRMGetListResult<ExpandoObject>();
            resultList.List = new List<ExpandoObject>();

            var values = JObject.Parse(data.Substring(data.IndexOf('{'), data.LastIndexOf('}') - data.IndexOf('{') + 1));
           
            var valueList = values["value"].ToList();

            foreach (var value in valueList)
            {
                FormatResultProperties((JObject)value);
                resultList.List.Add(value.ToObject<ExpandoObject>());
            }
            var deltaLink = values["@odata.deltaLink"];
            if (deltaLink != null)
                resultList.TrackChangesLink = deltaLink.ToString();
            var recordCount = values["@odata.count"];
            if (recordCount != null)
                resultList.Count = int.Parse(recordCount.ToString());
            
           return resultList;`

Alternatively we can also use the belo code:
`public async Task GetRecordsInBatch(string entity, string fetchXML)
{
string returnVal = "";
try
{
//Connect to crm
var accessToken = await _crm.GetApiToken();

            var httpClient = new HttpClient();

            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
            var batchid = "batch_" + Guid.NewGuid().ToString();

            MultipartContent batchContent = new MultipartContent("mixed", batchid);
            var changesetID = "changeset_" + Guid.NewGuid().ToString();
           //MultipartContent changeSetContent = new MultipartContent("mixed", changesetID);

            HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, _organizationWebApi + entity + "?fetchXml=" + fetchXML);
            req.Version = new Version(1, 1);

            //req.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
            HttpMessageContent content = new HttpMessageContent(req);
            content.Headers.Remove("Content-Type");
            content.Headers.TryAddWithoutValidation("Content-Type", "application/http");
            content.Headers.TryAddWithoutValidation("Content-Transfer-Encoding", "binary");
            content.Headers.TryAddWithoutValidation("Content-ID","1");

            //changeSetContent.Add(content);

            batchContent.Add(content);

            HttpRequestMessage batchRequest = new HttpRequestMessage(HttpMethod.Post, _organizationWebApi + "$batch");

            batchRequest.Content = batchContent;

            var batchstring = await batchRequest.Content.ReadAsStringAsync();

            var response = await httpClient.SendAsync(batchRequest);
            var responseString = response.Content.ReadAsStringAsync();
            MultipartMemoryStreamProvider batchStream = await response.Content.ReadAsMultipartAsync();
            var changesetStream = batchStream.Contents.FirstOrDefault();

        }
        catch (Exception ex)
        {
            return ex.Message;
        }

        return returnVal;
    }

`

@AshV
Copy link

AshV commented Mar 21, 2019

Hello all,

Associate on create is not working with $batch for me, I have raised below issue in the product documentation repo with request/response details.
https://github.com/MicrosoftDocs/dynamics-365-customer-engagement/issues/887

I saw another question about this in stack overflow https://stackoverflow.com/questions/32761311/create-annotation-to-a-contact-entity-in-microsoft-dynamics-crm-by-api

Has anyone tried this with $batch? Is that a product issue or something wrong with my request format.

I am adding this here because this thread has a very productive discussion about $batch.

Please have a look into given links & help me out.

Thanks,
AshV

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants