Как улучшить слой доступа к данным выбрать метод Pattern - PullRequest
8 голосов
/ 12 января 2009

В последнее время я пишу методы выбора уровня доступа к данным, где весь код принимает следующий общий вид:

public static DataTable GetSomeData( ... arguments)
{
    string sql = " ... sql string here:  often it's just a stored procedure name ... ";

    DataTable result = new DataTable();

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection
    using (SqlConnection cn = GetOpenConnection())
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        // could be any number of parameters, each with a different type
        cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function

        using (SqlDataReader rdr = cmd.ExecuteReader())
        {
            result.Load(rdr);
        }
    }

    return result;
}

Или вот так:

public static DataRow GetSomeSingleRecord( ... arguments)
{
    string sql = " ... sql string here:  often it's just a stored procedure name ... ";

    DataTable dt = new DataTable();

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection
    using (SqlConnection cn = GetOpenConnection())
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        // could be any number of parameters, each with a different type
        cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function

        using (SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SingleRow))
        {
            dt.Load(rdr);
        }
    }

    if (dt.Rows.Count > 0)
         return dt.Rows[0];
    return null;
}

Эти методы будут вызываться кодом бизнес-уровня, который затем преобразует базовый DataTable или DataRecord в строго типизированные бизнес-объекты, которые можно использовать на уровне представления.

Поскольку я неоднократно использую подобный код, я хочу убедиться, что этот код является наилучшим. Так как это можно улучшить? И стоит ли пытаться переместить общий код из этого в свой собственный метод. Если это так, как будет выглядеть этот метод (особенно в отношении передачи коллекции SqlParameter в)?

Ответы [ 7 ]

3 голосов
/ 12 мая 2009

Пришлось добавить свое:
Возврат DataReader из DataLayer в операторе Using

Новый шаблон позволяет мне иметь только одну запись в памяти за раз, но все равно заключает соединение в приятное выражение "using":

public IEnumerable<T> GetSomeData(string filter, Func<IDataRecord, T> factory)
{
    string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";

    using (SqlConnection cn = new SqlConnection(GetConnectionString()))
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
        cn.Open();

        using (IDataReader rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
            {
                yield return factory(rdr);
            }
            rdr.Close();
        }
    }
}
2 голосов
/ 12 января 2009

Один шаблон, который мне понравился, выглядит следующим образом с точки зрения клиентского кода:

        DataTable data = null;
        using (StoredProcedure proc = new StoredProcedure("MyProcName","[Connection]"))
        {
            proc.AddParameter("@LoginName", loginName);
            data = proc.ExecuteDataTable();
        }

Я обычно делаю соединение необязательным, и я буду кодировать его, чтобы извлечь его из раздела конфигурации ConnectionStrings или обработать как фактическую строку соединения. Это позволяет мне повторно использовать dal в одном сценарии и частично является хаббитом из дней COM +, когда я сохранял строку подключения, используя свойство конструкции объекта.

Мне это нравится, потому что он легко читается и скрывает от меня весь код ADO.

1 голос
/ 14 февраля 2013

Похоже на то, что я выложил здесь

public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, 
                             Func<IDataRecord, S> selector)
{
    using (var conn = new T()) //your connection object
    {
        using (var cmd = conn.CreateCommand())
        {
            if (parameterizer != null)
                parameterizer(cmd);
            cmd.CommandText = query;
            cmd.Connection.ConnectionString = _connectionString;
            cmd.Connection.Open();
            using (var r = cmd.ExecuteReader())
                while (r.Read())
                    yield return selector(r);
        }
    }
}

У меня есть эти простые методы расширения для облегчения вызова:

public static void Parameterize(this IDbCommand command, string name, object value)
{
    var parameter = command.CreateParameter();
    parameter.ParameterName = name;
    parameter.Value = value;
    command.Parameters.Add(parameter);
}

public static T To<T>(this IDataRecord dr, int index, T defaultValue = default(T),
                      Func<object, T> converter = null)
{
    return dr[index].To<T>(defaultValue, converter);
}

static T To<T>(this object obj, T defaultValue, Func<object, T> converter)
{
    if (obj.IsNull())
        return defaultValue;

    return converter == null ? (T)obj : converter(obj);
}

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

Так что теперь я могу позвонить:

var query = Get(sql, cmd =>
{
    cmd.Parameterize("saved", 1);
    cmd.Parameterize("name", "abel");
}, r => new User(r.To<int>(0), r.To<string>(1), r.To<DateTime?>(2), r.To<bool>(3)));
foreach (var user in query)
{

}

Это полностью универсально, подходит для любой модели, которая соответствует интерфейсам ado.net. Объект подключения и считыватель удаляются только после однократного перечисления коллекции.

1 голос
/ 15 января 2009

Во-первых, я думаю, что вы уже рассматривали возможность использования ORM вместо собственного. Я не буду вдаваться в это.

Мои мысли о развертывании собственного кода доступа к данным:

  • Со временем я обнаружил, что проще не иметь отдельных объектов DAL / BL, а скорее объединить их в один объект (спустя некоторое время после достижения такого вывода я обнаружил, что это довольно известный шаблон - а именно ActiveRecord). Это может выглядеть красиво и не иметь отдельных сборок DAL, но накладные расходы на обслуживание увеличатся. Каждый раз, когда вы добавляете новую функцию, вам придется создавать больше кода / модифицировать больше классов. По моему опыту, команда, которая поддерживает приложение, часто намного меньше, чем команда разработчиков, которая его создала, и они ненавидят дополнительную работу.
  • Для больших команд может иметь смысл отделить DAL (и позволить группе работать над ней, пока другие. Но это дает хороший стимул для раздувания кода.
  • Переходя к конкретному образцу: как вы используете итоговую таблицу данных? Итерировать строки, создавать типизированные объекты и получать данные из строки? Если ответ «да», подумайте о дополнительной DataTable, которую вы создали только для перемещения данных между DAL и BL. Почему бы не взять его прямо из DataReader?
  • Также о примере: если вы возвращаете нетипизированный DataTable, то, я думаю, вы должны использовать имена столбцов (из набора результатов, возвращаемых вызовом SP) вверх в вызывающем коде. Это означает, что если мне нужно что-то изменить в базе данных, это может повлиять на оба слоя.

Мое предложение (я попробовал оба метода - предложение - самый последний рабочий подход, который я придумал - оно как-то развилось со временем).

  • Создайте базовый класс для типизированных бизнес-объектов.
  • Сохранить состояние объекта в базовом классе (новый, измененный и т. Д.)
  • Поместите основные методы доступа к данным в этот класс как статические методы. Приложив немного усилий (подсказка: универсальные методы + Activator.CreateInstance), вы можете создать один бизнес-объект для каждой строки, возвращаемой в считыватель.
  • создать в бизнес-объекте абстрактный метод для анализа данных строки (непосредственно из DataReader!) И заполнить объект.
  • создает статические методы в производных бизнес-объектах, которые подготавливают сохраненные параметры процедуры (в зависимости от различных критериев фильтрации) и вызывают базовые методы доступа к данным из базового класса.

Цель состоит в том, чтобы в конечном итоге использовать такие как:

List<MyObject> objects = MyObject.FindMyObject(string someParam);

Преимущество для меня заключалось в том, что мне нужно всего лишь изменить один файл, чтобы справиться с изменениями в именах столбцов базы данных, их типах и т. Д. (В общем, небольшие изменения). С некоторыми хорошо продуманными областями вы можете организовать код так, чтобы они были отдельными «слоями» в одном объекте :). Другое преимущество заключается в том, что базовый класс действительно можно многократно использовать из одного проекта в другой. И раздувание кода минимально (ну, по сравнению с преимуществами. Вы также можете заполнить наборы данных и привязать их к элементам управления пользовательского интерфейса: D

Ограничения - вы получаете один класс на объект домена (обычно на основную таблицу базы данных). И вы не можете загружать объекты в существующие транзакции (хотя вы можете подумать о передаче транзакции, если она у вас есть).

Дайте мне знать, если вы заинтересованы в более подробной информации - я мог бы немного расширить ответ.

1 голос
/ 12 января 2009

Существует много способов реализации DBAL, на мой взгляд, вы на правильном пути. Что нужно учесть в вашей реализации:

  • Вы используете фабричный метод для создания своего SqlConnection, это второстепенный момент, но вы можете сделать то же самое для вашего SqlCommand.
  • Длина параметра является необязательной, поэтому вы можете на самом деле не указывать ее в вызове Parameter.Add.
  • Создайте методы для добавления параметров, пример кода ниже.

Добавить параметры, используя DbUtil.AddParameter(cmd, "@Id", SqlDbType.UniqueIdentifier, Id);

internal class DbUtil {

internal static SqlParameter CreateSqlParameter(
    string parameterName,
    SqlDbType dbType,
    ParameterDirection direction,
    object value
) {
    SqlParameter parameter = new SqlParameter(parameterName, dbType);

    if (value == null) {
        value = DBNull.Value;
    }

    parameter.Value = value;

    parameter.Direction = direction;
    return parameter;
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType
) {
    return AddParameter(sqlCommand, parameterName, dbType, null);
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType,
    object value
) {
    return AddParameter(sqlCommand, parameterName, dbType, ParameterDirection.Input, value);
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType,
    ParameterDirection direction,
    object value
) {
    SqlParameter parameter = CreateSqlParameter(parameterName, dbType, direction, value);
    sqlCommand.Parameters.Add(parameter);
    return parameter;
    }
}
1 голос
/ 12 января 2009

Единственное, что я делаю по-другому, - я переключился с собственных вспомогательных методов внутренней базы данных на действительный блок приложения доступа к данным http://msdn.microsoft.com/en-us/library/cc309504.aspx

Делает его немного более стандартизированным / унифицированным для других разработчиков, которые знают корпоративную библиотеку, чтобы нарастить код.

0 голосов
/ 10 сентября 2017

Самое простое решение:

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();
...