Как провести рефакторинг большого класса, который хранит многочисленные SqlCommands в словаре - PullRequest
1 голос
/ 17 мая 2019

Я недавно начал рефакторинг старой системы, разработанной кем-то с небольшим опытом работы в ООП. К счастью, (почти) весь доступ к базе данных находится в одном файле длиной 3000 строк. Эти файлы содержат Dictionary<string, SqlCommand>, SqlConnection, очень длинную функцию, добавляющую каждый запрос SQL в словарь, например:

cmd = new SqlCommand(null, _sqlConnection);
cmd.CommanText = "SELECT * FROM User WHERE User.UserID = @id;" // Most queries are far from being this simple
cmd.Parameters.Add(new SqlParameter("@id", SqlDbType.Int, 0));
cmd.Prepare();

_cmds.Add("getUser", cmd);

Эти запросы используются функциями в том же файле, которые будут выглядеть следующим образом:

public void deleteUser(int userId) 
{
    if (_cmds.TryGetValue("deleteUser", out SqlCommand cmd)) 
    {
        lock(cmd) 
        { 
            cmd.Parameters[0].Value = userId;
            cmd.ExecuteNonQuery();
        }
    }
}

public int isConnected(int userId, out int amount) 
{
    bool result = false;
    amount = 0;

    if (_cmds.TryGetValue("userInfo", out SqlCommand cmd)) 
    {
        lock (cmd) 
        {
            cmd.Parameters[0].Value = userId;

            using (SqlDataReader reader = new cmd.ExecuteReader()) 
            {
                 if (reader.HasRows)
                     while (reader.Read()) 
                     {
                         amount = (int)Math.Round(reader.GetDecimal(0));
                         result = reader.GetInt32(1);
                     }
            }
        }
    }


  return result;
}

Теперь работать и поддерживать это ужасно. У меня наконец-то есть время, чтобы изменить это. Я хотел превратить это в правильный DAL с репозиториями, которые будут использоваться сервисами и могут быть зависимыми от инъекций.

Меня не волнует изменение функций или запросов (например, с использованием ORM). Что меня больше интересует, так это разделить файл на множество файлов таким образом, чтобы мне было легче его смоделировать, протестировать и изменить. Я ищу способ лучше структурировать существующий код, хотя я знаю, что потребуется много копирования / вставки и перекодирования.

Ответы [ 3 ]

1 голос
/ 17 мая 2019

Выезд Dapper .Это «микро-ORM» и предлагает высокопроизводительный объектно-ориентированный доступ к данным.Вы можете продолжать использовать все существующие запросы, но замените весь код ADO.NET для стандартной схемы на Dapper.

1 голос
/ 17 мая 2019

Это займет некоторое повторение работы, но вот несколько идей о том, как справиться с этим.Это не приведёт код в идеальное состояние, но может сделать его немного более управляемым.Одна из проблем заключается в том, что у каждого метода есть части в двух местах - в методе и в другой, где команда хранится в словаре.

  • Никогда не добавляйте больше SQL в этот класс.Начните определять и использовать новые репозитории, которые вы хотите.
  • Легко и издеваться.Вы можете использовать рефакторинг extract interface , чтобы создать интерфейс, позволяющий имитировать этот класс, даже в его текущей форме.Это будет большой, уродливый интерфейс, но, по крайней мере, вы можете имитировать методы, если вам это нужно.

Это простая часть.Как можно реорганизовать весь класс, не нарушая ни одной его части?Эти шаги являются лишь некоторыми идеями:

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

public class YourDataAccessClass
{
    private readonly string _connectionString;

    public YourDataAccessClass(string connectionString)
    {
        _connectionString = connectionString;
    }
}

Вы будете использовать ее по одному методу за раз.Изначально вы можете оставить большую часть класса, включая словарь, как есть.Таким образом, методы, которые вы не изменили, продолжат работать.

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

Скорее всего, вы захотите переместить SQL для каждой функции в эту функцию.Вы могли бы сделать это, когда вы рефакторинг каждой функции, но это может быть менее болезненным, чтобы сделать все сразу, чтобы вы получили эффективность от повторения.

Вы можете определить новую переменную в каждой функции, скопировать и вставить:

var sql = "SELECT * FROM User WHERE User.UserID = @id;";

(Опять же, не так, как я обычно это пишу.)

Теперьу вас есть функция или 100 функций, которые выглядят следующим образом:

public void deleteUser(int userId) 
{
    var sql = "DELETE User WHERE User.UserID = @id;";
    if (_cmds.TryGetValue("deleteUser", out SqlCommand cmd)) 
    {
        lock(cmd) 
        { 
            cmd.Parameters[0].Value = userId;
            cmd.ExecuteNonQuery();
        }
    }
}

Для команд, не связанных с запросом, вы можете написать такую ​​функцию в своем классе, которая исключит повторяющийся код для открытия соединения,создайте команду и т.д:

private void ExecuteNonQuery(string sql, Action<SqlCommand> addParameters = null)
{
    using (var connection = new SqlConnection(_connectionString))
    using (var command = new SqlCommand(sql))
    {
        addParameters?.Invoke(command);
        connection.Open();
        command.ExecuteNonQuery();
    }
}

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

ExecuteNonQuery(sql, command =>
{

});

После того, как вы вставите его, переместите строку или строки, которые добавляют параметры, в тело аргумента cmd (которыйс именем cmd, чтобы вы могли перемещать строки без изменения имени переменной), а затем удалить существующий код, который выполнял запрос ранее.

ExecuteNonQuery(sql, cmd =>
{
    cmd.Parameters[0].Value = userId;
});

Теперь ваша функция выглядит следующим образом:

public void deleteUser(int userId) 
{
    var sql = "DELETE User WHERE User.UserID = @id;";
    ExecuteNonQuery(sql, cmd =>
    {
        cmd.Parameters[0].Value = userId;
    });
}

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

Те, которые на самом деле возвращают данные, менее забавны, но все же управляемы.

Во-первых, возьмите в значительной степени тот же самый шаблонный код.Вероятно, это можно улучшить, потому что он все еще немного повторяется, но, по крайней мере, он более автономен:

using (var connection = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
{

    connection.Open();        

}

Начиная с этого:

public int isConnected(int userId, out int name) 
{
    var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
    bool result = false;
    amount = 0;

    if (_cmds.TryGetValue("userInfo", out SqlCommand cmd)) 
    {
        lock (cmd) 
        {
            cmd.Parameters[0].Value = userId;

            using (SqlDataReader reader = new cmd.ExecuteReader()) 
            {
                 if (reader.HasRows)
                     while (reader.Read()) 
                     {
                         amount = (int)Math.Round(reader.GetDecimal(0));
                         result = reader.GetInt32(1);
                     }
            }
        }
    }
}

Вставьте шаблон в метод:

public int isConnected(int userId, out int name) 
{
    var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
    bool result = false;
    amount = 0;

    using (var connection = new SqlConnection(_connectionString))
    using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
    {

        connection.Open();        

    }

    if (_cmds.TryGetValue("userInfo", out SqlCommand cmd)) 
    {
        lock (cmd) 
        {
            cmd.Parameters[0].Value = userId;

            using (SqlDataReader reader = new cmd.ExecuteReader()) 
            {
                 if (reader.HasRows)
                     while (reader.Read()) 
                     {
                         amount = (int)Math.Round(reader.GetDecimal(0));
                         result = reader.GetInt32(1);
                         // was this a typo? The code in the question doesn't
                         // return anything or set the "out" variable. But
                         // if that's in the method then that will be part of
                         // what gets copied.
                     }
            }
        }
    }
}

Затем, как и прежде, переместите деталь, в которой вы добавляете свои параметры выше connection.Open();, и переместите деталь, где вы используете команду, прямо под connection.Open(); и удалите то, что осталось.Результат таков:

public int isConnected(int userId, out int name) 
{
    var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
    bool result = false;
    amount = 0;

    using (var connection = new SqlConnection(_connectionString))
    using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
    {
        cmd.Parameters[0].Value = userId;
        connection.Open();        
        using (SqlDataReader reader = new cmd.ExecuteReader()) 
        {
             if (reader.HasRows)
                 while (reader.Read()) 
                 {
                     amount = (int)Math.Round(reader.GetDecimal(0));
                     result = reader.GetInt32(1);
                 }
        }
    }
}

Вы, вероятно, можете попасть в паз и сделать это через минуту или две, что означает, что это займет всего несколько часов.

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

Вы также можете разбить его.Один из способов - переместить строку соединения и вспомогательную функцию в базовый класс (или просто дублировать вспомогательную функцию - она ​​действительно мала), и вы можете переместить любую функцию запроса в меньший класс, поскольку каждая функция является автономной.

1 голос
/ 17 мая 2019

Рекомендую заменить написанный вручную код отображения объектов с использованием объектно-реляционного сопоставителя, например NHibernate , что сэкономит время и усилия по созданию и поддержке уровня доступа к данным..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...