2

我们的旧版 Web 应用程序大量使用存储过程。我们有一个中央接口,通过它进行所有数据库调用(即查询和过程)。但是,当前实现在后台使用OracleCommandBuilder.DeriveParameters 方法绑定到适当的存储过程签名。从文档中:

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

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

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

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

更多信息:

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

4

1 回答 1

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));
        }
    }
}
于 2011-10-05T15:37:58.910 回答