in src/Core/Resolvers/SqlMutationEngine.cs [345:458]
public async Task<IActionResult?> ExecuteAsync(StoredProcedureRequestContext context, string dataSourceName = "")
{
dataSourceName = GetValidatedDataSourceName(dataSourceName);
ISqlMetadataProvider sqlMetadataProvider = _sqlMetadataProviderFactory.GetMetadataProvider(dataSourceName);
IQueryBuilder queryBuilder = _queryManagerFactory.GetQueryBuilder(sqlMetadataProvider.GetDatabaseType());
IQueryExecutor queryExecutor = _queryManagerFactory.GetQueryExecutor(sqlMetadataProvider.GetDatabaseType());
SqlExecuteStructure executeQueryStructure = new(
context.EntityName,
sqlMetadataProvider,
_authorizationResolver,
_gQLFilterParser,
context.ResolvedParameters);
string queryText = queryBuilder.Build(executeQueryStructure);
JsonArray? resultArray = null;
try
{
// Creating an implicit transaction
using (TransactionScope transactionScope = ConstructTransactionScopeBasedOnDbType(sqlMetadataProvider))
{
resultArray =
await queryExecutor.ExecuteQueryAsync(
queryText,
executeQueryStructure.Parameters,
queryExecutor.GetJsonArrayAsync,
dataSourceName,
GetHttpContext());
transactionScope.Complete();
}
}
// All the exceptions that can be thrown by .Complete() and .Dispose() methods of transactionScope
// derive from TransactionException. Hence, TransactionException acts as a catch-all.
// When an exception related to Transactions is encountered, the mutation is deemed unsuccessful and
// a DataApiBuilderException is thrown
catch (TransactionException)
{
throw _dabExceptionWithTransactionErrorMessage;
}
// A note on returning stored procedure results:
// We can't infer what the stored procedure actually did beyond the HasRows and RecordsAffected attributes
// of the DbDataReader. For example, we can't enforce that an UPDATE command outputs a result set using an OUTPUT
// clause. As such, for this iteration we are just returning the success condition of the operation type that maps
// to each action, with data always from the first result set, as there may be arbitrarily many.
switch (context.OperationType)
{
case EntityActionOperation.Delete:
// Returns a 204 No Content so long as the stored procedure executes without error
return new NoContentResult();
case EntityActionOperation.Insert:
HttpContext httpContext = GetHttpContext();
string locationHeaderURL = UriHelper.BuildAbsolute(
scheme: httpContext.Request.Scheme,
host: httpContext.Request.Host,
pathBase: GetBaseRouteFromConfig(_runtimeConfigProvider.GetConfig()),
path: httpContext.Request.Path);
// Returns a 201 Created with whatever the first result set is returned from the procedure
// A "correctly" configured stored procedure would INSERT INTO ... OUTPUT ... VALUES as the result set
if (resultArray is not null && resultArray.Count > 0)
{
using (JsonDocument jsonDocument = JsonDocument.Parse(resultArray.ToJsonString()))
{
// The final location header for stored procedures should be of the form ../api/<SP-Entity-Name>
// Location header is constructed using the base URL, base-route and the set location value.
return new CreatedResult(location: locationHeaderURL, SqlResponseHelpers.OkMutationResponse(jsonDocument.RootElement.Clone()).Value);
}
}
else
{ // If no result set returned, just return a 201 Created with empty array instead of array with single null value
return new CreatedResult(
location: locationHeaderURL,
value: new
{
value = JsonDocument.Parse("[]").RootElement.Clone()
}
);
}
case EntityActionOperation.Update:
case EntityActionOperation.UpdateIncremental:
case EntityActionOperation.Upsert:
case EntityActionOperation.UpsertIncremental:
// Since we cannot check if anything was created, just return a 200 Ok response with first result set output
// A "correctly" configured stored procedure would UPDATE ... SET ... OUTPUT as the result set
if (resultArray is not null && resultArray.Count > 0)
{
using (JsonDocument jsonDocument = JsonDocument.Parse(resultArray.ToJsonString()))
{
return SqlResponseHelpers.OkMutationResponse(jsonDocument.RootElement.Clone());
}
}
else
{
// If no result set returned, return 200 Ok response with empty array instead of array with single null value
return new OkObjectResult(
value: new
{
value = JsonDocument.Parse("[]").RootElement.Clone()
}
);
}
default:
throw new DataApiBuilderException(
message: "Unsupported operation.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
}