Генератор поставщиков данных

79

Генератор поставщиков данных .NET позволяет создать единую кодовую базу с помощью обобщенных типов доступа к данным. Более того, посредством конфигурационных файлов приложения (и подэлемента <connectionStrings>) можно получить поставщики и строки подключения без необходимости в повторной компиляции или развертывания сборки, в которой используются API ADO.NET.

Чтобы разобраться в реализации генератора поставщиков данных, вспомните, что все классы в поставщике данных порождены от одних и тех же базовых классов, определенных в пространстве имен System.Data.Common:

DbCommand

абстрактный базовый класс для всех объектов команд;

DbConnection

абстрактный базовый класс для всех объектов подключений;

DbDataAdapter

абстрактный базовый класс для всех объектов адаптеров данных;

DbDataReader

абстрактный базовый класс для всех объектов чтения данных;

DbParameter

абстрактный базовый класс для всех объектов параметров;

DbTransaction

абстрактный базовый класс для всех объектов транзакций.

Все поставщики данных, разработанные Microsoft, содержат класс, порожденный от System.Data.Common.DbProviderFactory. В этом базовом классе определен ряд методов, которые выбирают объекты данных, характерные для конкретных поставщиков.

Для получения типа, порожденного от DbProviderFactory, непосредственно для вашего поставщика данных в пространстве имен System.Data.Common имеется класс DbProviderFactories. С помощью метода GetFactory() можно получить конкретный объект DbProviderFactory для указанного поставщика данных. Для этого нужно указать строковое имя, которое представляет пространство имен .NET, содержащее функциональность поставщика:

DbProviderFactory sqlFactory = 
      DbProviderFactories.GetFactory("System.Data.SqlClient");

Разумеется, генератор можно получить не с помощью жестко закодированного строкового литерала, а, например, прочитать эту информацию из клиентского файла *.config (приблизительно так же, как в предыдущем примере с MyConnectionFactory). Вскоре вы увидите, как это сделать, а пока после получения генератора для поставщика данных можно получить связанные с ним объекты данных (например, подключения, команды и объекты чтения данных).

Для всех практических целей можно рассматривать аргумент, передаваемый в DbProviderFactories.GetFactory(), как имя пространства имен .NET для поставщика данных. В реальности это строковое значение используется в значении machine.config для динамической загрузки нужной библиотеки из глобального кэша сборок.

Для примера мы создадим новое консольное C#-приложение с именем DataProviderFactory, которое выводит список всех автомобилей из базы данных AutoLot. Поскольку это первый пример, мы жестко закодируем логику доступа к данным непосредственно в сборке DataProviderFactory.exe (чтобы пока не усложнять программирование). Но когда мы начнем разбираться в деталях модели программирования ADO.NET, мы изолируем логику данных в специальную кодовую библиотеку .NET, которая будет использоваться на протяжении всего оставшегося текста.

Вначале добавьте ссылку на сборку System.Configuration.dll и импортируйте пространство имен System.Configuration. Затем добавьте файл Арр.config в текущий проект и определите пустой элемент <appSettings>. Добавьте новый ключ по имени provider, который отображает в пространство имен нужное имя поставщика данных (System.Data.SqlClient). Кроме того, определите строку подключения, которая описывает подключение к базе данных AutoLot (с помощью локального экземпляра SQL Server Express):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Поставщик -->
    <add key="provider" value="System.Data.SqlClient"/>
    <!-- Строка подключения -->
    <add key="cnStr" value="Data Source=MICROSOF-1EA29E\SQLEXPRESS;
         Initial Catalog=AutoLot;Integrated Security=True;Pooling=False"/>
  </appSettings>
</configuration>

Чуть ниже мы рассмотрим строки подключения подробнее. А пока учтите, что если выбрать в Server Explorer значок базы данных AutoLot, то можно скопировать и вставить правильную строку подключения из свойства Connection String (Строка подключения) в окне Properties (Свойства) Visual Studio 2010.

Теперь у вас есть корректный файл *.config, и вы можете прочитать значения provider и cnStr с помощью индексатора ConfigurationManager.AppSettings.

Значение provider нужно передать в метод DbProviderFactories.GetFactory(), чтобы получить тип генератора для необходимого поставщика данных. Значение cnStr будет использовано для установки свойства ConnectionString в типе, порожденном от DbConnection.

Если вы импортировали пространства имен System.Data и System.Data.Common, метод Main() можно изменить следующим образом:

static void Main(string[] args)
        {
            // Получение строки подключения и поставщика из *.config
            string dp = ConfigurationManager.AppSettings["provider"];
            string cnStr = ConfigurationManager.AppSettings["cnStr"];

            // Получение генератора поставщика
            DbProviderFactory df = DbProviderFactories.GetFactory(dp);

            // Получение объекта подключения
            using (DbConnection cn = df.CreateConnection())
            {
                Console.WriteLine("Объект подключения --> " + cn.GetType().Name);
                cn.ConnectionString = cnStr;
                cn.Open();

                // Создание объекта команды
                DbCommand cmd = df.CreateCommand();
                Console.WriteLine("Объект команды --> " + cmd.GetType().Name);
                cmd.Connection = cn;
                cmd.CommandText = "Select * From Inventory";

                // Вывод данных с помощью объекта чтения данных
                using (DbDataReader dr = cmd.ExecuteReader())
                {
                    Console.WriteLine("Объект чтения данных --> " + dr.GetType().Name);
                    Console.WriteLine("\n*** Текущее содержимое Inventory ***\n");
                    while (dr.Read())
                        Console.WriteLine("-> Автомобиль #{0} - {1}\n",
                            dr["CarID"], dr["Make"].ToString());
                }
                Console.ReadLine();
            }

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

Вывод объектов подключения в консоль

Теперь укажите в файле *.config в качестве поставщика данных System.Data.OleDb (и измените строку подключения и сегмент Provider):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Поставщик -->
    <add key="provider" value="System.Data.OleDb"/>
    <!-- Строка подключения -->
    <add key="cnStr" value="Provider=SQLOLEDB;Data Source=MICROSOF-1EA29E\SQLEXPRESS;
         Initial Catalog=AutoLot;Integrated Security=True;Pooling=False"/>
  </appSettings>
</configuration>

Вы обнаружите, что неявно были задействованы типы System.Data.OleDb.

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

Это действительно очень мощная модель, но все же нужно проверить, что в кодовой базе используются только типы и методы, общие для всех поставщиков как потомков абстрактных базовых классов. Поэтому при разработке кодовой базы следует ограничиться членами из DbConnection, DbCommand и других типов из пространства имен System.Data.Common.

Но такой обобщенный подход не позволит непосредственно задействовать дополнительные возможности конкретной СУБД. Если все же потребуются вызовы специфических членов конкретного поставщика (например, SqlConnection), то это можно сделать с помощью явного преобразования типа, как в данном примере:

Console.WriteLine("Объект подключения --> " + cn.GetType().Name);
        cn.ConnectionString = cnStr;
        cn.Open();
        if (cn is SqlConnection)
        {
            // Вывод используемой версии SQL Server
            Console.WriteLine(((SqlConnection)cn).ServerVersion);
        }
...

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

Элемент <connectionStrings>

Пока наша строка подключения находится в элементе <appSettings> файла *.config. В конфигурационных файлах приложения может быть определен элемент <connectionStrings>. В этом элементе можно задать любое количество пар имя/значение, которые программа может прочитать в память с помощью индексатора ConfigurationManager.ConnectionStrings. Одним из преимуществ данного подхода (по сравнению с использованием элемента <appSettings> и индексатора ConfigurationManager.AppSettings) является то, что при этом можно однотипным образом определить несколько строк подключения для одного приложения.

Чтобы увидеть все это в действии, модифицируйте текущий файл Арр.config следующим образом (обратите внимание, что каждая строка подключения описывается с помощью атрибутов name и connectionString, а не атрибутов key и value, как в <appSettings>):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Поставщик -->
    <add key="provider" value="System.Data.SqlClient"/>
  </appSettings>
  <connectionStrings>
    <add name="AutoLotSqlProvider" connectionString="Data Source=MICROSOF-1EA29E\SQLEXPRESS;
         Initial Catalog=AutoLot;Integrated Security=True;Pooling=False"/>
    <add name="AutoLotOleDbProvider" connectionString="Provider=SQLOLEDB;Data Source=MICROSOF-1EA29E\SQLEXPRESS;
         Initial Catalog=AutoLot;Integrated Security=True;Pooling=False"/>
  </connectionStrings>
</configuration>

Теперь можно изменить метод Main():

// Получение строки подключения и поставщика из *.config
            string dp = ConfigurationManager.AppSettings["provider"];
            string cnStr = ConfigurationManager.ConnectionStrings["AutoLotSqlProvider"].ConnectionString;
            ...

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

Итак, первый пример закончен, и теперь можно углубиться в детали работы с подключенным уровнем ADO.NET. Теперь вы уже оценили роль генераторов поставщиков данных в ADO.NET, и в последующих примерах мы будем явно использовать типы из пространства имен System.Data.SqlClient, чтобы не отвлекаться от текущих задач. Если вы пользуетесь другой СУБД (например, Oracle), то вам надо будет соответствующим образом изменить кодовую базу.

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