Has anyone had any success batching REST calls together with the batch service methods?
We are currently on V11 in a RAMP environment and I'm having some issues with this. I am get the error "Object reference not set to an instance of an object." for any service URI I enter (local, relative, or even web.
I'm really stuck on this right now and I'm not sure if this is a V11, RAMP, or coding or other random issue.
Here is my current POST body to https://fairlb/testapi/constituentservice/constituents/batch from within my RAMP terminal (local to the gateway):
<BatchRequest>
<Requests>
<Request>
<Id>1</Id>
<Uri>https://fairlb/testapi/constituentservice/constituents/5</Uri>
<HttpMethod>GET</HttpMethod>
</Request>
</Requests>
</BatchRequest>
Has anyone had similar issues in a non-RAMP environment?
-Chris
Hi Chris,
It's a little tricky with the dataservice because it does not know about the objects you are trying to batch, so you have to use the content type of a:XElement like below:
<BatchRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Content i:type="a:XElement" xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml.Linq">
<CustomThing>
<CustomColumn>1</CustomColumn>
</CustomThing>
</Content>
<HttpMethod>POST</HttpMethod>
<Uri>https://website.com/testapi/tessituraservice/custom/customthings/</Uri>
<Id>2</Id>
<Uri>https://website.com/testapi/tessituraservice/custom/customthings/1</Uri>
Thanks Paul-- That will probably save me some extra headaches in the future. Seems my GET call is matching yours, though, so I'm still unsure of what might be wrong with my base calls.
Do you know if the API loops back through the network to make the batch calls? I'm still not entirely sure of the stack behavior when it comes to these Batch methods
Sorry, nevermind, found it. Now I get a different error at least!
(Update: wrong content-type)
Okay, so now it just fails silently. That's something, I guess?
Is your accept header set?
It wasn't, and now I get the same error I get through the built-in REST client. Oh well. Oddly, I didn't need it to run some other (not Batch) methods correctly.
Gawain, hoping this helps a bit:
in your batch request above, try moving Content to the first element. Using XML, the elements need to be alphabetically ordered in your request. It's not seeing Content first and deserializing it as NULL.
<BatchRequest> <Requests> <Request> <HttpMethod>POST</HttpMethod> <Id>1</Id> <Uri>http://calplb/TestAPI/TessituraService/CRM/Addresses/</Uri> <Content i:type="address" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Constituent><Id>20335666</Id></Constituent> <Addresstype><Id>14</Id></Addresstype> <Street1>111 Nob Hill Crescent</Street1> <City>San Francisco</City> <State><Id>11</Id></State> <PostalCode>94109</PostalCode> <Country><Id>1</Id></Country> <Label>True</Label> <PrimaryIndicator>False</PrimaryIndicator> <Months>YYYYNNNNYYYY</Months> </Content> </Request> </Requests> </BatchRequest>
I have one final note regarding custom and batching. At this time, the Custom resources are not supported in Batch requests. The Batch service relies heavily on known service entities (like 'Constituent', 'PriceType', e.g.) to build its requests and responses. Custom resources are by their nature ad hoc and dynamic and the capabiliity of building requests based on these does not exist in the service.
Hope to see you at TLCC!
-Ryan Creps
Tessitura Network
Hi Ryan,
Thanks! I realized I have read that before, but totally forgot when setting this up. That does have me moving forward a bit. But do you mean that even Custom resources defined in TR_DATASERVICE_TABLES cannot be used with Batch? Will that change in v14? If that's true, I'm really sunk.
This has moved me forward with my Batch->Addresses test, but now I'm getting an error "Field 'Address Months' cannot be null or empty." I'm specifying it, as you can see in the example, and that label is what is used in the REST documentation. Is there some other?
Actually, running a direct call to CRM/Addresses with XML nets more errors:
<ErrorMessages xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <ErrorMessage> <Code>FIELD_CANNOT_BE_NULL</Code> <Description>Field 'Address Months' cannot be null or empty.</Description> <Details i:nil="true" /> <ErrorPath>Address Months</ErrorPath> </ErrorMessage> <ErrorMessage> <Code>FIELD_CANNOT_BE_NULL</Code> <Description>Field 'Address City' cannot be null or empty.</Description> <Details i:nil="true" /> <ErrorPath>Address City</ErrorPath> </ErrorMessage> <ErrorMessage> <Code>FIELD_CANNOT_BE_NULL</Code> <Description>Field 'Address State' cannot be null or empty.</Description> <Details i:nil="true" /> <ErrorPath>Address State</ErrorPath> </ErrorMessage> <ErrorMessage> <Code>FIELD_CANNOT_BE_NULL</Code> <Description>Field 'Address PostalCode' cannot be null or empty.</Description> <Details i:nil="true" /> <ErrorPath>Address PostalCode</ErrorPath> </ErrorMessage> </ErrorMessages>
Still, this does still raise the question of why other people can do it (Batch->CRM/Addresses->JSON) and I can't. Nick, Chris, one other variable (I guess), is that I'm on RAMP, and that means we are using SQL Server 2008. I'm being told this issue is somehow fixed in v14 (which we can't have because we're stuck on SS 2008 with RAMP. Is there a possibility that you guys are already on SS 2012?
Lastly, alas, I'm not going to be able to make the TLCC this year. I'll miss you guys!
Huh, if I just run CRM/Addresses, and I sort the elements alphabetically:
<Address xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Addresstype><Id>14</Id></Addresstype> <City>San Francisco</City> <Constituent><Id>20335666</Id></Constituent> <Country><Id>1</Id></Country> <Label>True</Label> <Months>YYYYNNNNYYYY</Months> <PostalCode>94109</PostalCode> <PrimaryIndicator>False</PrimaryIndicator> <State><Id>11</Id></State> <Street1>111 Nob Hill Crescent</Street1> </Address>
then I get a different error:
<ErrorMessages xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <ErrorMessage> <Code>FIELD_CANNOT_BE_NULL</Code> <Description>Field 'entity' cannot be null or empty.</Description> <Details> at Tessitura.Services.Common.Guard.ArgumentNotNull(Expression`1 expression, String errorPath) at Tessitura.Services.Common.Guard.EntityIdShouldNotBeGreaterThanZero(IEntity entity) at Tessitura.Service.Impl.SubEntityService.CreateSubEntity[T](T subEntity) at Tessitura.Service.Impl.AddressService.Create(AddressEntity subEntity) at Tessitura.Service.Web.Controllers.CRM.AddressesController.<>c__DisplayClass7.<Post>b__6() at Tessitura.Service.Web.Controllers.Base.BaseController.RunInTransaction[T](Func`1 func) at Tessitura.Service.Web.Controllers.CRM.AddressesController.Post(Address address) at lambda_method(Closure , Object , Object[] ) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()</Details> <ErrorPath>entity</ErrorPath> </ErrorMessage> </ErrorMessages>
You are missing a few properties. Try something along the lines of:
<Address xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <AddressType> <Id>1</Id> </AddressType> <AffiliatedConstituent/> <AltSalutationType> <Id>0</Id> </AltSalutationType> <City>San Francisco</City> <Constituent> <Id>1007</Id> </Constituent> <Country> <Id>1</Id> </Country> <GeoArea>1</GeoArea> <Id>-999</Id> <Inactive>false</Inactive> <Label>true</Label> <Months>YYYYYYYYYYYY</Months> <PostalCode>94109</PostalCode> <PrimaryIndicator>true</PrimaryIndicator> <State> <Id>11</Id> </State> <Street1>sample string 9</Street1> </Address>
Bear in mind that Batch is just a convenient way of bundling calls into a single request. This shouldn't preclude you from making requests sequentially and doing the dependencies between them yourself from each response.
There is no plan at this point to include Custom into the Batch resource.
Sorry to see that you'll be missing TLCC this year!
-Ryan
Do I really need those other fields? I don't with JSON. And should I even be sending "Id" on a create? In any event, I tried this and got the same failure:
<Address xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Addresstype><Id>14</Id></Addresstype> <AffiliatedConstituent/> <AltSalutationType><Id>0</Id></AltSalutationType> <City>San Francisco</City> <Constituent><Id>20335666</Id></Constituent> <Country><Id>1</Id></Country> <GeoArea>1</GeoArea> <Id>-999</Id> <Inactive>False</Inactive> <Label>True</Label> <Months>YYYYNNNNYYYY</Months> <PostalCode>94109</PostalCode> <PrimaryIndicator>False</PrimaryIndicator> <State><Id>11</Id></State> <Street1>111 Nob Hill Crescent</Street1> </Address>
Ryan-
Batching provides the ability to roll back if there is a failure, something that is not currently availble, or even possible, via sequential calls. That's what the docs say, at least. I haven't used it in practice yet.
For what it is worth two others in this thread made the same call with the same request body that was provided. It seems that there are some other issues at play here that don't seem to add up.
Man, that's a shame.
Well, I'm building this for a partner to use to share data with us. What happens is that we are exporting contribution data to them (they happen to be the system of record for campus contributions). That part was simple enough, using Custom/Execute on a procedure I used to pull together all the information they needed.
Then they return some information that they file about each gift, pledge or pledge payment. For each of those they are also going to return some of their biographical info that we lack for each relevant donor (this can include A1, A2 and any/all soft credit donors). Lastly, for each donor, they return what they know of their education history. So for each gift, pledge, or pledge payment entered on a given day, they return:
For X (probably about 3-50) contributions.
This I intend to have them push into three staging tables that I can then review in order to decide how to import that information into our data structures. The ideal would have been to be able to use Batch to A) isolate a single set of returns such that on error the whole set is backed out and B) be able to use DependsOnData in order to feed row ids from one table to the next (i.e. Contribution Id -> Donor, Donor Id -> Degree), and C) that way our partner simply takes their data set and translates it into a single (ideally JSON) formatted request, and then pushes that to our API.
Without Batch they will have to build a system which pushes a row, takes an id, formats another row, sends that, takes another id, etc. and all of the batch handling, should something fail part way, falls on me.
As to TLCC, got a very high maintenance 11-month-old. Hopefully next year! You should hit Tucson again...
--Gawain
Christopher,
What version of SQL Server are you guys on?
So, one key point to understand with Batch requests, is that each request within a batch runs in its own transaction. What this means is if the first one succeeds, but the second one fails, the first request has been committed.
From my interpretation of this, batch wouldn't help you in this scenario because part A and B may succeed...but if part C fails, there is no rollback for requests A and B.
So the docs are wrong, ok.