Зависимость Inject Sql Connection? - PullRequest
6 голосов
/ 15 ноября 2009

Во-первых, я начинаю использовать StructureMap, но подойдет пример в любой среде DI.

У меня есть класс, как так,

public class GeoData
{
   public List<Country> GetCountries()
   {
      IDbConnection con = new SqlConnection(ConfigurationManager.ConnectionString["GeoDataConnection"])    
      //Sql stuff to return countries from a database
   }
}

Это упрощенное представление о том, как на самом деле выглядит класс, но по сути, все.

Теперь у меня есть новое требование. Мне нужно иметь возможность изменить строку подключения при инициализации класса или метода. Э.Г.

public void Do()
{
   var geoData = new GeoData();

   if(x)
   {
      geoData.ConnectionString = ConfigurationManager.ConnectionString["LIVEGeoDataConnection"]);
   }
   else
   {
      geoData.ConnectionString = ConfigurationManager.ConnectionString["STAGINGGeoDataConnection"]);
   }

   geoData.GetCountries();
}

Есть ли лучшее решение для этого с использованием внедрения зависимостей? Как бы вы сделали это, используя DI-фреймворк по вашему выбору?

Ответы [ 6 ]

9 голосов
/ 15 ноября 2009

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

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

public static class ConnectionFactory
{
    public static IDbConnection GetConnection()
    {
        return GetConnection(ConfigurationManager.ConnectionString["GeoDataConnection"]);
    }

    public static IDbConnection GetConnection(string connectionString)
    {
        return new SqlConnection(connectionString);
    }
}

Тогда я бы использовал это так:

public class GeoData
{
   public List GetCountries()
   {
      using (IDbConnection con = ConnectionFactory.GetConnection())
      {
        //Sql stuff to return countries from a database
      }
   }
}

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

Надеюсь, это поможет ...

5 голосов
/ 15 ноября 2009

Первый вопрос, который нужно задать себе, это что такое GeoData? Другими словами, какова ответственность класса?

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

Если это так, каковы зависимости? Один из способов определить это - попытаться написать модульных тестов , которые тестируют GeoData изолированно. Если тестирование требует значительных настроек, тестируемый класс либо тесно связан с другими классами, либо имеет несколько обязанностей (низкая сплоченность ).

Допустим, мы изменили класс так, чтобы конструктор принял параметр строки соединения. Как мы можем протестировать публичный метод GetCountries? Что ж, сначала мы создали базу данных с известными тестовыми данными ...

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

Ну, мы могли бы передать объект, реализующий IDbConnection, конструктору (внедрение конструктора). Обратите внимание, что внедрение зависимостей обычно включает передачу интерфейсов или абстрактных классов. Чтобы проверить это, нам нужно создать поддельный IDbConnection. Мы могли бы использовать изолирующую (насмешливую) структуру . Но тогда нам понадобится создать поддельный IDbCommand при вызове CreateCommand ...

Цитируя Джереми Миллера (автора StructureMap ): «Это слишком много усилий для слишком маленькой выгоды». См. Его статью Лучшие и худшие практики для фиктивных объектов .

Одна из возможностей - использовать Шаблон репозитория . Вы должны передать интерфейс к конкретному репозиторию в конструктор GeoData. Это было бы легко подделать (вручную или с насмешливой библиотекой) для тестирования. Конкретный репозиторий будет обрабатывать все данные доступа. Его можно комбинировать с каркасом ORM для дальнейшего абстрагирования доступа к данным. Управление строкой соединения будет осуществляться через ORM или через репозиторий (предпочтительно в другой зависимости или базовом классе).

Если это звучит сложно, потому что это так. Вы выбрали один из самых сложных случаев для внедрения зависимостей (который, к сожалению, также является одним из самых распространенных).

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

РЕДАКТИРОВАТЬ: Просто чтобы быть ясно, Внедрение зависимости не сложная часть. Разделение доступа к данным.

5 голосов
/ 15 ноября 2009

Просто, вам не нужны рамки. Просто есть перегруженный конструктор для вашего GeoData класса.

GeoData geo = new GeoData(yourConnString);

Строка - это ваша зависимость. Поскольку это не сложный тип, у вас есть инъекция зависимости прямо здесь.

Я не специалист по ракетостроению, хотя некоторым может нравиться, что вы верите в это.

1 голос
/ 15 ноября 2009

Я бы создал фабрику, которая создает экземпляр класса GeoData, который, в свою очередь, реализует интерфейс с методом Do (скажем, IDoCommand).

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

0 голосов
/ 15 ноября 2009

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

public class ConnectionStringManager
{
    public string GeoDataConnectionString
    {
        get
        {
            return x 
                ? ConfigurationManager.ConnectionString["LIVEGeoDataConnection"])
                : ConfigurationManager.ConnectionString["STAGINGGeoDataConnection"]);
        }
    }
}

Затем вы можете внедрить это в класс, содержащий метод Do, для настройки экземпляров GeoData следующим образом:

public class Blah(ConnectionStringManager connManager)
{
    public void Do()
    {
        var geoData = new GeoData { ConnectionString = connManager.GeoDataConnectionString };
        geoData.GetCountries();
    }
}
0 голосов
/ 15 ноября 2009

У Мартина Фаулера есть статья на эту тему здесь , в которой объясняются различные подходы. Лично я предпочитаю внедрение интерфейса, но это вопрос вкуса.

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