Код, не зависимый от поставщика

100

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

Тем не менее, модель поставщиков не идеальна. Хотя можно применять стандартные интерфейсы для взаимодействия с объектами Command и Connection, при создании их экземпляров необходимо знать специфичные для конкретного поставщика, строго типизированные классы, которые придется использовать (такие как SqlConnection). Это ограничение усложняет построение других инструментов или дополнений, применяющих ADO.NET.

Например, чуть позже вы узнаете о новых элементах управления источниками данных ASP.NET, которые позволяют создавать привязанные к данным страницы без написания даже одной строки кода. Для использования этой функциональности элементам управления данными требуется каким-то образом создавать необходимые им объекты ADO.NET "за кулисами". Это было невозможно в .NET 1.x. Однако в .NET 2.0 появилась новая модель фабрики с усовершенствованной поддержкой написания независимого от поставщика кода (кода, который может работать с любой базой данных). В .NET 3.5 эта модель осталась без изменений.

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

Создание фабрики (factory)

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

Фабрика классов сама по себе специфична для поставщика. Например, поставщик данных SQL Server включает класс по имени System.Data.SqlClient.SqlClientFactory. Поставщик Oracle использует System.Data.OracleClient.OracleClientFactory. На первый взгляд может показаться, что это исключает написание независимого от поставщика кода. Однако на самом деле существует полностью стандартизованный класс, который предназначен для динамического нахождения и создания необходимой фабрики. Это класс System.Data.Common.DbProviderFactories. Он предоставляет статический метод GetFactory(), возвращающий фабрику на основе имени поставщика.

Например, вот как выглядит код, использующий DbProviderFactories для получения SqlClientFactory:

string factory = "System.Data.SqlClient";
DbProviderFactory provider = DbProviderFactories.GetFactory(factory);

Несмотря на то что класс DbProviderFactories возвращает строго типизированный объект SqlClientFactory, его не следует трактовать как таковой. Вместо этого код должен обращаться к нему как к экземпляру DbProviderFactory. Причина в том, что все фабрики унаследованы от DbProviderFactory. Если вы используете только члены DbProviderFactory, то сможете написать код, работающий с любой фабрикой.

Недостаток показанного выше фрагмента кода в том, что методу DbProviderFactories.GetFactory() приходится передавать строку, идентифицирующую поставщика. Обычно эта строка будет читаться из настроек приложения в файле web.config. Таким образом, можно писать полностью независимый от базы данных код и легко переключать приложение на использование другого поставщика простой модификацией единственного параметра настройки.

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

Чтобы класс DbProviderFactories работал, поставщик должен зарегистрировать фабрику в конфигурационном файле machine.config или web.config. В файле machine.config регистрируются четыре поставщика, включенные в состав .NET Framework:

<system.data>
    <DbProviderFactories>
      <add name="Odbc Data Provider" invariant="System.Data.Odbc" 
      		description=".Net Framework Data Provider for Odbc" 
            type="System.Data.Odbc.OdbcFactory, System.Data, ..." />
      <add name="OleDb Data Provider" invariant="System.Data.OleDb" 
      		description=".Net Framework Data Provider for OleDb" 
            type="System.Data.OleDb.OleDbFactory, System.Data, ..." />
      <add name="OracleClient Data Provider" invariant="System.Data.OracleClient" 
      		description=".Net Framework Data Provider for Oracle" 
            type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient, ..." />
      <add name="SqlClient Data Provider" invariant="System.Data.SqlClient" 
      		description=".Net Framework Data Provider for SqlServer" 
            type="System.Data.SqlClient.SqlClientFactory, System.Data, ..." />
    </DbProviderFactories>
</system.data>

На этом шаге регистрации идентифицируется класс фабрики и назначается уникальное имя поставщику (которое, по соглашению, совпадает с названием пространства имен поставщика). При наличии поставщика данных от независимых разработчиков, который должен использоваться, необходимо зарегистрировать его в разделе <DbProviderFactories> файла machine.config (чтобы сделать его доступным на конкретном компьютере), либо в web.config (чтобы обеспечить доступ к нему из конкретного веб-приложения). Весьма вероятно, что лицо или компания, разработавшая поставщика, предоставит программу настройки для автоматизации этой задачи или явно описанный синтаксис конфигурации.

Создание объектов с помощью фабрики

Получив в свое распоряжение фабрику, вы можете создавать другие объекты, такие как экземпляры Connection и Command, используя для этого методы DbProviderFactory.Create[...](). Например, метод CreateConnection() возвращает объект Connection для поставщика данных. Опять-таки, вы должны исходить из того, что не знаете, какой именно поставщик будет использован, поэтому взаимодействовать с объектами, созданными фабрикой, можно только через стандартный базовый класс.

Как объяснялось в предыдущих статьях, специфичные для поставщика объекты также реализуют определенные интерфейсы (вроде IDbConnection). Однако поскольку некоторые объекты используют более одного интерфейса ADO.NET (например, DataReader реализует как IDataRecord, так и IDataReader), применение базового класса упрощает эту модель.

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

Интерфейсы для стандартных объектов ADO.NET
Тип объекта Базовый класс Пример Метод DbProviderFactory
Connection DbConnection SqlConnection, OracleConnection CreateConnection()
Command DbCommand SqlCommand, OracleCommand CreateCommand()
Parameter DbDataParameter SqlParameter, OracleParameter CreateParameter()
DataReader DbDataReader SqlDataReader, OracleDataReader Отсутствует (вместо него используйте IDbCommand.ExecuteReader())
DataAdapter DbDataAdapter SqlDataAdapter, OracleDataAdapter CreateDataAdapter()

Запрос с независимым от поставщика кодом

Чтобы лучше понять, как все это работает вместе, рассмотрим простой пример. В этом разделе будет показано, как выполнить запрос и отобразить результаты, используя независимый от поставщика код. Первый шаг предусматривает настройку в файле web.config строки соединения, имени поставщика и текста запроса для примера:

<configuration>
  <!-- ... -->

  <connectionStrings>
    <add name="Northwind" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True"/>
  </connectionStrings>
  <appSettings>
    <add key="factory" value="System.Data.SqlClient"/>
    <add key="employeeQuery" value="SELECT * FROM Employees"/>
  </appSettings>
</configuration>

А вот так выглядит код, связанный с фабрикой:

protected void Page_Load(object sender, EventArgs e)
{
        // Получить фабрику
        string factory = WebConfigurationManager.AppSettings["factory"];
        DbProviderFactory provider = DbProviderFactories.GetFactory(factory);

        // Использовать фабрику для получения соединения
        DbConnection con = provider.CreateConnection();
        con.ConnectionString = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

        // Создать команду
        DbCommand cmd = provider.CreateCommand();
        cmd.CommandText = WebConfigurationManager.AppSettings["employeeQuery"];
        cmd.Connection = con;
        
        // Открыть соединение и получить данные
        using (con)
        {
            con.Open();
            DbDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                Label1.Text += String.Format("Имя сотрудника: <b>{0}</b>, фамилия: <b>{1}</b><br>",
                    reader["FirstName"], reader["LastName"]);
            }
        }
}

Для тестовой базы данных Northwind поставщика SQL Server этот код отобразит:

Создание запроса с помощью фабрики

Чтобы протестировать этот пример, попробуйте модифицировать файл web.config для использования другого поставщика. Например, если вы применяете SQL Server 2005 или выше, то можете обратиться к той же базе данных через поставщик OLE DB, внеся следующее изменение:

<configuration>
  <!-- ... -->

  <connectionStrings>
    <add name="Northwind"
    	connectionString="Provider=SQLOLEDB;Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True"/>
  </connectionStrings>
  <appSettings>
    <add key="factory" value="System.Data.OleDb"/>
    <add key="employeeQuery" value="SELECT * FROM Employees"/>
  </appSettings>
</configuration>

Теперь, когда вы запустите страницу, то увидите тот же список записей. Разница в том, теперь класс DbDataFactories создает объекты OLE DB для работы с вашим кодом.

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

Пройди тесты
Лучший чат для C# программистов