API хранимых процедур, не зависящих от базы данных - PullRequest
2 голосов
/ 03 октября 2011

Наше устаревшее веб-приложение интенсивно использует хранимые процедуры.У нас есть центральный интерфейс, через который выполняются все вызовы базы данных (т.е. запросы и процедуры).Однако текущая реализация использует метод OracleCommandBuilder.DeriveParameters под капотом для привязки к соответствующей сигнатуре хранимой процедуры.Из документации:

DeriveParameters использует обход базы данных и должен использоваться только во время разработки.Чтобы избежать ненужных обращений к базе данных в производственной среде, сам метод DeriveParameters должен быть заменен явными настройками параметров, которые были возвращены методом DeriveParameters во время разработки.

Мы могли бы использовать класс OracleCommand для явного связывания с правильной сигнатурой хранимой процедуры.Однако засорение нашего кода (даже если только уровень доступа к данным) объектами OracleCommand не зависит от базы данных.Мы уже поддерживаем независимые от базы данных динамические запросы в нашем интерфейсе базы данных (в дальнейшем именуемом IDatabaseService ), который выглядит следующим образом:

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

Мы хотим также поддерживать независимые от базы данных вызовы хранимых процедур.Каков хороший шаблон для этого?

Дополнительная информация:

Для привязки к определенной подпрограмме OracleCommands позволяют BindByName .Мы предпочитаем не использовать этот подход, так как строка более подвержена ошибкам, чем тип.Другой подход для привязки вызова подпрограммы заключается в предоставлении типов параметров.Мы можем полагаться на значения параметров и отражать типы времени выполнения, но мы хотим большей безопасности, чем эта.Мы хотим требовать, чтобы типы были явно предоставлены интерфейсу базы данных, чтобы мы могли проверить, соответствуют ли предоставленные значения параметров предоставленным типам параметров подпрограммы, прежде чем мы сообщимся с базой данных.

1 Ответ

1 голос
/ 05 октября 2011

После создания прототипа различных подходов мы остановились на следующем.

К IDatabaseService мы добавили новые методы ExecuteYYY, которые принимают объект, реализующий IDatabaseSubroutineSignature и (необязательно, через перегрузку) IEnumerable , которые являются значениями параметров.

Методы ExecuteYYY в IDatabaseService выглядят так:

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, который предоставляет методы для создания параметров:

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));
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...