Программирование на ASP.NET

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

Показывать лекцию целиком
Файлы к лекции Вы можете скачать здесь.

Как правило, в практических приложениях создание соединения и доступ к данным оформляется в виде отдельного класса, который называют компонентом данных. В него также включают класс DataSet, который загружает данные и играет роль источника данных даже после закрытия соединения с базой данных. Объект DataSet не является обязательным для страниц ASP.NET, однако он предоставляет большую гибкость в работе с данными, включая навигацию, фильтрацию и сортировку.

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

Построение компонента доступа к данным

При создании хорошо спроектированного класса-компонента данных нужно следовать некоторым рекомендациям:

  • Быстрое открытие и закрытие соединения. Соединения никогда не должны удерживаться открытыми и клиент никогда не должен ими управлять. Удерживаемое открытым соединение занимает ресурсы, что пагубно влияет на масштабируемость (мало клиентов можно обслужить одновременно).
  • Детальная обработка ошибок. При любом исключении нужно гарантированно закрыть соединение с базой данных и известить об этом пользователя.
  • Практика дизайна без сохранения состояния. Передавать всю необходимую методу информацию в его входных параметрах и возвращать извлеченные данные через выходные параметры метода.
  • Запрет для клиента указывать параметры строки соединения. Это нарушает безопасность и ослабляет использование пулов соединений, которые требуют точных совпадений в повторных соединениях.
  • Исключение возможности соединения под клиентским идентификатором пользователя. Аутентификацию и ограничение прав пользователя нужно выявлять отдельно и заранее, а затем подключаться к базе данных от своего имени с последующим формированием нужного SQL-запроса. Это исключит возможность вариации параметров в строке соединений и сохранит работу механизма пула и работает быстрее, чем попытка выполнения запроса от имени некорректной учетной записи с ожиданием ошибки.
  • Ограничение количества информации, предоставляемой пользователю за один запрос. Каждый запрос пользователя должен благоразумно выбирать лишь ту информацию, которая ему действительно нужна. Необходимо интенсивно использовать в SQL-запросах конструкцию WHERE или TOP, ограничивающие диапазон выборки данных. Это разгрузит как саму базу данных, так и сеть передачи данных клиенту.
  • По-возможности использовать отдельный класс для каждой таблицы базы данных или логически связанной группы таблиц.
  • Запросы к базе данных выполнять через соответствующие тщательно разработанные и отлаженные хранимые процедуры. Это обеспечит должную безопасность и быстродействие.
  • На следующем рисунке показан рекомендуемый многослойный дизайн работы с базой данных.

    Реализуем этот простой дизайн в непростом примере. Разработаем страницу, в которой будем работать с таблицей Employees учебной базы данных Northwind.

  • Создайте новый сайт командой File/New/Web Site с именем
  • Мы хотим создать структурированный код в виде компонента данных, который будет состоять из класса-оболочки доступа к полям таблицы и класса обработки записей таблицы. Хранимые процедуры записываются в базу данных один раз на этапе проектирования, но мы создадим отдельную страницу, через которую запишем хранимые процедуры программно, выполнив ее только один раз.

    Создание страницы для одноразовой записи хранимых процедур

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

  • Добавьте к приложению командой Website/Add Existing Item страницу TestStoredProcedure.aspx из каталога WebSite7 и переименуйте ее в SaveStoredProcedure.aspx
  • Аналогичным образом добавьте к приложению файл Web.Config из каталога WebSite7 с готовой строкой соединения и откорректируйте его так
    (рис ) Файл Web.Config<?xml version="1.0"?>
    <configuration>
    	<connectionStrings>
    		<add name="Northwind" connectionString="Data Source=localhost; 
       Initial Catalog=Northwind; user id=sa; password=;" />
    	</connectionStrings>
    	<system.web>
    		<compilation debug="true"/>
    	</system.web>
    </configuration>
  • Откорректируйте страницу SaveStoredProcedure.aspx следующим образом
    (рис ) Код страницы SaveStoredProcedure.aspx одноразовой записи хранимых процедур<%@ Page Language="C#" %>
        
    <script runat="server">
        
        // Объявления хранимых процедур
        // Вставка записи
        string sql1 =
            "CREATE PROCEDURE InsertEmployee "
          + "@EmployeeID		int OUTPUT,"
          + "@FirstName		varchar(10),"
          + "@LastName		varchar(20),"
          + "@TitleOfCourtesy	varchar(25) "
          + "AS "
          + "INSERT INTO Employees "
          + "(TitleOfCourtesy, LastName, FirstName, HireDate) "
          + "VALUES(@TitleOfCourtesy, @LastName, @FirstName, GETDATE()) "
          + "SET @EmployeeID = @@IDENTITY";
        
        // Удаление записи
        string sql2 =
            "CREATE PROCEDURE DeleteEmployee "
          + "@EmployeeID		int "
          + "AS "
          + "DELETE FROM Employees WHERE EmployeeID = @EmployeeID";
        
        // Обновление записи
        string sql3 =
            "CREATE PROCEDURE UpdateEmployee "
          + "@EmployeeID		int, "
          + "@FirstName		varchar(10),"
          + "@LastName		varchar(20),"
          + "@TitleOfCourtesy	varchar(25) "
          + "AS "
          + "UPDATE Employees "
          + "SET "
          + "FirstName = @FirstName,"
          + "LastName = @LastName,"
          + "TitleOfCourtesy = @TitleOfCourtesy "
          + "WHERE EmployeeID = @EmployeeID";
        
        // Выбрать все
        string sql4 =
            "CREATE PROCEDURE GetAllEmployees "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, TitleOfCourtesy "
          + "FROM Employees";
        
        // Подсчитать число записей
        string sql5 =
            "CREATE PROCEDURE CountEmployees "
          + "AS "
          + "SELECT COUNT(EmployeeID) "
          + "FROM Employees";
        
        // Выбрать запись
        string sql6 =
            "CREATE PROCEDURE GetEmployee "
          + "@EmployeeID		int "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, TitleOfCourtesy "
          + "FROM Employees "
          + "WHERE EmployeeID = @EmployeeID";
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Извлекли строку соединения из web.config
            string connectString = System.Web.Configuration.WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
        
            // Создали объект соединения
            System.Data.SqlClient.SqlConnection con =
                new System.Data.SqlClient.SqlConnection(connectString);
        
            // Создали объект Command
            System.Data.SqlClient.SqlCommand cmd =
                new System.Data.SqlClient.SqlCommand();
        
            // Настроили объект Command
            cmd.Connection = con;
            cmd.CommandType = System.Data.CommandType.Text;
        
            lblInfo.Text = "";
        
            // Открываем соединение в безопасном режиме
            try
            {
                con.Open();
            }
            catch
            {
                con.Close();
                return;
            }
        
            // Выполняем команды в безопасном режиме
            // Удаляем процедуры на случай повторного запуска этой страницы
            try
            {
                cmd.CommandText = "DROP PROCEDURE InsertEmployee";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE InsertEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE DeleteEmployee";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROC DeleteEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE UpdateEmployee";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE UpdateEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE GetAllEmployees";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE GetAllEmployees\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE CountEmployees";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE CountEmployees\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE GetEmployee";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE GetEmployee\"<br />";
            }
            catch { }
        
            // Выполняем команды в безопасном режиме
            // Добавляем в базу данных хранимые процедуры
            try
            {
                cmd.CommandText = sql1;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE InsertEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql2;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE DeleteEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql3;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE UpdateEmployee\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql4;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetAllEmployees\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql5;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE CountEmployees\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql6;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetEmployee\"<br />";
        
            }
            catch { }
            finally
            {
                con.Close();
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:Label ID="lblInfo" runat="server" />
        </form>
    </body>
    </html>
  • Выполните страницу SaveStoredProcedure.aspx, чтобы добавить в базу хранимые процедуры, которые мы далее будем использовать при разработке примера
  • Добавление класса-оболочки для доступа к полям данных

    Для облегчения доступа к данным таблицы Employees учебной базы данных Northwind создадим класс-оболочку с именем EmployeeDetails, который представит нужные нам поля в виде одноименных открытых свойств.

  • Через контекстное меню корня Web-дерева создайте каталог с предопределенным именем
  • Вызовите контекстное меню для созданного каталога
  • Заполните созданную оболочкой заготовку класса следующим кодом
    (рис ) Код класса EmployeeDetails.csusing System;
        
    public class EmployeeDetails
    {
        // Общий конструктор
        public EmployeeDetails(int employeeID, string firstName, 
            string lastName, string titleOfCourtesy)
        {
            this.employeeID = employeeID;
            this.firstName = firstName;
            this.lastName = lastName;
            this.titleOfCourtesy = titleOfCourtesy;
        }
        
        // Конструктор по умолчанию обязателен, 
        // если создали общий конструктор
        public EmployeeDetails()
        {
        }
        
        // Добавляем свойства класса
        private int employeeID;
        public int EmployeeID
        {
            get { return employeeID; }
            set { employeeID = value; }
        }
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set { firstName = value; }
        }
        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set { lastName = value; }
        }
        private string titleOfCourtesy;
        public string TitleOfCourtesy
        {
            get { return titleOfCourtesy; }
            set { titleOfCourtesy = value; }
        }
    }
  • Добавление класса-оболочки для операций с данными

    Создадим класс, который в своих методах использует записанные нами ранее хранимые процедуры SQL-запросов к таблице Employees учебной базы данных Northwind. Воспользуемся новым средством частичных классов ( partial ) версии языка C#2.0 и разместим класс с операциями в нескольких файлах, в каждом из которых реализуем один специфический метод.

  • Вызовите контекстное меню для созданного каталога App_Code и выполните команду Add New Item, чтобы добавить в приложение класс C# с именем EmployeeDB в файле EmployeeDB.cs
  • Из объявлений пространств имен using, автоматически сгенерированных оболочкой, оставьте только using System; и добавьте в заголовок объявления класса ключевое слово partial (частичный), чтобы дать указание компилятору считать одноименные классы в отдельных файлах единым классом (нам так удобнее разместить код отдельных методов)
  • Сделайте в каталоге App_Code шесть копий файла EmployeeDB.cs с именами
  • InsertEmployeeDB.cs
  • DeleteEmployeeDB.cs
  • UpdateEmployeeDB.cs
  • GetAllEmployeeDB.cs
  • CountEmployeeDB.cs
  • GetEmployeeDB.cs
  • Заполните первую часть заготовки класса EmployeeDB в файле EmployeeDB.cs следующим кодом
    (рис ) Часть класса EmployeeDB в файле EmployeeDB.csusing System;
        
    using System.Web.Configuration;
        
    public partial class EmployeeDB
    {
        private string connectionString;
        public EmployeeDB()
        {
            // Извлечь из файла web.config строку соединения по умолчанию
            connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
        }
        
        public EmployeeDB(string connectionStringCustom)
        {
            // Извлечь из файла web.config другую строку соединения
            connectionString = WebConfigurationManager.
                ConnectionStrings[connectionStringCustom].ConnectionString;
        }
    }
  • Обратите внимание, что мы по ходу дела добавляем к коду необходимые пространства имен инструкцией using.

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

    using System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        public int InsertEmployee(EmployeeDetails emp)
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("InsertEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("@FirstName", SqlDbType.NVarChar, 10));
            cmd.Parameters["@FirstName"].Value = emp.FirstName;
            cmd.Parameters.Add(new SqlParameter("@LastName", SqlDbType.NVarChar, 20));
            cmd.Parameters["@LastName"].Value = emp.LastName;
            cmd.Parameters.Add(new SqlParameter("@TitleOfCourtesy", SqlDbType.NVarChar, 25));
            cmd.Parameters["@TitleOfCourtesy"].Value = emp.TitleOfCourtesy;
            cmd.Parameters.Add(new SqlParameter("@EmployeeID", SqlDbType.Int, 4));
            cmd.Parameters["@EmployeeID"].Direction = ParameterDirection.Output;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
                return (int)cmd.Parameters["@EmployeeID"].Value;
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
    using System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        public void DeleteEmployee(int employeeID)
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("DeleteEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("@EmployeeID", SqlDbType.Int, 4));
            cmd.Parameters["@EmployeeID"].Value = employeeID;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
    using System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        public void UpdateEmployee(int employeeID, string firstName,
                                   string lastName, string titleOfCourtesy)
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("UpdateEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("@EmployeeID", SqlDbType.Int, 4));
            cmd.Parameters["@EmployeeID"].Value = employeeID;
            cmd.Parameters.Add(new SqlParameter("@FirstName", SqlDbType.NVarChar, 10));
            cmd.Parameters["@FirstName"].Value = firstName;
            cmd.Parameters.Add(new SqlParameter("@LastName", SqlDbType.NVarChar, 20));
            cmd.Parameters["@LastName"].Value = lastName;
            cmd.Parameters.Add(new SqlParameter("@TitleOfCourtesy", SqlDbType.NVarChar, 25));
            cmd.Parameters["@TitleOfCourtesy"].Value = titleOfCourtesy;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
    using System;
    using System.Data;
        
    using System.Data.SqlClient;
    using System.Collections.Generic;
        
    public partial class EmployeeDB
    {
        public List<EmployeeDetails> GetAllEmployees()
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("GetAllEmployees", con);
            cmd.CommandType = CommandType.StoredProcedure;
        
            // Создать коллекцию для всех записей 
            List<EmployeeDetails> employees = new List<EmployeeDetails>();
        
            try
            {
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    EmployeeDetails emp = new EmployeeDetails(
                    (int)reader["EmployeeID"],
                    (string)reader["FirstName"],
                    (string)reader["LastName"],
                    (string)reader["TitleOfCourtesy"]);
                    employees.Add(emp);
                }
                reader.Close();
                return employees;
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
    using System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        public int CountEmployees()
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("CountEmployees", con);
            cmd.CommandType = CommandType.StoredProcedure;
        
            try
            {
                con.Open();
                return (int)cmd.ExecuteScalar();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
    using System;
    using System.Data;
            
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        public EmployeeDetails GetEmployee(int employeeID)
        {
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand("GetEmployee", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("@EmployeeID", SqlDbType.Int, 4));
            cmd.Parameters["@EmployeeID"].Value = employeeID;
        
            try
            {
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleRow);
                // Получить первую строку
                reader.Read();
                EmployeeDetails emp = new EmployeeDetails(
                    (int)reader["EmployeeID"],
                    (string)reader["FirstName"],
                    (string)reader["LastName"],
                    (string)reader["TitleOfCourtesy"]);
                reader.Close();
                return emp;
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }

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

    Мы видим, что каждый метод сам открывает и закрывает соединение с базой данных. Все тонкости работы с данными скрыты внутри методов. Наша задача теперь, в нужном месте создать экземпляр класса EmployeeDB и правильно вызывать его методы, соблюдая установленный в них интерфейс. Раз создав этот класс, его можно применять многократно по мере необходимости, не заботясь об инкапсулированных в нем тонкостях программирования. Мы же не знаем (да и не хотим знать), как реализованы библиотечные классы .NET Framework. Мы уверены, что они будут работать как надо, если правильно их использовать. Вот это и есть преимущество объектно-ориентированного программирования во всей его красе: обращайся правильно к интерфейсу класса - и все будет работать.

    Тестовая страница для испытания компонента доступа к данным

    Весь код класса-оболочки, который мы разработали выше, теперь нужно испытать на тестовой странице.

  • Добавьте к проекту (в корень Web-дерева) новую страницу с разделяемым кодом и именем TestComponent.aspx
  • Добавьте на страницу компонент Label и дайте ему имя lblInfo
  • Откройте на редактирование файл поддержки TestComponent.aspx.cs и наполните его следующим кодом
    (рис ) Файл поддержки TestComponent.aspx.cs тестовой страницы TestComponent.aspxusing System;
            
    using System.Text;
    using System.Collections.Generic;
            
    public partial class TestComponent : System.Web.UI.Page
    {
        // Создать компонент базы данных
        private EmployeeDB db = new EmployeeDB();
        
        protected void Page_Load(object sender, EventArgs e)
        {
            lblInfo.Text = "<h2>Исходная таблица</h2>";
            WriteEmployeesList();
            
            int empID = db.InsertEmployee(
                new EmployeeDetails(0, "Алексей", "Зиборов", "Ст."));
            lblInfo.Text += "<h2>Вставлена 1 запись.</h2>";
            WriteEmployeesList();
        
            db.DeleteEmployee(empID);
            lblInfo.Text += "<h2>Удалена 1 запись.</h2>";
            WriteEmployeesList();
        }
        
        private void WriteEmployeesList()
        {
            StringBuilder htmlStr = new StringBuilder("");
        
            List<EmployeeDetails> employees = db.GetAllEmployees();
            foreach (EmployeeDetails emp in employees)
            {
                htmlStr.Append("<li>");
                htmlStr.Append(emp.EmployeeID);
                htmlStr.Append(" ");
                htmlStr.Append(emp.TitleOfCourtesy);
                htmlStr.Append(" <b>");
                htmlStr.Append(emp.FirstName);
                htmlStr.Append("</b>, ");
                htmlStr.Append(emp.LastName);
                htmlStr.Append("</li>");
            }
              
            int numEmployees = db.CountEmployees();
            htmlStr.Append("<hr />Число записей: <b>");
            htmlStr.Append(numEmployees.ToString());
            htmlStr.Append("</b><br /><br />");
            lblInfo.Text += htmlStr.ToString();
        }
    }
  • Назначьте страницу TestComponent.aspx стартовой и исполните ее. Должен получиться примерно такой результат
  • Исходная таблица

  • 1 Ms. Nancy, Davolio
  • 2 Dr. Andrew, Fuller
  • 3 Ms. Janet, Leverling
  • 4 Mrs. Margaret, Peacock
  • 5 Mr. Steven, Buchanan
  • 6 Mr. Michael, Suyama
  • 7 Mr. Robert, King
  • 8 Ms. Laura, Callahan
  • 9 Ms. Anne, Dodsworth
  • Число записей: 9

    Вставлена 1 запись.

  • 1 Ms. Nancy, Davolio
  • 2 Dr. Andrew, Fuller
  • 3 Ms. Janet, Leverling
  • 4 Mrs. Margaret, Peacock
  • 5 Mr. Steven, Buchanan
  • 6 Mr. Michael, Suyama
  • 7 Mr. Robert, King
  • 8 Ms. Laura, Callahan
  • 9 Ms. Anne, Dodsworth
  • 99 Ст. Алексей, Зиборов
  • Число записей: 10

    Удалена 1 запись.

  • 1 Ms. Nancy, Davolio
  • 2 Dr. Andrew, Fuller
  • 3 Ms. Janet, Leverling
  • 4 Mrs. Margaret, Peacock
  • 5 Mr. Steven, Buchanan
  • 6 Mr. Michael, Suyama
  • 7 Mr. Robert, King
  • 8 Ms. Laura, Callahan
  • 9 Ms. Anne, Dodsworth
  • Число записей: 9

    Мы видим, что все работает как надо. Обратите внимание, что значение поля EmployeeID прирастает на 1 при повторных запусках тестовой страницы. Это значит, что физически запись не удаляется, а только помечается на удаление SQL-запросом DELETE, а поле имеет установку для СУБД SQL Server, которая является посредником в нашем коде доступа к данным, автоматически увеличивать значение счетчика.

    Библиотечный класс DataSet и автономные данные

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

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

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

    Некоторые методы DataSet
    Метод Описание
    Clear() Очищает все данные таблиц, но не трогает информацию о схеме и отношениях
    Copy() Возвращает точный дубликат DataSet с тем же набором таблиц, данных и отношений
    Clone() Возвращает DataSet с той же структурой (таблицами и отношениями), но без данных
    Merge() Принимает на входе другой DataSet и объединяет его с текущим DataSet, добавляя новые таблицы и объединяя данные в существующих
    GetXml(), GetXmlSchema() Возвращают строку данных в формате XML или информацию схемы для DataSet. Информация схемы - это структурированная информация наподобие количества таблиц, их имен, столбцов, типов данных и установленных отношений
    ReadXml(), ReadXmlSchema() Создают таблицы в DataSet на основе существующего документа XML или документа схемы XML. Источником XML может быть файл или другой поток
    WriteXml(), WriteXmlSchema() Сохраняют данные и схемы DataSet в файле или потоке формата XML

    Класс DataSet является сердцем автономного доступа к данным. Он содержит в себе в виде классов-свойств коллекцию из нуля или более таблиц и коллекцию из нуля или более отношений между таблицами. Базовая структура DataSet приведена на рисунке.

    Каждая запись DataSet представлена как объект DataRow. DataRow - это контейнер для действительных значений полей. К полям записи можно обращаться через объект DataRow как к ассоциативному массиву, используя в качестве ключа имена полей, например, myRow["FieldName"].

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

    Класс 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
  • Поиск определенных строк в извлеченных автономных данных

    Класс DataTable имеет метод Select(), позволяющий извлекать из заполненного DataSet массив объектов-строк DataRow по дополнительному условию на основе SQL-выражения. Чтобы проиллюстрировать этот метод, модифицируем последний пример.

  • Сделайте из страницы DataSetRelationShips.aspx копию с именем DataTableSelect.aspx
  • Заполните файл DataTableSelect.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 sqlProducts = "SELECT ProductName, Discontinued FROM Products";
        
            // Создаем и настраиваем объект DataAdapter
            SqlDataAdapter adapter = new SqlDataAdapter(sqlProducts, con);
        
            // Создаем пустой объект DataSet набора данных
            DataSet dataset = new DataSet();
        
            // Выполняем запрос к БД с автоматическим 
            // открытием и закрытием соединения
            try
            {
                // Добавить в DataSet данные с именованной
                // меткой ProdTable из таблицы Products
                adapter.Fill(dataset, "ProdTable");
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
        
            // Получить по дополнительному условию
            // массив продуктов, имеющих скидки
            DataRow[] matchRows = dataset.Tables["ProdTable"].
                Select("Discontinued <> 0"); // Дополнительное условие
        
            // Выбрать имена продуктов со скидками
            StringBuilder htmlStr = new StringBuilder("");
            htmlStr.Append("<ol>"); // Открыли нумерованный список HTML
            foreach (DataRow row in matchRows)
            {
                htmlStr.Append("<li>"); // Элемент маркированного списка HTML
                htmlStr.Append(row["ProductName"].ToString());  // Имя поля
                htmlStr.Append("</li>");
            }
            htmlStr.Append("</ol>"); // Закрыли нумерованный список HTML
        
            // Отображаем полученные данные пользователю
            lblInfo.Text = "<h2>Список продуктов,<br />имеющих скидки</h2>";
            lblInfo.Text += htmlStr.ToString();
        }
    }

    Приведенный код примера достаточно прост. Из данных, загруженных в DataSet по SQL-запросу, мы выбираем данные в массив строк по дополнительному условию.

  • Исполните страницу DataTableSelect.aspx, чтобы получить следующий результат
  • Список продуктов, имеющих скидки

  • Chef Anton's Gumbo Mix
  • Mishi Kobe Niku
  • Alice Mutton
  • Guaranс Fantсstica
  • RЎssle Sauerkraut
  • Th№ringer Rostbratwurst
  • Singaporean Hokkien Fried Mee
  • Perth Pasties
  • Первое знакомство с механизмом привязки извлекаемых автономных данных

    Иногда более удобно не самому формировать HTML-вывод для изъятых из базы автономных данных, как мы это делали до сих пор, а воспользоваться специальным механизмом привязки данных, который позаботится о правильном представлении данных пользователю. Самым простым в использовании для привязки и отображении данных является элемент управления GridView. Он автоматически формирует HTML-таблицы, в ячейки которых выводит данные, загруженные в набор данных DataSet. Порядок привязки набора данных dataset к экземпляру GridView1 класса GridView следующий:

  • Подключить набор данных
    GridView1.DataSource = dataset;
  • Указать именованную метку таблицы в наборе данных, подлежащих отображению
    GridView1.DataMember = "TableLabel";
  • Загрузить привязанные данные в конкретный элемент отображения или сразу во все элементы отображения страницы
    GridView1.DataBind();        или        Page.DataBind();
  • Если мы вызовем Page.DataBind(), то он пройдет по всем элементам управления страницы, поддерживающих привязку данных, и для каждого вызовет метод DataBind().

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

  • Добавьте к приложению WebSite8 новую страницу с раздельным кодом и именем GridViewEmployees.aspx. Назначьте эту страницу стартовой
  • Поместите на страницу из вкладки Data панели Toolbox элемент управления GridView с именем GridView1
  • Откройте на редактирование файл поддержки GridViewEmployees.aspx.cs и заполните его следующим кодом
    (рис ) Код файла GridViewEmployees.aspx.csusing System;
    using System.Data;
        
    using System.Web.Configuration;
    using System.Data.SqlClient;
        
    public partial class GridViewEmployees : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Содать Connection, DataAdapter и DataSet
            string connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
            SqlConnection con = new SqlConnection(connectionString);
            string sql = 
                "SELECT TOP 5 EmployeeID, TitleOfCourtesy, LastName, "
              + "FirstName FROM Employees";
            SqlDataAdapter adapter = new SqlDataAdapter(sql, con);
            DataSet dataset = new DataSet();
            adapter.Fill(dataset, "EmployeesTable");
        
            // Привязать данные к GridView1 для отображения
            GridView1.DataSource = dataset;
            GridView1.DataMember = "EmployeesTable";
            GridView1.DataBind();
        }
    }
  • Исполните страницу GridViewEmployees.aspx, чтобы получить следующий результат
  • EmployeeID TitleOfCourtesy LastName FirstName
    1 Ms. Davolio Nancy
    2 Dr. Fuller Andrew
    3 Ms. Leverling Janet
    4 Mrs. Peacock Margaret
    5 Mr. Buchanan Steven

    К элементу отображения можно привязать сразу конкретную таблицу из набора данных. В этом случае строки привязки будут выглядеть так (страница GridViewEmployees1.aspx )

    // Привязать данные к GridView1 для отображения
    GridView1.DataSource = dataset.Tables["EmployeesTable"];
    GridView1.DataBind();

    Более того, существует класс DataView, который является представлением класса DataTable, с помощью которого можно предварительно отсортировать или отфильтровать данные перед привязкой к элементу управления GridView для показа пользователю.

    Сортировка извлеченных автономных данных с помощью DataView

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

  • Скопируйте страницу GridViewEmployees.aspx и дайте ей имя GridViewDataView.aspx

    При копировании не забудьте на странице в директиве @Page исправить параметр Inherits="GridViewDataView" а в файле поддержки переопределить имя класса на GridViewDataView

  • Поместите на страницу дополнительные элементы управления GridView так, чтобы они располагались друг за другом по вертикали в порядке GridView1, GridView2, GridView3
  • Вставьте перед каждым элементом строку-заголовок
  • <h2>Несортированные данные</h2>
  • <h2>Сортированы по полю FirstName</h2>
  • <h2>Сортированы по полю LastName</h2>
  • Интерфейс страницы на этапе проектирования должен быть таким

  • Отредактируйте файл поддержки страницы следующим образом
    (рис ) Код файла GridViewDataView.aspx.cs using System;
    using System.Data;
        
    using System.Web.Configuration;
    using System.Data.SqlClient;
        
    public partial class GridViewDataView : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Содать Connection, DataAdapter и DataSet
            string connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
            SqlConnection con = new SqlConnection(connectionString);
            string sql = 
                "SELECT TOP 5 EmployeeID, TitleOfCourtesy, LastName, "
              + "FirstName FROM Employees";
            SqlDataAdapter adapter = new SqlDataAdapter(sql, con);
            DataSet dataset = new DataSet();
            adapter.Fill(dataset, "EmployeesTable");
        
            // Привязать данные к GridView1 для отображения
            // без сортировки как они есть
            GridView1.DataSource = dataset.Tables["EmployeesTable"];
        
            // Скопировать данные в экземпляр класса DataView,
            // отсортировать по полю FirstName, затем привязать
            DataView view2 = new DataView(dataset.Tables["EmployeesTable"]);
            view2.Sort = "FirstName";
            GridView2.DataSource = view2;
        
            // Скопировать данные в экземпляр класса DataView,
            // отсортировать по полю FirstName, затем привязать
            DataView view3 = new DataView(dataset.Tables["EmployeesTable"]);
            view3.Sort = "LastName";
            GridView3.DataSource = view3;
        
            // Загрузить привязанные данные во все элементы отображения GridView
            Page.DataBind();
        }
    }
  • Исполните страницу GridViewDataView.aspx, чтобы получить следующий результат
  • Несортированные данные

    EmployeeID TitleOfCourtesy LastName FirstName
    1 Ms. Davolio Nancy
    2 Dr. Fuller Andrew
    3 Ms. Leverling Janet
    4 Mrs. Peacock Margaret
    5 Mr. Buchanan Steven

    Сортированы по полю FirstName

    EmployeeID TitleOfCourtesy LastName FirstName
    2 Dr. Fuller Andrew
    3 Ms. Leverling Janet
    4 Mrs. Peacock Margaret
    1 Ms. Davolio Nancy
    5 Mr. Buchanan Steven

    Сортированы по полю LastName

    EmployeeID TitleOfCourtesy LastName FirstName
    5 Mr. Buchanan Steven
    1 Ms. Davolio Nancy
    2 Dr. Fuller Andrew
    3 Ms. Leverling Janet
    4 Mrs. Peacock Margaret

    Фильтрация извлеченных автономных данных с помощью DataView

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

    Некоторые операторы фильтрации в свойстве DataView.RowFilter
    Операция Описание
    <, >, <=, >= Сравнение числовых или строковых типов
    <>, = Проверка на эквивалентность
    NOT Отрицание
    BETWEEN Указывает диапазон включительно. Например, Units BETWEEN 5 AND 15 выбирает строки, у которых значение столбца Units находится в диапазоне 5-15 включительно
    IS NULL Проверяет столбец на нулевое значение
    IN(a, b, c) Краткая форма операции OR с одним и тем же полем. Проверяет эквивалентность значения столбца любому из перечисленных значений списка нужной длины, например a, b, c
    LIKE Проверяет соответствие строкового значения шаблону
    + Конкатенация складывает два числа или склеивает две строки
    - Вычитает одно числовое значение из другого
    * Перемножает два числовых значения
    / Делит одно числовое значение на другое
    % Вычисляет модуль - остаток от деления одного числового значения на другое
    AND Логическое умножение
    OR Логическое сложение

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

  • Создайте копию страницы GridViewDataView.aspx с именем DataViewFiltered.aspx и назначьте ее стартовой
  • В странице DataViewFiltered.aspx выполните следующие изменения
  • В директиве @Page установите новое значение атрибута Inherits="DataViewFiltered"
  • Поменяйте HTML-дескрипторы <h2> на <h3> со следующим содержимым
  • <h3>
    Фильтровать продукт Chocolade<br />
    (RowFilter = "ProductName = 'Chocolade'")
    </h3>
  • <h3>
    Фильтровать продукты, которых нет в заказах и на складе<br />
    (RowFilter = "UnitsInStock = 0 AND UnitsOnOrder = 0")
    </h3>
  • <h3>
    Фильтровать продукты, чье название начинается с буквы P<br />
    (RowFilter = "ProductName LIKE 'P%'")
    </h3>
  • В файле поддержки DataViewFiltered.aspx.cs установите новое имя класса DataViewFiltered и наполните файл следующим кодом
    (рис ) Код файла DataViewFiltered.aspx.csusing System;
    using System.Data;
        
    using System.Web.Configuration;
    using System.Data.SqlClient;
        
    public partial class DataViewFiltered : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Содать Connection, DataAdapter и DataSet
            string connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
            SqlConnection con = new SqlConnection(connectionString);
            string sql = 
                "SELECT ProductID, ProductName, UnitsInStock, UnitsOnOrder, "
              + "Discontinued FROM Products";
            SqlDataAdapter adapter = new SqlDataAdapter(sql, con);
            DataSet dataset = new DataSet();
            adapter.Fill(dataset, "ProductsTable");
        
            // Фильтровать продукт Chocolade
            DataView view1 = new DataView(dataset.Tables["ProductsTable"]);
            view1.RowFilter = "ProductName = 'Chocolade'";
            GridView1.DataSource = view1;
        
            // Фильтровать продукты, которых нет в заказах и на складе
            DataView view2 = new DataView(dataset.Tables["ProductsTable"]);
            view2.RowFilter = "UnitsInStock = 0 AND UnitsOnOrder = 0";
            GridView2.DataSource = view2;
        
            // Фильтровать продукты, чье название начинается с буквы P
            DataView view3 = new DataView(dataset.Tables["ProductsTable"]);
            view3.RowFilter = "ProductName LIKE 'P%'";
            GridView3.DataSource = view3;
        
            // Загрузить привязанные данные во все элементы отображения GridView
            this.DataBind();
        }
    }
  • Запустите страницу DataViewFiltered.aspx и получите следующий результат
  • Фильтровать продукт Chocolade (RowFilter = "ProductName = 'Chocolade'")

    ProductID ProductName UnitsInStock UnitsOnOrder Discontinued
    48 Chocolade 15 70

    Фильтровать продукты, которых нет в заказах и на складе (RowFilter = "UnitsInStock = 0 AND UnitsOnOrder = 0")

    ProductID ProductName UnitsInStock UnitsOnOrder Discontinued
    5 Chef Anton's Gumbo Mix 0 0
    17 Alice Mutton 0 0
    29 Th№ringer Rostbratwurst 0 0
    53 Perth Pasties 0 0

    Фильтровать продукты, чье название начинается с буквы P (RowFilter = "ProductName LIKE 'P%'")

    ProductID ProductName UnitsInStock UnitsOnOrder Discontinued
    16 Pavlova 29 0
    53 Perth Pasties 0 0
    55 Pтtщ chinois 115 0

    Класс System.Data.DataView также имеет свойство RowStateFilter, которое можно использовать для фильтрации данных так, чтобы отображались только строки, имеющие определенное состояние (вставленные, помеченные на удаление, модифицированные или неизмененные). По умолчанию это свойство установлено на отображение всех строк, кроме помеченных на удаление.

    Фильтрация извлеченных автономных данных в DataView с установкой отношений

    Класс DataView может выполнять и более сложную фильтрацию на основе таблиц, связанных отношением "Главный-подчиненный". Например, можно отобразить категории, каждая из которых содержит более 20 наименований продуктов, или отобразить заказчиков, сделавших определенное количество покупок.

    Продемонстрируем эту возможность на примере двух таблиц: Categories и Products учебной базы Northwind.

  • Сделайте копию страницы DataSetRelationShips.aspx и назовите ее DataViewRelation.aspx
  • Исправьте значения атрибута Inherits в директиве @Page страницы и имя класса в файле поддержки
  • Удалите со страницы DataViewRelation.aspx текстовую метку lblInfo и поместите вместо нее заголовочный дескриптор
    <h2>Категории с продуктами дороже $50</h2>
  • Поместите после заголовочного дескриптора элемент управления GridView из вкладки Data панели Toolbox
  • Скорректируйте файл поддержки страницы DataViewRelation.aspx.cs следующим образом
    (рис ) Код файла DataViewRelation.aspx.csusing 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, UnitPrice 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);
        
            // Создаем объект DataView, в который загружаем данные CatTable
            DataView view1 = new DataView(dataset.Tables["CatTable"]);
        
            // Устанавливаем фильтр для отображения только категорий продуктов,
            // цена которых в связанной таблице продуктов удовлетворяет условию
            // "Самый дорогой продукт дороже 50"
            view1.RowFilter = "MAX(Child(CatProd).UnitPrice) > 50";
        
            // Показываем отфильтрованные данные пользователю
            GridView1.DataSource = view1;
            GridView1.DataBind();
        }
    }
  • Назначьте страницу DataViewRelation.aspx стартовой и выполните ее
  • Должен получиться следующий результат

    Категории с продуктами дороже $50

    CategoryID CategoryName
    1 Beverages
    3 Confections
    4 Dairy Products
    6 Meat/Poultry
    7 Produce
    8 Seafood

    В фильтре мы установили условие, чтобы показать пользователю только те записи в родительской таблице, для которых в дочерней связанной таблице имеется хотя бы одна запись со значением поля UnitPrice>50. Это означает, что выводятся категории продуктов, в которых хотя бы один продукт дороже $50.

    Добавление к автономным данным вычисляемых столбцов

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

    DataSet.Tables["TableName"].Select("Дополнительное_условие")

    Показывали, также, данные с условием, наложенным в свойстве DataView.RowFilter вспомогательного объекта DataView.

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

    Чтобы создать вычисляемый столбец в DataSet, необходимо отдельно создать новый объект класса DataColumn, установить формулу в его свойстве Expression и добавить этот столбец в коллекцию Columns объекта DataSet

    dataset.Tables["TableName"].Columns.Add(datacolumn)

    Рассмотрим пример, в котором создадим столбец, объединяющий фамилию и имя каждого служащего из таблицы Employees учебной базы данных Northwind.

  • Создайте копию файла TestDataSet.aspx с именем ExpressionColumns.aspx и назначьте ее стартовой
  • Откройте на редактирование файл поддержки ExpressionColumns.aspx.cs и отредактируйте его так
    (рис ) Код файла ExpressionColumns.aspx.cs using System;
    using System.Data;
        
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Text;
        
    public partial class ExpressionColumns : 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.");
            }
        
            // Создаем именованный столбец и добавляем к таблице в DataSet
            string strExpr = 
                "'Сотрудник ' + TitleOfCourtesy + ' ' + LastName + ', ' + FirstName";
            DataColumn column = new DataColumn("FullName", typeof(string), strExpr);
            dataset.Tables["EmployeesResult"].Columns.Add(column);
        
            // Перебираем все объекты DataRow с полученным записами
            StringBuilder htmlStr = new StringBuilder("");
            foreach (DataRow dr in dataset.Tables["EmployeesResult"].Rows)
            {
                htmlStr.Append("<li>");
                // Существующий столбец
                htmlStr.Append(dr["EmployeeID"].ToString() + ") ");
                htmlStr.Append("<b>");
                // Новый столбец
                htmlStr.Append(dr["FullName"].ToString());  
                htmlStr.Append("</b>");
                htmlStr.Append("</li>");
            }
        
            // Отображаем полученные данные
            lblInfo.Text = "<h2>Список сотрудников</h2>";
            lblInfo.Text += htmlStr.ToString();
        }
    }
  • Выполните страницу ExpressionColumns.aspx и получите следующий результат
  • Список сотрудников

  • 1) Сотрудник Ms. Davolio, Nancy
  • 2) Сотрудник Dr. Fuller, Andrew
  • 3) Сотрудник Ms. Leverling, Janet
  • 4) Сотрудник Mrs. Peacock, Margaret
  • 5) Сотрудник Mr. Buchanan, Steven
  • 6) Сотрудник Mr. Suyama, Michael
  • 7) Сотрудник Mr. King, Robert
  • 8) Сотрудник Ms. Callahan, Laura
  • 9) Сотрудник Ms. Dodsworth, Anne
  • Добавление к автономным данным вычисляемых столбцов для связанных таблиц

    Можно создать в DataSet вычисляемые столбцы для связанных строк. Например, можно добавить в виртуальную таблицу CatTable столбец, показывающий количество связанных строк виртуальной таблицы ProdTable. В этом случае нужно определить отношение объектом DataRelation и использовать агрегатную функцию SQL, такую, как AVG(), MAX(), MIN(), COUNT().

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

  • Создайте копию страницы DataViewRelation.aspx с именем ExpressionColumnsRelation.aspx и назначьте ее стартовой
  • Исправьте значения атрибута Inherits в директиве @Page страницы и имя класса в файле поддержки
  • Измените на странице заголовочный дескриптор на
    <h2>Добавление вычисляемых столбцов</h2>
  • Откройте кодовый файл ExpressionColumnsRelation.aspx.cs на редактирование и откорректируйте его так
    (рис ) Код файла ExpressionColumnsRelation.aspx.csusing System;
    using System.Data;
        
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Text;
        
    public partial class ExpressionColumnsRelation : 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, UnitPrice 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);
        
            // Создать вычисляемые столбцы и добавить их в DataSet
            DataColumn count = new DataColumn("Кол. продуктов", typeof(int), 
                "COUNT(Child(CatProd).CategoryID)");
            dataset.Tables["CatTable"].Columns.Add(count);
            DataColumn max = new DataColumn("Самый дорогой продукт", typeof(decimal),
                "MAX(Child(CatProd).UnitPrice)");
            dataset.Tables["CatTable"].Columns.Add(max);
            DataColumn min = new DataColumn("Самый дешевый продукт", typeof(decimal),
                "MIN(Child(CatProd).UnitPrice)");
            dataset.Tables["CatTable"].Columns.Add(min);
        
            // Меняем названия заголовков столбцов на русский язык
            dataset.Tables["CatTable"].Columns["CategoryID"].ColumnName = "№ п/п";
            dataset.Tables["CatTable"].Columns["CategoryName"].ColumnName = "Наименование категории";
        
            // Показываем данные пользователю
            GridView1.DataSource = dataset.Tables["CatTable"];
            GridView1.DataBind();
        }
    }
  • Исполните страницу ExpressionColumnsRelation.aspx и получите следующий результат
  • Добавление вычисляемых столбцов

    № п/п Наименование категории Кол. продуктов Самый дорогой продукт Самый дешевый продукт
    1 Beverages 12 263,5000 4,5000
    2 Condiments 12 43,9000 10,0000
    3 Confections 13 81,0000 9,2000
    4 Dairy Products 10 55,0000 2,5000
    5 Grains/Cereals 7 38,0000 7,0000
    6 Meat/Poultry 6 123,7900 7,4500
    7 Produce 5 53,0000 10,0000
    8 Seafood 12 62,5000 6,0000
    Вернуться к учебному плану