Опубликован: 07.05.2010 | Доступ: свободный | Студентов: 1678 / 62 | Оценка: 4.56 / 4.06 | Длительность: 34:11:00
Лекция 12:

Компоненты данных ADO.NET

Класс DbDataAdapter

DataSet никогда не оставляет открытым соединение с базой данных и закрывает его автоматически сразу после пересылки данных. Он даже не связывается с источником данных напрямую, а только через промежуточный объект System.Data.Common.DbDataAdapter. Класс DbDataAdapter служит посредником между одним DataTable в DataSet и источником данных. DbDataAdapter наследует от базового класса System.Data.Common.DataAdapter и предоставляет три ключевых метода, приведенные в таблице

Некоторые методы System.Data.Common.DbDataAdapter
Метод Описание
Fill() Заполняет данными DataSet за счет выполнения запроса в свойстве SelectCommand. Если запрос возвращает множественные результирующие наборы, то этот метод добавит множество объектов DataTable за одно обращение. Его можно также использовать для заполнения данными одного существующего объекта DataTable
FillSchema() Заполняет DataSet информацией о структуре таблицы или множества таблиц
Update() Обновляет данные из DataSet в источник данных (базу данных)

Чтобы позволить DbDataAdapter изменять данные в источнике, нужно специфицировать объекты System.Data.Common.DbCommand для свойств UpdateCommand, InsertCommand, DeleteCommand объекта DbDataAdapter. Чтобы использовать DbDataAdapter для наполнения DataSet, потребуется установить свойство SelectCommand.

Класс System.Data.Common.DbCommand является базовым для более специализированных классов

  • System.Data.SqlClient.SqlDataAdapter
  • System.Data.Odbc.OdbcDataAdapter
  • System.Data.OleDb.OleDbDataAdapter
  • System.Data.OracleClient.OracleDataAdapter

которые в конечном итоге мы и должны использовать в своих приложениях.

Пример использования DataAdapter и DataSet для извлечения автономных данных

Продемонстрируем на простом примере извлечения данных в результирующий набор DataSet через DataAdapter. Данные будем извлекать из таблицы Employees учебной базы данных Northwind, поддерживаемой SQL Server. При этом DataSet автоматически создаст соединение, добавит в свою коллекцию DataTables объект DataTable, в котором каждую запись разместит в отдельном объекте DataRow коллекции DataRows. После этого соединение с базой данных будет автоматически разорвано.

  • Добавьте к корневому узлу приложения страницу TestDataSet.aspx с разделяемым кодом и сделайте ее стартовой
  • Поместите на страницу элемент управления Label с именем lblInfo
  • Откройте файл поддержки TestDataSet.aspx.cs и наполните его следующим кодом
using System;
using System.Data;
    
using System.Web.Configuration;
using System.Data.SqlClient;
using System.Text;
    
public partial class TestDataSet : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Извлекаем строку соединения с именем Northwind из файла web.config
        string connectionString = WebConfigurationManager.
            ConnectionStrings["Northwind"].ConnectionString;
				
        // Формируем строку SQL для выборки всех данных таблицы Employees
        string commandString = "SELECT * FROM Employees";
    
        // Создаем и настраиваем экземпляр класса SqlDataAdapter
        SqlDataAdapter adapter = 
            new SqlDataAdapter(commandString, connectionString);
    
        // Создаем объект DataSet результирующего набора данных
        DataSet dataset = new DataSet();
    
        // Безопасно заполняем данными объект DataTable с произвольным 
        // именем, например "EmployeesResult", созданного объекта DataSet
        try
        {
            adapter.Fill(dataset, "EmployeesResult");
        }
        catch
        {
            throw new ApplicationException("Ошибка данныx.");
        }
    
        // Перебираем все объекты DataRow с полученным записами
        StringBuilder htmlStr = new StringBuilder("");
        foreach (DataRow dr in dataset.Tables["EmployeesResult"].Rows)
        {
            htmlStr.Append("<li>");
            htmlStr.Append(dr["TitleOfCourtesy"].ToString());
            htmlStr.Append(" <b>");
            htmlStr.Append(dr["FirstName"].ToString());
            htmlStr.Append("</b>, ");
            htmlStr.Append(dr["LastName"].ToString());
            htmlStr.Append("</li>");
        }
    
        // Отображаем полученные данные
        lblInfo.Text = "<h2>Список сотрудников</h2>";
        lblInfo.Text += htmlStr.ToString();
    }
}

Строка

adapter.Fill(dataset, "EmployeesResult");

выполняет строку запроса и помещает результат в новый именованный объект DataTable коллекции DataTables объекта dataset класса DataSet. Мы указали явно имя создаваемого объекта DataTable, которое выбрали произвольно. Если этого не сделать, автоматически будет назначено имя по умолчанию.

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

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

  • Исполните страницу TestDataSet.aspx и получите такой результат

Список сотрудников

  • Ms. Nancy, Davolio
  • Dr. Andrew, Fuller
  • Ms. Janet, Leverling
  • Mrs. Margaret, Peacock
  • Mr. Steven, Buchanan
  • Mr. Michael, Suyama
  • Mr. Robert, King
  • Ms. Laura, Callahan
  • Ms. Anne, Dodsworth

Следует помнить, что объекты полученных данных существуют только на время жизни страницы. При следующем запросе всю работу по извлечению данных системы ASP.NET и ADO.NET будут повторять заново. Если выполнение запроса трудоемко, а данные используются в нескольких страницах, то их нужно сохранять либо в объекте Session, либо в объекте Cache.

Работа с множественными таблицами и отношениями в извлеченных автономных данных

Рассмотрим пример, в котором демонстрируется более интересное применение DataSet, которое в дополнение к представлению автономных данных использует отношения таблиц. Этот пример показывает, как извлекать некоторые записи из таблиц Categories и Products базы данных Northwind. В нем также показано, как создавать отношения между таблицами для организации простой навигации от записи о категории к ее дочерним записям о продуктах, чтобы создать простой отчет.

  • Добавьте к корневому узлу приложения страницу DataSetRelationShips.aspx с разделяемым кодом и сделайте ее стартовой
  • Поместите на страницу элемент управления Label с именем lblInfo
  • Откройте файл поддержки DataSetRelationShips.aspx.cs и наполните его следующим кодом
using System;
using System.Data;
    
using System.Web.Configuration;
using System.Data.SqlClient;
using System.Text;
    
public partial class DataSetRelationShips : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Извлекаем строку соединения с именем Northwind из файла web.config
        string connectionString = WebConfigurationManager.
            ConnectionStrings["Northwind"].ConnectionString;
    
        // Создаем объект соединения
        SqlConnection con = new SqlConnection(connectionString);
    
        // Формируем строки SQL-запросов
        string sqlCategories = "SELECT CategoryID, CategoryName FROM Categories";
        string sqlProducts = "SELECT ProductName, CategoryID FROM Products";
    
        // Создаем объект DataAdapter
        SqlDataAdapter adapter = new SqlDataAdapter(sqlCategories, con);
    
        // Создаем пустой объект DataSet набора данных
        DataSet dataset = new DataSet();
    
        // Выполняем два запроса к БД с открытием 
        // и закрытием соединения вручную.
        // Возможные исключения не обрабатываем, а просто подавляем
        try
        {
            con.Open();
            // Наполнить DataSet данными из таблицы Categories
            // с именованной меткой CatTable
            adapter.Fill(dataset, "CatTable");
            // Сменить команду и добавить в DataSet данные
            // с именованной меткой ProdTable из таблицы Products
            adapter.SelectCommand.CommandText = sqlProducts;
            adapter.Fill(dataset, "ProdTable");
        }
        finally
        {
            con.Close();
        }
    
        // Определение отношения между  извлеченными в DataSet
        // именованными данными CatTable и ProdTable
        DataRelation relation = new DataRelation(
            "CatProd",                                          // Имя отношения
            dataset.Tables["CatTable"].Columns["CategoryID"],   // Родительская таблица
            dataset.Tables["ProdTable"].Columns["CategoryID"]   // Дочерняя таблица
                                                    );
        // Добавление отношения в коллекцию отношений DataSet
        dataset.Relations.Add(relation);
    
        // Перебираем все извлеченные категории продуктов
        // и для каждой из них собираем сопоставленные продукты
        StringBuilder htmlStr = new StringBuilder("");
        foreach (DataRow row in dataset.Tables["CatTable"].Rows)
        {
            htmlStr.Append("<b>");
            htmlStr.Append(row["CategoryName"].ToString());         // Имя поля
            htmlStr.Append("</b>");
    
            // Собираем дочерние записи из ProdTable для 
            // текущего значения родителя CatTable в массив
            DataRow[] childRows = row.GetChildRows(relation);
            htmlStr.Append("<ul>"); // Открыли маркированный список HTML
            foreach (DataRow childRow in childRows)
            {
                htmlStr.Append("<li>"); // Элемент маркированного списка HTML
                htmlStr.Append(childRow["ProductName"].ToString());  // Имя поля
                htmlStr.Append("</li>");
            }
            htmlStr.Append("</ul>"); // Закрыли маркированный список HTML
        }
    
        // Отображаем полученные данные ненавистному пользователю
        lblInfo.Text = htmlStr.ToString();
    }
}

Пояснения к коду страницы DataSetRelationShips.aspx

Прежде всего мы извлекаем из конфигурационного файла строку соединения и создаем для нее объект соединения. Затем заготавливаем строки SQL-запросов для извлечения данных из двух таблиц. Создаем объект DataAdapter для будущего подключения и извлечения данных из первой таблицы. Вручную открываем соединение с базой данных и извлекаем последовательно данные из двух таблиц в именованные объекты DataTable коллекции DataTables предварительно созданного объекта DataSet.

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

Таблицы Categories и Products связаны в базе данных по ключевому полю CategoryID отношением "один в Categories ко многим в Products ". Это поле является первичным ключем таблицы Categories и внешним ключем таблицы Products. Это отношение не передается в объект DataSet из базы данных автоматически и мы вынуждены сами повторять его в коде для связывания по столбцам извлеченных данных.

Именованное отношение создается путем определения объекта DataRelation и добавления его в коллекцию отношений объекта DataSet. При создании объекта DataRelation в его конструкторе мы указываем три параметра: имя отношения, имя поля первичного ключа таблицы-родителя, имя поля внешнего ключа дочерней таблицы.

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

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

Один из способов обойти эту проблему - создать DataRelation с перегруженным конструктором, имеющим четвертый булевский параметр createConstraints, которому нужно задать значение false. Другой подход состоит в отключении способности DataSet проверять целостность данных, в том числе целостность отношений. Это нужно сделать перед добавлением в него отношения установкой свойства DataSet.EnforceConstraints в значение false.

Долее мы работаем с автономными данными DataSet в отсоединенном режиме. Для выборки данных из виртуального источника данных DataSet мы организуем два цикла полного перебора элементов.

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

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

  • Исполните страницу DataSetRelationShips.aspx и получите следующий результат

Beverages

  • Chai
  • Chang
  • Guaranс Fantсstica
  • Sasquatch Ale
  • Steeleye Stout
  • CЇte de Blaye
  • Chartreuse verte
  • Ipoh Coffee
  • Laughing Lumberjack Lager
  • Outback Lager
  • RhЎnbrфu Klosterbier
  • LakkalikЎЎri

Condiments

  • Aniseed Syrup
  • Chef Anton's Cajun Seasoning
  • Chef Anton's Gumbo Mix
  • Grandma's Boysenberry Spread
  • Northwoods Cranberry Sauce
  • Genen Shouyu
  • Gula Malacca
  • Sirop d'щrable
  • Vegie-spread
  • Louisiana Fiery Hot Pepper Sauce
  • Louisiana Hot Spiced Okra
  • Original Frankfurter gr№ne So-e

Confections

  • Pavlova
  • Teatime Chocolate Biscuits
  • Sir Rodney's Marmalade
  • Sir Rodney's Scones
  • NuNuCa Nu--Nougat-Creme
  • Gumbфr Gummibфrchen
  • Schoggi Schokolade
  • Zaanse koeken
  • Chocolade
  • Maxilaku
  • Valkoinen suklaa
  • Tarte au sucre
  • Scottish Longbreads

Dairy Products

  • Queso Cabrales
  • Queso Manchego La Pastora
  • Gorgonzola Telino
  • Mascarpone Fabioli
  • Geitost
  • Raclette Courdavault
  • Camembert Pierrot
  • Gudbrandsdalsost
  • Flotemysost
  • Mozzarella di Giovanni

Grains/Cereals

  • Gustaf's KnфckebrЎd
  • TunnbrЎd
  • Singaporean Hokkien Fried Mee
  • Filo Mix
  • Gnocchi di nonna Alice
  • Ravioli Angelo
  • Wimmers gute SemmelknЎdel

Meat/Poultry

  • Mishi Kobe Niku
  • Alice Mutton
  • Th№ringer Rostbratwurst
  • Perth Pasties
  • Tourtiшre
  • Pтtщ chinois

Produce

  • Uncle Bob's Organic Dried Pears
  • Tofu
  • RЎssle Sauerkraut
  • Manjimup Dried Apples
  • Longlife Tofu

Seafood

  • Ikura
  • Konbu
  • Carnarvon Tigers
  • Nord-Ost Matjeshering
  • Inlagd Sill
  • Gravad lax
  • Boston Crab Meat
  • Jack's New England Clam Chowder
  • Rogede sild
  • Spegesild
  • Escargots de Bourgogne
  • RЎd Kaviar