Our legacy web application heavily uses stored procedures. We have a central interface through which all database calls (i.e. queries and procedures) are made. However, the current implementation uses the OracleCommandBuilder.DeriveParameters method under the hood to bind to the appropriate stored procedure signature. From the documentation:
DeriveParameters incurs a database round-trip and should only be used during design time.
To avoid unnecessary database round-trips in a production environment, the
DeriveParameters method itself should be replaced with the explicit parameter settings
that were returned by the DeriveParameters method at design time.
We could use the OracleCommand class to explicitly bind to the correct stored procedure signature. However, littering our code (even if only the Data Access Layer) with OracleCommand objects is not database agnostic. We already support database agnostic dynamic queries in our database interface (hereafter referred to as IDatabaseService), which looks like this:
int ExecuteNonQuery(string query, object[] parameterValues);
IDataReader ExecuteReader(string query, object[] parameterValues);
// etc.
We want to also support database agnostic stored procedure calls. What is a good pattern for this?
More information:
To bind to a specific subroutine, OracleCommands allow BindByName. We prefer to not use that approach, as a string is more error-prone than a type. The other approach for binding a subroutine call is to provide the parameter types. We could rely on the parameter values and reflect on the runtime types, but we want stronger safety than that. We want to require that the types are explicitly provided to the database interface so that we can check that the provided parameter values match the provided subroutine parameter types before we communicate to the database.
After prototyping various approaches, we settled on the following.
To IDatabaseService we added new ExecuteYYY methods that take an object implementing IDatabaseSubroutineSignature and (optionally, via an overload) an IEnumerable that are the parameter values.
The ExecuteYYY methods on IDatabaseService look like this:
There are some differences between the standard .NET BCL ExecuteYYY methods and the above:
IDatabaseSubroutineSignature looks like this:
The final problem we had is with a convenient way to create (and represent) the signatures in code. We settled on a monadesque approach by creating a subinterface of IDatabaseSubroutineSignature that exposes methods for creating parameters:
Finally, here is a usage example: