数据库不可知的存储过程API

时间:2011-10-03 17:22:47

标签: oracle stored-procedures database-agnostic

我们的遗留Web应用程序大量使用存储过程。我们有一个中央接口,通过它可以进行所有数据库调用(即查询和过程)。但是,当前实现使用引擎盖下的the OracleCommandBuilder.DeriveParameters method来绑定到适当的存储过程签名。来自文档:

  

DeriveParameters会导致数据库往返,并且只能在设计时使用。   为了避免在生产环境中进行不必要的数据库往返,   DeriveParameters方法本身应该用显式参数设置替换   在设计时由DeriveParameters方法返回。

我们可以使用the OracleCommand class显式绑定到正确的存储过程签名。但是,使用OracleCommand对象乱丢我们的代码(即使只有数据访问层)与数据库无关。我们已经在数据库接口(以下简称​​ IDatabaseService )中支持数据库不可知的动态查询,如下所示:

int ExecuteNonQuery(string query, object[] parameterValues);
IDataReader ExecuteReader(string query, object[] parameterValues);
// etc.

我们还希望支持与数据库无关的存储过程调用。对此有什么好的模式?

更多信息:

要绑定到特定子例程,OracleCommands允许BindByName。我们更喜欢不使用该方法,因为字符串比类型更容易出错。绑定子例程调用的另一种方法是提供参数类型。我们可以依赖参数值并反映运行时类型,但我们需要更强的安全性。我们希望要求将类型显式提供给数据库接口,以便在与数据库通信之前检查提供的参数值是否与提供的子例程参数类型匹配。

1 个答案:

答案 0 :(得分:1)

在对各种方法进行原型设计之后,我们选择了以下内容。

IDatabaseService ,我们添加了新的ExecuteYYY方法,这些方法将实现 IDatabaseSubroutineSignature 的对象和(可选地,通过重载) IEnumerable 作为参数值。

IDatabaseService 上的ExecuteYYY方法如下所示:

DataSet ExecuteDataSet(IDatabaseSubroutineSignature signature);
DataSet ExecuteDataSet(IDatabaseSubroutineSignature signature, IEnumerable<object> parameterValues);
void ExecuteNonQuery(IDatabaseSubroutineSignature signature);
void ExecuteNonQuery(IDatabaseSubroutineSignature signature, IEnumerable<object> parameterValues);
IDataReader ExecuteReader(IDatabaseSubroutineSignature signature);
IDataReader ExecuteReader(IDatabaseSubroutineSignature signature, IEnumerable<object> parameterValues);
object ExecuteScalar(IDatabaseSubroutineSignature signature);
object ExecuteScalar(IDatabaseSubroutineSignature signature, IEnumerable<object> parameterValues);
ReadOnlyCollection<object> ExecuteScalarMultiple(IDatabaseSubroutineSignature signature);
ReadOnlyCollection<object> ExecuteScalarMultiple(IDatabaseSubroutineSignature signature, IEnumerable<object> parameterValues);

标准.NET BCL ExecuteYYY方法与上述方法之间存在一些差异:

  • 我们的ExecuteNonQuery方法返回void。这是因为执行存储过程时,ExecuteNonQuery(在命令对象上)始终返回-1。
  • 我们引入了一个新的ExecuteScalarMultiple方法。这说明了多个输出参数。

IDatabaseSubroutineSignature 如下所示:

public interface IDatabaseSubroutineSignature
{
    string Name { get; }
    IEnumerable<IDatabaseSubroutineParameter> Parameters { get; }
}

public interface IDatabaseSubroutineParameter
{
    ParameterType Type { get; }
    ParameterDirection Direction { get; }
}

// Using custom DbType attribute.
public enum ParameterType
{
    [DbType(DbType.Decimal)]
    Decimal,
    [DbType(DbType.String)]
    String,
    [DbType(DbType.StringFixedLength)]
    Character,
    RefCursor,
    [DbType(DbType.Double)]
    Double,
    [DbType(DbType.Int32)]
    Int32,
    [DbType(DbType.Int64)]
    Int64,
    [DbType(DbType.DateTime)]
    DateTime
}

我们遇到的最后一个问题是使用方便的方法在代码中创建(和表示)签名。我们通过创建IDatabaseSubroutineSignature的子接口来解决monadesque方法,该子接口公开了创建参数的方法:

public interface IDatabaseSubroutineSignatureCreator : IDatabaseSubroutineSignature
{
    IDatabaseSubroutineSignatureCreator Input(ParameterType dbType);
    IDatabaseSubroutineSignatureCreator Output(ParameterType dbType);
    IDatabaseSubroutineSignatureCreator InputOutput(ParameterType dbType);
    IDatabaseSubroutineSignatureCreator ReturnValue(ParameterType dbType);
}

最后,这是一个用法示例:

private static readonly IDatabaseSubroutineSignature MyProcedureSignature =
    DatabaseSubroutineSignatureFactory.Create("pkg.myprocedure")
        .Input(ParameterType.Decimal)
        .Input(ParameterType.String)
        .Output(ParameterType.RefCursor);

public IEnumerable<DataObject> CallMyProcedure(decimal userId, string searchQuery)
{
    using (IDatabaseService dbService = ...)
    using (IDataReader dataReader = dbService.ExecuteReader(MyProcedureSignature,
        new object[] { userId, searchQuery }))
    {
        while (dataReader.Read())
        {
            yield return new DataObject(
                dataReader.GetDecimal(0),
                dataReader.GetString(1));
        }
    }
}