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

Привязка данных ADO.NET

Разбить на страницы
Показывать лекцию целиком

Программировать стараться - с конкурентами распрощаться

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

Профессиональное Web-приложение всегда имеет дело с данными, которые могут храниться в базе данных, XML- или структурированном файле, или где-то еще. Правильное отображение этих данных на странице пользователя так же важно, как и их извлечение. ADO.NET имеет механизм привязки данных, который позволяет декларативно (на этапе проектирования) определить связь между элементами извлечения данных и элементами их отображения. При этом может не потребоваться ни строчки кода.

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

Привязка одного значения

Привязка одного значения позволяет связать свойство элемента управления с местом на странице. Это место называется вычислимым выражением привязки данных. Различают вычислимые # - выражения и более общие $ - выражения привязки данных. Выражение привязки данных может располагаться как внутри дескрипторов элементов управления, так и самостоятельно в любом месте страницы, даже вне дескриптора <form>...</form>.

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

  • значение свойства
  • значение, возвращаемое функцией
  • переменную-член класса
  • ссылку на свойство другого элемента управления
  • арифметическое выражение с применением литералов и переменных
  • и т.д.
  • Выражение привязки данных в ASP.NET помещается в ограничительные скобки <%#...%>PHP это будут скобки <?php...?> или <?=...?> ). Если выражение привязки данных используется внутри дескриптора элемента управления в качестве вычисляемого значения атрибута, то оно помещается в одинарные или двойные кавычки. Исполнение выражения привязки данных требует обязательного выполнения метода Page.DataBind() в сценарии страницы. Если мы забудем это сделать, то выражение привязки данных будет проигнорировано в HTML-выводе страницы.

    Пример корректных # - выражений привязки данных:

  • <%# Request.Browser.Browser %>
  • <%# GetUserName(ID) %>
  • <%# variable %>
  • <%# "Дорогой " + "друг " + name %>
  • <%# 2 * PI * radius + cos(alpha) %>
  • Пример интерфейсной части страницы, использующей несколько выражений привязки данных:

    <html>
    <body>
    <form method="post" runat="server">
    	<asp:Image ID="Image1" runat="server" ImageUrl='<%# FilePath %>' /><br />
    	<asp:Label ID="Label1" runat="server" Text="<%# FilePath %>" /><br />
    	<asp:TextBox ID="TextBox1" runat="server" Text='<%# GetFilePath() %>' /><br />
    	<asp:HyperLink ID="HyperLink1" runat="server"
    		NavigateUrl="<%# LogoPath.Value %>" Font-Bold="true" Text="Show logo" /><br />
    	<input type="hidden" ID="LogoPath" runat="server" value="MyMy.png">
    		<b><%# FilePath %></b><br />
    	<img src="<%# GetFilePath() %>">
    </form>
    </body>
    </html>

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

    Приведем простой пример.

  • Создайте новый сайт с именем WebSite9 командой File/New/Web Site (шаблон ASP.NET Web Site )
  • Скопируйте из проекта WebSite8 файл Web.Config, который должен иметь такой вид
    (рис ) Содержимое файла 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>
  • Добавьте к приложению новую страницу с совмещенным кодом и именем SingleValueBinding.aspx, назначьте страницу стартовой
  • Вызовите для корня Web-дерева контекстное меню и создайте каталог Pictures сайта командой New Folder
  • Скопируйте из каталога Pictures данной работы в каталог Pictures сайта два файла: MyPhoto.jpg и MyMy.png (или любые другие рисунки)
  • Наполните страницу SingleValueBinding.aspx следующим кодом
    (рис ) Код страницы SingleValueBinding.aspx <%@ Page Language="C#" %>
        
    <script runat="server">
        // Объявления переменных класса
        string cat = "Васька", photo;
        
        // Методы
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                Label1.Text = "<h1>Заполните поля и пошлите запрос</h1>";
                pict.Src = "Pictures/MyPhoto.jpg";
            }
            else
            {
                Label1.Text = "<h1>Вот Вам такие ответы</h1>";
                photo = "Pictures/MyMy.png";
                pict.Border = 3;
            }
        }
        
        public int AddFun(string a, string b)
        {
            try
            {
                return Convert.ToInt32(a) + Convert.ToInt32(b);
            }
            catch
            {
                Label1.Text = "<h1 style='color:red'>В поля вводите целые числа</h1>";
                Label1.Text += "<hr style='height:10; color:red' />";
                return 0;
            }
        }
        
        protected void Button1_Click(object sender, EventArgs e)
        {
            this.DataBind();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Выражения привязки одного значения</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Label ID="Label1" runat="server" Text="Label" />
                <h2>
                    Кто сказал Мяу...?</h2>
                <b style="color: Red">
                    <%# "Кот " + cat + "!" %>
                </b>
                <h2>
                    Сколько будет дважды-два?</h2>
                <b style="color: Red">
                    <%# 2 * 2 %>
                </b>
                <h2>
                    Введите два <em>целых</em> числа<br />
                    и получите их сумму</h2>
                <asp:TextBox ID="TextBox1" runat="server" />
                +
                <asp:TextBox ID="TextBox2" runat="server" />
                = <b style="color: Red">
                    <%# AddFun(TextBox1.Text, TextBox2.Text) %></b>
                    <h2>Кто здесь изображен?</h2>
                    <img id="pict" runat="server" src="<%# photo %>" />
                <br />
                <br />
                <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Выполнить" />
            </div>
        </form>
    </body>
    </html>
  • При первой загрузке страницы в текстовую метку выводится начальный заголовок <h1> и установленное по умолчанию значение свойства src дескриптора <img>. Все выражения привязки данных при этом игнорируются. При обратной отсылке, инициированной кнопкой, срабатывает метод DataBind() страницы. Он заставляет выполниться все выражения привязки данных, код получения которых размещен в блоке сценариев <script>.

  • Выполните страницу SingleValueBinding.aspx и убедитесь в работоспособности выражений привязки данных
  • Результат при начальной загрузке страницы:

    Результат после обратной отсылки страницы:

    Привязка множественного значения к списковым элементам управления

    Некоторые элементы управления библиотеки .NET Framework, служащие для отображения данных, позволяют привязать сразу несколько своих дочерних элементов к источнику данных. К ним относятся прежде всего списковые элементы управления:

  • Все списковые элементы управления, которые генерируют свой HTML-код с использованием дескриптора <select>, включая HtmlSelect, ListBox и DropDownList
  • Элементы управления CheckBoxList и RadioButtonList, генерирующие HTML-код своих дочерних элементов в виде флажков или переключателей
  • Элемент управления BulletedList, создающий нумерованный или маркированный список
  • Все они наследуют от System.Web.UI.WebControls.ListControl, который содержит свойства, необходимые для привязки множественного значения. Эти свойства приведены в таблице

    Свойства ListControl для привязки множественного значения
    Свойство Описание
    DataSource Подключает ссылку на объект с данными (для загрузки методом DataBind() ), имеющими
    DataSourceID Присоединяется к уже загруженному объекту данных. Используется как альтернатива свойству DataSource. Оба свойства сразу применять нельзя
    DataTextField Содержит наименование поля (столбца) таблицы, значения которого будут отображаться списком
    <select size="3" name="ListBox1" id="ListBox1">
        <option value="1">Text1</option>
        <option value="2">Text2</option>
    </select>
    DataValueField Содержит наименование поля (столбца) таблицы, значения которого будут загружены в список как атрибуты value, но пользователь их не увидит
    <select size="3" name="ListBox1" id="ListBox1">
        <option value="1">Text1</option>
        <option value="2">Text2</option>
    </select>

    В свою очередь, класс ListControl наследует метод DataBind() от класса System.Web.UI.WebControls.BaseDataBoundControl, иницирующий привязку.

    Для примера привязки множественного значения спроектируем страницу, в которой в качестве объекта, имитирующего источник данных, используем класс Hashtable (hash - мусор). Он реализует интерфейс System.Collections.Generic.IDictionary, имеющий шаблонный метод IDictionary<TKey, TValue>.Add(TKey, TValue).

  • Добавьте к приложению страницу с неразделяемым кодом и именем RepeatedValueBinding.aspx
  • Заполните интерфейсную часть страницы следующим кодом
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <select runat="server" id="Select1" 
                datatextfield="Key" datavaluefield="Value" size="3" />
            <select runat="server" id="Select2" 
                datatextfield="Key" datavaluefield="Value" />
            <asp:ListBox runat="server" ID="ListBox1" 
                DataTextField="Key" DataValueField="Value" Rows="3" />
            <asp:DropDownList runat="server" ID="DropDownList1" 
                DataTextField="Key" DataValueField="Value" />
            <asp:RadioButtonList runat="server" ID="RadioButtonList1" 
                DataTextField="Key" DataValueField="Value" />
            <asp:CheckBoxList runat="server" ID="CheckBoxList1"
                DataTextField="Key" DataValueField="Value" />
            <br />
            <asp:Button runat="server" ID="GetSelection" Text="Получить выделенное" />
            <br />
            <br />
            <asp:Literal runat="server" ID="Result" EnableViewState="false" />
        </div>
        </form>
    </body>
    </html>

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

  • Переведите редактор страницы в режим Design и двойным щелчком мыши на свободном месте создайте обработчик Page_Load(), который заполните так
  • protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            // Создать источник данных (коллекцию) с тремя элементами
            Hashtable ht = new Hashtable(3);
            
            // Добавить в коллекцию элементы по схеме: Add(Key, Value)
            ht.Add("Иванов", "Value1");
            ht.Add("Петров", "Value2");
            ht.Add("Сидоров", "Value3");
            
            // Связать элементы управления с источником данных
            Select1.DataSource = ht;
            Select2.DataSource = ht;
            ListBox1.DataSource = ht;
            DropDownList1.DataSource = ht;
            RadioButtonList1.DataSource = ht;
            CheckBoxList1.DataSource = ht;
            
            // Загрузить данные в элементы управления
            this.DataBind();
        }
    }

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

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

  • Создайте обработчик для кнопки Button и заполните его следующим кодом
  • protected void GetSelection_Click(object sender, EventArgs e)
        {
            try
            {
                Result.Text += "<li>В Select1 выбрано: "
                            + "(\""
                            + Select1.Items[Select1.SelectedIndex].Text // Key
                            + "\", \""
                            + Select1.Value + "\")";                    // Value
                Result.Text += "<li>В Select2 выбрано: "
                            + "(\""
                            + Select2.Items[Select2.SelectedIndex].Text // Key
                            + "\", \""
                            + Select2.Value + "\")";                    // Value
                Result.Text += "<li>В ListBox1 выбрано: "
                            + "(\""
                            + ListBox1.SelectedItem.Text                // Key
                            + "\", \""
                            + ListBox1.SelectedItem.Value + "\")";      // Value
                Result.Text += "<li>В DropDownList1 выбрано: "
                            + "(\""
                            + DropDownList1.SelectedItem.Text           // Key
                            + "\", \""
                            + DropDownList1.SelectedItem.Value + "\")"; // Value
                Result.Text += "<li>В RadioButtonList1 выбрано: "
                            + "(\""
                            + RadioButtonList1.SelectedItem.Text          // Key
                            + "\", \""
                            + RadioButtonList1.SelectedItem.Value + "\")";// Value
        
                // Проверяем флажки
                int checkedCount = 0;
                string checkString = "";
                foreach (ListItem check in CheckBoxList1.Items)
                {
                    if (check.Selected)
                    {
                        checkString += "(\""
                                    + check.Text                        // Key
                                    + "\", \""
                                    + check.Value + "\"); ";            // Value
                        checkedCount++;  // Считем выделенные флажки
                    }
                }
                if (checkedCount == 0)
                    Result.Text += "<li>В CheckBoxList1 ничего не выбрано!";
                else
                    Result.Text += "<li>В CheckBoxList1 выбрано: " + checkString;
            }
            catch
            {
                Result.Text = "<h1 style='color:Red'>Выберите в каждом элементе</h1>";
            }
        }

    Разница в коде доступа к свойствам списков между серверными элементами управления и статическими объясняется подгонкой фирмой Microsoft HTML-классов под ранее разработанные дескрипторы <select>.

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

    Предыдущий пример использует в качестве источника данных класс Hashtable, потому что он реализует интерфейс

    System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.

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

  • System.Collections.ArrayList
  • Microsoft.VisualBasic.Collection
  • System.Collections.Hashtable
  • System.Collections.Generic.Dictionary<TKey, TValue>
  • System.Data.DataView
  • Учитывая, что DataReader представляет данные также в виде таблицы с именованными столбцами, мы можем так сформулировать SQL-запрос к базе данных, что в DataReader будут загружены два нужных нам столбца: один из которых можно считать Key, другой - Value. В элементе управления свойству DataTextField нужно присвоить имя поля, отображаемого в списке, а свойству DataValueField - имя поля, неотображаемого в списке.

    Приведем пример. Пусть мы хотим заполнить окно списка ListBox полными именами всех сотрудников, хранящихся в таблице Employees учебной базы данных Northwind. Чтобы получить два столбца DataReader, один из которых будет вычисляемый, нужно сформулировать SQL-запрос таким образом

    SELECT EmployeeID, TitleOfCourtesy + ' ' + FirstName + ' ' + LastName AS FullName FROM Employees
  • Добавьте к проекту новую страницу DataReaderListBinding.aspx с разделяемым кодом и назначьте ее стартовой
  • Оформите и настройте интерфейсную часть страницы DataReaderListBinding.aspx так, чтобы она имела следующее дескрипторное представление
    (рис ) Дескрипторное представление интерфейсной части страницы DataReaderListBinding.aspx<%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="DataReaderListBinding.aspx.cs" 
        Inherits="DataReaderListBinding" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ListBox ID="ListBox1" runat="server" DataTextField="FullName" 
                DataValueField="EmployeeID" SelectionMode="Multiple" Rows="10" />
            <br />
            <br />
            <asp:Button ID="GetSelection" runat="server" Text="Получить выделенное" 
                OnClick="GetSelection_Click" />
            <br />
            <br />
            <asp:Literal ID="Result" runat="server" EnableViewState="False" />
        </div>
        </form>
    </body>
    </html>
  • Заполните файл поддержки DataReaderListBinding.aspx.cs следующим кодом
    (рис ) Код файла поддержки DataReaderListBinding.aspx.csusing System;
    using System.Data;
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Web.UI.WebControls;
        
    public partial class DataReaderListBinding : System.Web.UI.Page
    {
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                // Создать объекты Command и Connection
                string connectionString = WebConfigurationManager.
                    ConnectionStrings["Northwind"].ConnectionString;
                string sql = "SELECT EmployeeID, "
                            + "TitleOfCourtesy + ' ' + FirstName + ' ' + LastName "
                            + "AS FullName FROM Employees";
                SqlConnection con = new SqlConnection(connectionString);
                SqlCommand cmd = new SqlCommand(sql, con);
        
                try
                {
                    // Открыть соединение и получить DataReader
                    con.Open();
                    SqlDataReader reader = cmd.ExecuteReader();
                    // Привязать DataReader к списку и заполнить список
                    ListBox1.DataSource = reader;
                    ListBox1.DataBind();
                    reader.Close();
                }
                finally
                {
                    // Закрыть соединение
                    con.Close();
                }
            }
        }
        
        protected void GetSelection_Click(object sender, EventArgs e)
        {
            Result.Text = "<b>Выделены сотрудники:</b>";
            Result.Text += "<ol>";
            foreach (ListItem item in ListBox1.Items)
                if (item.Selected)
                    Result.Text += 
                        String.Format("<li> (\"{0}\", \"{1}\")</li>", item.Text, item.Value);
            Result.Text += "</ol>";
        }
    }
  • Здесь мы заполнили список только при первом запросе страницы, расчитывая, что далее заполненный список будет сохраняться в состоянии вида. Если в элементе ListBox1 отключить сохранение состояния вида, установив EnableViewState="False", то при обратной отсылке список окажется пустым (попробуйте!).

  • Исполните страницу DataReaderListBinding.aspx для получения следующего результата
  • Выделены сотрудники:

  • ("Dr. Andrew Fuller", "2")
  • ("Mr. Steven Buchanan", "5")
  • ("Ms. Anne Dodsworth", "9")
  • Если для визуализации данных мы хотим использовать DropDownList, CheckListBox или RadioButtonList вместо ListBox, то достаточно изменить объявление элемента управления в интерфейсной части страницы. Остальная часть кода останется прежней.

    Элемент управления GridView с множественной привязкой данных

    Кроме списковых элементов управления, которые можно использовать для отображения множественных данных, существуют более специализированные элементы управления, спроектированные специально для этих целей. К ним относятся GridView, DetailsView и FormView. Кроме того, механизм привязки имеют и такие элементы, как Menu, TreeView и AdRotator, хотя и служат несколько для других целей.

    Здесь мы поговорим об элементе управления GridView, возможных способах его настройки и привязки к нему разных источников данных. Как и списковые элементы управления, GridView имеет свойство DataSource и метод DataBind(). Типовое объявление элемента GridView в интерфейсной части страницы выглядит так:

    <asp:GridView ID="GridView1" runat="server" />

    Рассмотрим пример, в котором данные DataReader будут привязываться не к списковому элементу управления, а к GridView.

  • Создайте новую страницу GridViewDataBind.aspx с разделяемым кодом и назначьте ее стартовой
  • Поместите на страницу из вкладки Data панели Toolbox элемент управления GridView
  • Откройте на редактирование файл GridViewDataBind.aspx.cs и заполните его следующим кодом
    (рис ) Код файла GridViewDataBind.aspx.csusing System;
    using System.Data;
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Web.UI.WebControls;
        
    public partial class DataReaderListBinding : System.Web.UI.Page
    {
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать объекты Command и Connection
            string connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
            string sql = "SELECT EmployeeID, FirstName, LastName, "
                        + "Title, City FROM Employees";
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand(sql, con);
        
            try
            {
                // Открыть соединение и получить DataReader
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader();
                // Привязать DataReader к GridView
                GridView1.DataSource = reader;
                GridView1.DataBind();
                reader.Close();
            }
            finally
            {
                // Закрыть соединение
                con.Close();
            }
        }
    }
  • Исполните страницу GridViewDataBind.aspx для получения следующего результат
  • EmployeeID FirstName LastName Title City
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Anne Dodsworth Sales Representative London

    Элементы управления источниками данных

    В библиотеке .NET Framework имеется ряд классов, которые соединяются с любым поставщиком данных ADO.NET, облегчая привязку данных к элементам отображения. Они называются источниками данных и реализуют интерфейс IDataSource.

    К источникам данных относятся следующие классы:

  • System.Web.UI.WebControls.ObjectDataSource - позволяет подключиться к пользовательскому классу доступа к данным
  • System.Web.UI.WebControls.SqlDataSource - позволяет подключиться к любому поставщику ADO.NET
  • System.Web.UI.WebControls.SiteMapDataSource - позволяет подключиться к файлу System.Web.SiteMap, описывающему навигационную структуру Web-сайта
  • System.Web.UI.WebControls.XmlDataSource - позволяет подключиться к XML-файлу
  • Все эти классы представлены как элементы управления на вкладке Data панели Toolbox оболочки Visual Studio 2005 и являются новшеством библиотеки .NET Framework 2.0. Если перенести этот элемент на страницу, то он отображается серым цветом как невизуальный компонент страницы. В то же время в разметочной части страницы появляется соответствующий дескриптор элемента управления.

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

  • System.Data.Common
  • System.Data.SqlClient
  • System.Data.OracleClient
  • System.Data.OleDb
  • System.Data.Odbc
  • Элемент управления SqlDataSource

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

  • строку соединения ConnectionString
  • имя фабрики поставщиков ProviderName
  • тип SQL-команды (встроенная SQL-команда Text или хранимая процедура StoredProcedure ) в свойствах SelectCommandType, InsertCommandType, UpdateCommandType и DeleteCommandType
  • саму SQL-команду или имя хранимой процедуры в свойствах SelectQuery, InsertQuery, UpdateQuery и DeleteQuery (Такие имена имеют свойства в панели Properties, которые в дескрипторном представлении источника данных трансформируются в имена атрибутов SelectCommand, InsertCommand, UpdateCommand и DeleteCommand )
  • Нам нужно определять команды только для тех типов действий, которые мы намерены выполнить. Так, например, если мы используем источник данных только для чтения набора записей, то нам достаточно определить только свойство SelectQuery.

    Строку соединения можно жестко указать в свойствах источника данных, но лучше читать из файла web.config, используя $ - выражение привязки данных.

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

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
        "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />

    Источник данных можно сконфигурировать и с помощью мастера, вызвав его командой Configure Data Source встроенного меню

    Создав и настроив источник данных, мы получаем возможность привязать к нему любой элемент отображения данных: HtmlSelect, ListBox, DropDownList, CheckBoxList, RadioButtonList, BulletedList уже на этапе проектирования, не написав ни одной строки кода в обработчике Page_Load(). Более того, если выполнить команду Refresh Scheme встроенной панели источника данных, то он одноразово соединится с базой данных и заполнится требуемой информацией, которую потом можно выбирать из раскрывающихся списков при настройке привязываемых к нему элементов отображения данных. Если мы используем источник данных, то создавать соединение, объекты команд и вызывать метод Page.DataBind() в коде страницы для заполнения элементов отображения, не нужно - все будет выполняться автоматически при запуске страницы.

  • Создайте страницу с объединенным кодом и именем SqlDataSourceSimple.aspx. Назначьте ее стартовой
  • Из вкладки Data панели Toolbox добавьте на страницу неотображаемый элемент управления SqlDataSource, через который мы привяжем данные к элементу отображения
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления ListBox
  • Из вкладки Data панели Toolbox добавьте на страницу элемент управления GridView
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления Button
  • Настройте объекты страницы: источник данных SqlDataSource1, элемент отображения данных ListBox1, многофункциональный элемент отображения данных GridView1, кнопку обратной отсылки Button1 следующим образом
    (рис ) Разметочная часть страницы SqlDataSourceSimple.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" /></div>
        </form>
    </body>
    </html>
  • Исполните страницу SqlDataSourceSimple.aspx, чтобы получить следующий результат
  • EmployeeID FirstName LastName Title City
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Anne Dodsworth Sales Representative London

    Все работает автоматически без необходимости написания кода (который нам тоже не страшен). Привязка данных при каждой обратной отсылке повторяется заново, и в этом есть некоторый недостаток, поскольку каждый раз должно создаваться соединение с базой данных и производиться выборка данных. К счастью, разработчики ASP.NET 2.0 устранили этот недостаток за счет автоматического кэширования данных источником SqlDataSource на случай, если данные изменяются редко.

    Если изменить свойство DataSourceMode источника данных с DataSet (установлено по умолчанию) на DataReader и исполнить страницу, то в данном случае получим тот же самый результат. Режим DataSourceMode=DataSet работы источника данных почти всегда лучше, чем режим DataSourceMode=DataReader, поскольку он поддерживает расширенную сортировку, фильтрацию и настройку кэширования, имеющиеся в DataSet. Однако, если стоит задача отображать большие экранные таблицы, то лучше использовать режим DataSourceMode=DataReader, потому что он более эффективно использует память сервера.

    Передача параметра источнику данных через элемент управления

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

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

  • Создайте страницу с объединенным кодом и именем SqlDataSourceParameters.aspx. Назначьте ее стартовой
  • Удалите контейнер сценария <script runat="server">...</script> как ненужный для данного примера
  • Из вкладки Data панели Toolbox добавьте на страницу первый источник данных SqlDataSource, который будет поставлять список всех городов из таблицы Employees. Назовите его sourceEmployeeCities
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления DropDownList, который будет отображать все города, выбранные источником sourceEmployeeCities из таблицы Employees. Назовите его lstCities
  • Из вкладки Data панели Toolbox добавьте на страницу второй источник данных, который будет выбирать всех сотрудников из таблицы Employees, проживающих в переданном ему из раскрывающегося списка lstCities через параметр городе. Назовите его sourceEmployees
  • Из вкладки Data панели Toolbox добавьте на страницу элемент управления GridView с именем GridView1, который будет отображать список сотрудников, проживающих в выбранном городе и полученных из источника sourceEmployees
  • Настройте объекты страницы так, как показано ниже.
  • При добавлении параметров пользуйтесь удобным редактором оболочки, позволяющим создавать команды и определять параметры. Редактор вызывается при выделенном элементе второго источника sourceEmployees щелчком на кнопке ( ...) свойства SelectQuery в панели Properties

    Обратите внимание, что обратную отсылку мы выполняем за счет включенного свойства AutoPostBack раскрывающегося списка lstCities. Обратите также внимание, что правильный перенос строковой константы в свойстве SelectCommand второго источника не влияет на возвращаемые SQL-командой результаты.

    <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT DISTINCT City FROM Employees" />
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True" DataSourceID="sourceEmployeeCities" 
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, 
                    Title, City FROM Employees WHERE City=@City">
                <SelectParameters>
                    <asp:ControlParameter Name="City" ControlID="lstCities" 
                        PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу SqlDataSourceParameters.aspx для получения следующего результата
  • Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    9 Anne Dodsworth Sales Representative London

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

    Вернемся к редактору команд и параметров, представленному ранее окном

    После набора команды в области SELECT command щелчок на кнопке Refresh Parameters сканирует введенную команду и заполняет другие области и раскрывающиеся списки редактора нужной информацией, из которой можно потом быстро выбрать требуемое значение. В списке Parameter source перечислены типы элементов, которые могут передавать значения параметров в источник данных:

  • Cookie
  • Control
  • Form
  • Profile
  • QueryString
  • Session
  • В нашем случае используется элемент управления DropDownList, поэтому мы установили тип параметра Control.

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

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

  • Создайте новую страницу с объединенным кодом и именем QueryParameter1.aspx. Назначьте ее стартовой
  • Поместите на страницу источник данных SqlDataSource, списочный элемент ListBox и кнопку Button
  • Настройте свойства элементов так, как показано ниже, включая обработчики, полученные двойным щелчком на свободном месте страницы ( Page_Load ) и на кнопке ( cmdGo_Click )
    (рис ) Разметочная часть страницы QueryParameter1.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                lstCities.SelectedIndex = 0;
        }
        
        protected void cmdGo_Click(object sender, EventArgs e)
        {
            Response.Redirect("QueryParameter2.aspx?selectCity="
                + lstCities.SelectedValue);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница выбора города</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server"
                ProviderName="System.Data.SqlClient"
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                SelectCommand="SELECT DISTINCT City FROM Employees">
            </asp:SqlDataSource>
            
            <h2>
                Выберите город</h2>
            <asp:ListBox ID="lstCities" runat="server"
                DataSourceID="sourceEmployeeCities"
                DataTextField="City">
            </asp:ListBox>
            <br />
            <br />
            <asp:Button ID="cmdGo" runat="server" Text="Применить" 
                OnClick="cmdGo_Click" />
        </div>
        </form>
    </body>
    </html>
  • Создайте новую страницу с объединенным кодом и именем QueryParameter2.aspx
  • Поместите на страницу источник данных SqlDataSource и элемент GridView
  • Настройте свойства элементов так, как показано ниже
    (рис ) Разметочная часть страницы QueryParameter2.aspx<%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница проживающих сотрудников</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, 
                    City FROM Employees WHERE City=@City">
                <SelectParameters>
                    <asp:QueryStringParameter Name="City" QueryStringField="selectCity" />
                </SelectParameters>
            </asp:SqlDataSource>
            
            <h2>
                Проживающие сотрудники</h2>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployees" >
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу QueryParameter1.aspx и убедитесь, что запрос, сформированный на ней и направленный странице QueryParameter2.aspx на обработку, дает тот же самый результат, что и в предыдущем примере
  • Передача параметра источнику данных в хранимых процедурах

    Мы уже говорили о достоинствах хранимых процедур, которые представляют собой именованные SQL-команды, хранимые прямо в базе данных. Они более надежны, поскольку заранее проверены и отлажены. Они более безопасны, поскольку недоступны изменениям со стороны кода страницы, а значит недоступны злоумышленнику. Они упрощают часть нашей .aspx -страницы за счет переноса действительного SQl-запроса в базу данных, который может достигать на странице внушительных размеров. Хранимые процедуры позволяют передавать параметры, в том числе в источники данных.

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

  • Скопируйте командой Add Existing Item из приложения WebSite8 разработанную нами ранее страницу SaveStoredProcedure.aspx и переименуйте ее в
  • Откройте на редактирование страницу SaveStoredProcedure1.aspx и откорректируйте ее так
    (рис ) Код страницы SaveStoredProcedure1.aspx для нового примера<%@ Page Language="C#" %>
        
    <script runat="server">
        
        // Объявления хранимых процедур
        // Выборка городов
        string sql1 =
            "CREATE PROCEDURE GetCityes "
          + "AS "
          + "SELECT DISTINCT City FROM Employees";
        
        // Выборка сотрудников для выбранного города
        string sql2 =
            "CREATE PROCEDURE GetEmployeesByCity "
          + "@paramCity		varchar(15) "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, Title, City "
          + "FROM Employees WHERE City=@paramCity";
        
        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 GetCityes";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE GetCityes\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE GetEmployeesByCity";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROC GetEmployeesByCity\"<br />";
            }
            catch { }
        
            // Выполняем команды в безопасном режиме
            // Добавляем в базу данных хранимые процедуры
            try
            {
                cmd.CommandText = sql1;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetCityes\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql2;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetEmployeesByCity\"<br />";
            }
            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>
  • Исполните страницу SaveStoredProcedure1.aspx, чтобы добавить в базу данных Northwind две хранимых процедуры, одна из которых содержит параметр
  • Теперь осталось воспользоваться этими хранимыми процедурами в источниках данных. Не мудрствуя лукаво, мы просто заменим в двух предыдущих примерах строку SQL-запроса источников на имя хранимой процедуры, а также изменим тип команды.

  • Скопируйте страницу SqlDataSourceParameters.aspx и присвойте ей имя StoredProcedureParam.aspx. Назначьте страницу StoredProcedureParam.aspx стартовой
  • Внесите в страницу StoredProcedureParam.aspx небольшие изменения, чтобы она стала такой
    (рис ) Код страницы StoredProcedureParam.aspx<%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetCityes" 
                SelectCommandType="StoredProcedure">
            </asp:SqlDataSource>
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True" DataSourceID="sourceEmployeeCities" 
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetEmployeesByCity" 
                SelectCommandType="StoredProcedure">
                <SelectParameters>
                    <asp:ControlParameter ControlID="lstCities" 
                        Name="paramCity" PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу StoredProcedureParam.aspx и убедитесь, что она выдает точно такие же результаты, как и страница SqlDataSourceParameters.aspx
  • Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    9 Anne Dodsworth Sales Representative London

    Теперь попробуем проверить работу хранимых процедур при передаче параметра через строку запроса из одной страницы в другую.

  • Выделите в панели Solution Explorer сразу две страницы QueryParameter1.aspx и QueryParameter2.aspx и создайте их копии с именами QueryStoredProcParam1.aspx и QueryStoredProcParam2.aspx. Назначьте страницу QueryStoredProcParam1.aspx стартовой
  • Заполните обе новых страницы следующими поправками
    (рис ) Код страницы QueryStoredProcParam1.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                lstCities.SelectedIndex = 0;
        }
        
        protected void cmdGo_Click(object sender, EventArgs e)
        {
            Response.Redirect("QueryStoredProcParam2.aspx?selectCity="
                + lstCities.SelectedValue);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница выбора города</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetCityes" 
                SelectCommandType="StoredProcedure">
            </asp:SqlDataSource>
            
            <h2>
                Выберите город</h2>
            <asp:ListBox ID="lstCities" runat="server"
                DataSourceID="sourceEmployeeCities"
                DataTextField="City">
            </asp:ListBox>
            <br />
            <br />
            <asp:Button ID="cmdGo" runat="server" Text="Применить" 
                OnClick="cmdGo_Click" />
        </div>
        </form>
    </body>
    </html>
    <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница проживающих сотрудников</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetEmployeesByCity" 
                SelectCommandType="StoredProcedure">
                <SelectParameters>
                    <asp:QueryStringParameter Name="paramCity" QueryStringField="selectCity" />
                </SelectParameters>
            </asp:SqlDataSource>
            
            <h2>
                Проживающие сотрудники</h2>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployees" >
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу QueryStoredProcParam1.aspx и убедитесь, что получился тот же результат, что и в соответствующем примере без использования хранимых процедур, но код вышел более удобным (все переходим на хранимые процедуры!)
  • Обработка исключений источников данных

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

    Сбой в работе источника данных SqlDataSource сопровождается генерацией события, которое происходит сразу после возникновения ошибки. Сбой при выполнении запроса выбрасывает событие Selected, а при обновлении, вставке или удалении записей - события Updated, Inserted или Deleted соответственно. Мы можем предусмотреть обработку каждого из этих событий в отдельности или запланировать общий обработчик на любой из этих сбоев.

    В обработчике ошибок можно получить доступ к объекту сгенерированного исключения через его свойство SqlDataSourceStatusEventArgs.Exception. Если после выброса исключения мы хотим подавить его далнейшее распространение, то в обработчике должны установить SqlDataSourceStatusEventArgs.ExceptionHandled в true.

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

  • Создайте копию страницы SqlDataSourceSimple.aspx с именем SqlDataSourceException.aspx и назначьте ее стартовой
  • Откройте страницу SqlDataSourceException.aspx на редактирование в режиме Design и двойным щелчком на свободном месте Web-формы создайте обработчик Page_Load()
  • В обработчике
  • Следуйте указаниям подсказчика кода и нажимайте клавишу
  • Указанным способом зарегистрируйте события (и создайте для каждого из них обработчики)
  • Selected
  • Updated
  • Inserted
  • Deleted
  • После проведенных действий страница SqlDataSourceException.aspx должна выглядеть так

    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Selected);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Updated);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Inserted);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Deleted);
        }
        
        void SqlDataSource1_Deleted(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Inserted(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Updated(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Selected(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" /></div>
        </form>
    </body>
    </html>
  • Оставьте только один обработчик, а остальные удалите
  • Измените имя оставшегося общего обработчика на SqlDataSource1_Error и внесите это имя в регистрацию каждого события, чтобы блок кода сценария стал таким
  • <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
        }
        
        void SqlDataSource1_Error(object sender, SqlDataSourceStatusEventArgs e)
        {
        }
    </script>

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

  • Поместите в конец страницы текстовую метку Label с именем lblError
  • Заполните обработчик ошибки источника данных предупреждающим кодом, чтобы окончательно страница стала следующей
    (рис ) Окончательный код страницы SqlDataSourceException.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
        }
        
        void SqlDataSource1_Error(object sender, SqlDataSourceStatusEventArgs e)
        {
            if (e.Exception != null)
            {
                // Известить пользователя, что команда не была завершена
                lblError.Text = "<h1 style='color:#FF0000'>"
                    + "При исполнении запроса произошло исключение!</h1>";
        
                // Известить систему, что ошибка обработана
                e.ExceptionHandled = true;            
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" />
            <asp:Label ID="lblError" runat="server" />
        </div>
        </form>
    </body>
    </html>
  • Чтобы испытать обработчик исключения, внесите какую-нибудь ошибку в строку запроса (измените имя поля или исказите SQL-оператор) и запустите страницу. Результат будет таким
  • Редактирование записей в источнике данных

    Чтобы дать возможность пользователю редактировать записи в механизме множественной привязки данных, не обязательно писать дополнительный код. Можно настроить источник данных и элемент отображения так, чтобы редактирование данных происходило автоматически. Списковые элементы отображения не имеют возможностей редактирования, а более сложные элементы, такие как GridView, DetailsView и FormView имеют такие средства, которые можно в них включить.

    Настройка возможностей редактирования состоит из двух шагов:

  • Определения SQL-операций в соответствующих свойствах UpdateQuery, InsertQuery и DeleteQuery
  • Настройки элемента отображения
  • Какие операции с данными мы хотим разрешить пользователю, такие свойства в источнике данных и нужно определять, а остальные можно пропускать.

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

  • Скопируйте страницу SqlDataSourceSimple.aspx с именем SqlDataSourceUpdates.aspx и назначьте ее стартовой
  • Удалите из нее объекты списка ListBox1 и кнопки Button1, чтобы остались только источник данных SqlDataSource1 и элемент отображения GridView1
  • Исполните страницу, чтобы убедиться, что данные, извлеченные источником из таблицы Employees, по-прежнему отображаются в GridView1
  • Теперь настроим источник на обновление данных.

  • Выделите в режиме Design источник данных SqlDataSource1 и в панели Properties щелкните на кнопке с многоточием в поле UpdateQuery, чтобы запустить редактор формирования SQL-команды обновления записей с параметрами
  • Заполните поле UPDATE command редактора строкой SQL-команды с параметрами
    UPDATE Employees SET FirstName=@FirstName, 
           LastName=@LastName, Title=@Title, City=@City 
           FROM Employees WHERE EmployeeID=@EmployeeID
  • После щелчка на кнопке Refresh Parameters автоматически заполнится поле Parameters, как показано на рисунке

    После таких действий редактор параметров сгенерирует дополнительные поправки в настройку источника данных, дескрипторное описание которого станет таким

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
            "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
        UpdateCommand="UPDATE Employees SET FirstName=@FirstName, 
            LastName=@LastName, Title=@Title, City=@City 
            FROM Employees WHERE EmployeeID=@EmployeeID" >
        <UpdateParameters>
            <asp:Parameter Name="FirstName" />
            <asp:Parameter Name="LastName" />
            <asp:Parameter Name="Title" />
            <asp:Parameter Name="City" />
            <asp:Parameter Name="EmployeeID" />
        </UpdateParameters>
    </asp:SqlDataSource>

    Обратите внимание, что имена параметров, предваряемых символом @, мы выбрали совпадающими с именами полей таблицы. Это необходимо потому, что объект GridView1 после привязки к нему источника данных будеть иметь коллекцию столбцов с именами полей, которые и будут выступать в качестве параметров для обмена при обновлении записей. Такое соглашение именования параметров должно выполняться для всех элементов управления данными ASP.NET множественной привязки.

  • Исполните страницу SqlDataSourceUpdates.aspx с внесенными на данном этапе настройками источника данных, чтобы убедиться, что все работает по старому и никаких дополнительных возможностей редактирования пока не появилось
  • Теперь осталось настроить интерфейсный объект GridView1 на возможность редактирования данных. Для этого добавим к GridView1 дополнительный столбец с функциями управления редактированием записей.

  • Вызовите внутреннюю панель настройки объекта GridView1, как показано на рисунке, и выберите пункт Edit Columns
  • Появится диалоговое окно Fields, которое предоставляет в удобном виде настройки столбцов объекта GridView1

  • Используя диалоговое окно Fields выполните следующее:
  • Сбросьте флажок Auto-generate fields, чтобы отключить неконтролируемое отображение всех столбцов
  • Добавьте кнопкой Add в поле Selected fields пять столбцов типа BoundField, которые будут связаны с источником данных
  • Выделяя каждый из пяти добавленных столбцов, определите их свойства DataField (имя связываемого поля в источнике данных) и HeaderText (отображаемый на экране заголовок столбца)
  • Добавьте шестой столбец типа CommandField/Edit, Update, Cancel и задайте ему следующие свойства:
  • EditText="Редакция"
  • CancelText="Отмена"
  • UpdateText="Применить
  • ButtonType="Button"
  • HeaderText="Изменить"
  • HeaderStyle/BackColor="Red"
  • HeaderStyle/ForeColor="Yellow"
  • В результате таких опосредованных действий оболочка сформирует следующее описание объекта GridView1

    <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                AutoGenerateColumns="False" >
        <Columns>
            <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
            <asp:BoundField DataField="FirstName" HeaderText="Имя" />
            <asp:BoundField DataField="LastName" HeaderText="Отчество" />
            <asp:BoundField DataField="Title" HeaderText="Статус" />
            <asp:BoundField DataField="City" HeaderText="Проживает" />
            <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                ShowEditButton="True" ShowHeader="True"
                EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                <HeaderStyle BackColor="Red" ForeColor="Yellow" />
            </asp:CommandField>
        </Columns>
    </asp:GridView>

    В итоге окончательный код страницы будет следующим

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                    "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
                UpdateCommand="UPDATE Employees SET FirstName=@FirstName, 
                    LastName=@LastName, Title=@Title, City=@City 
                    FROM Employees WHERE EmployeeID=@EmployeeID" >
                <UpdateParameters>
                    <asp:Parameter Name="FirstName" />
                    <asp:Parameter Name="LastName" />
                    <asp:Parameter Name="Title" />
                    <asp:Parameter Name="City" />
                    <asp:Parameter Name="EmployeeID" />
                </UpdateParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                        AutoGenerateColumns="False" >
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="Title" HeaderText="Статус" />
                    <asp:BoundField DataField="City" HeaderText="Проживает" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу и испытайте ее функциональность. Можно спокойно редактировать записи, не написав при этом ни строчки кода - все делает встроенный механизм ASP.NET
  • Страница SqlDataSourceUpdates.aspx в действии может выглядеть так

    № п/п Имя Фамилия Статус Проживает Изменить
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Владимир Снетков Доцент 007 Красноярск

    В добавленном столбце управления редактированием (его можно разместить на любом месте, изменив порядок следования дескрипторов) щелчок на кнопке "Применить" инициирует обратную отсылку, которая при поступлении на сервер передает источнику значения коллекции параметров, используя имена полей, и затем вызывает метод SqlDataSource.Update() для внесения изменений в базу данных.

    Можно создать аналогичные параметризованные команды в источнике данных для свойств InsertQuery и DeleteQuery. Чтобы разрешить, например, вставку в элементе отображения GridView, нужно добавить управляющий столбец типа CommandField и установить его отображение в свойстве ShowInsertButton

    Обновление данных при параллельном доступе

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

    Такая ситуация называется конкуренцией данных или конфликтом изменений. Для ее решения применяются разные способы, один из которых опирается на тот факт, что в элементе отображения GridView хранятся как оригинальные значения полей, так и их последние изменения. Сам источник данных может оперировать с двумя видами параметров: оригинальными, носящими имя поля с префиксом original по умолчанию (например, @original_FirstName ), и измененными, носящими имя поля. Источник может понимать имена оригинальных параметров и с другим префиксом, который можно установить в его свойстве OldValuesParameterFormatString (например, xxx{0} ).

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

    UpdateCommand="UPDATE Employees SET FirstName=@FirstName,
        LastName=@LastName, Title=@Title, City=@City
        FROM Employees WHERE EmployeeID=@original_EmployeeID
        AND FirstName=@original_FirstName
        AND LastName=@original_LastName
        AND Title=@original_Title AND City=@original_City"

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

    Подобную проверку конфликта изменений данных не обязательно закладывать в SQL-команду обновления. Эту обязанность можно возложить на источник данных, поменяв в нем значение свойства ConflictDetection с OverwriteChanges (заменять без проверки, установлено по умолчанию) на CompareAllValues (заменять только при совпадении всех оригинальных значений).

    Применение параметров обновления, не совпадающими с именами полей

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

    Рассмотрим этот способ на примере.

  • Скопируйте страницу SqlDataSourceUpdates.aspx с именем OtherNameParamUpdates.aspx и назначьте ее стартовой
  • Проверьте, что страница OtherNameParamUpdates.aspx работает
  • Назовите параметры источника другими именами, отличными от имен полей, например так
    (рис ) Назначение параметрам команды обновления других имен<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
            "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
        UpdateCommand="UPDATE Employees SET FirstName=@paramFirstName, 
            LastName=@paramLastName, Title=@paramTitle, City=@paramCity 
            FROM Employees WHERE EmployeeID=@paramEmployeeID" >
        <UpdateParameters>
            <asp:Parameter Name="paramFirstName" Type="string" />
            <asp:Parameter Name="paramLastName" Type="string" />
            <asp:Parameter Name="paramTitle" Type="string" />
            <asp:Parameter Name="paramCity" Type="string" />
            <asp:Parameter Name="paramEmployeeID" Type="string" />
        </UpdateParameters>
    </asp:SqlDataSource>
  • Запустите страницу OtherNameParamUpdates.aspx и убедитесь, что конечные обновления в базе данных не выполняются из за того, что измененные поля не передаются из GridView1 в источник данных SqlDataSource1
  • Для решения задачи используем событие SqlDataSource.Updating, которое возбуждается непосредственно перед подтверждением обновлений.

  • Перейдите в режим Design редактирования страницы OtherNameParamUpdates.aspx и двойным щелчком на свободном месте Web-формы создайте обработчик события Page_Load()
  • Зайдите в тело обработчика и начинайте регистрировать событие Updating источника данных, набирая следующий код
    SqlDataSource1.Updating +=
    и далее используя клавишу
  • Оболочка создаст правильную заготовку обработчика события Updating. Теперь можно полностью удалить обработчик Page_Load() как неиспользуемый.

  • Удалите ненужный теперь обработчик Page_Load()
  • Добавьте к описанию источника атрибут OnUpdating="SqlDataSource1_Updating"
  • Здесь мы пошли более длинным, но универсальным путем создания заготовки обработчика SqlDataSource1_Updating(). Знакомый нам более короткий и традиционный путь - создать заготовку обработчика через панель Properties, при этом имя обработчика автоматически добавится в описание источника данных.

  • Заполните обработчик события Updating так, чтобы страница имела следующий вид
    (рис ) Окончательный код страницы OtherNameParamUpdates.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        void SqlDataSource1_Updating(object sender, SqlDataSourceCommandEventArgs e)
        {
            // Передать значения нестандартным именам параметров
            e.Command.Parameters["@paramFirstName"].Value =
                e.Command.Parameters["@FirstName"].Value;
            e.Command.Parameters["@paramLastName"].Value =
                e.Command.Parameters["@LastName"].Value;
            e.Command.Parameters["@paramTitle"].Value =
                e.Command.Parameters["@Title"].Value;
            e.Command.Parameters["@paramCity"].Value =
                e.Command.Parameters["@City"].Value;
            e.Command.Parameters["@paramEmployeeID"].Value =
                e.Command.Parameters["@EmployeeID"].Value;
        
            // Удалить из коллекции стандартные имена параметров
            e.Command.Parameters.Remove(e.Command.Parameters["@FirstName"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@LastName"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@Title"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@City"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@EmployeeID"]);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                    "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
                UpdateCommand="UPDATE Employees SET FirstName=@paramFirstName, 
                    LastName=@paramLastName, Title=@paramTitle, City=@paramCity 
                    FROM Employees WHERE EmployeeID=@paramEmployeeID" 
                    OnUpdating="SqlDataSource1_Updating" >
                <UpdateParameters>
                    <asp:Parameter Name="paramFirstName" Type="string" />
                    <asp:Parameter Name="paramLastName" Type="string" />
                    <asp:Parameter Name="paramTitle" Type="string" />
                    <asp:Parameter Name="paramCity" Type="string" />
                    <asp:Parameter Name="paramEmployeeID" Type="string" />
                </UpdateParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                        AutoGenerateColumns="False" >
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="Title" HeaderText="Статус" />
                    <asp:BoundField DataField="City" HeaderText="Проживает" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу OtherNameParamUpdates.aspx и убедитесь, что она сохранила прежние способности обновления данных в базе
  • Переходим к следующей части...

    Старательно сопишь - значит победишь

    Компонент управления источником данных ObjectDataSource

    Компонент ObjectDataSource находится на вкладке Data панели Toolbox. Он позволяет создавать декларативным (на этапе проектирования) способом связь между элементом отображения и компонентом доступа к данным, который запрашивает и обновляет их. Он является более гибким по сравнению с источником данных SqlDataSource. Но для корректного применения пользовательский класс доступа к данным, которым может быть и разработанный нами класс, должен подчиняться нескольким правилам:

  • Пользовательский класс не должен сохранять состояние, поскольку ObjectDataSource создает его экземпляры по необходимости и уничтожает их за собой в конце каждого запроса
  • Пользовательский класс должен иметь конструктор по умолчанию (без аргументов)
  • Компонент должен быть представлен только одним классом. Если для решения разных частей одной задачи используются несколько классов, то они должны быть упакованы в оболочку одного высокоуровневого класса
  • Ни один из основных рабочих методов, используемых для извлечения и обновления записей, не должен быть статическим. Они должны быть только методами экземпляра
  • Каждый тип запроса (извлечение, вставка, удаление и обновление) должен быть представлен единственным методом, возвращающим результаты запроса
  • Результаты запроса должны размещаться в общедоступных свойствах, которые представляют собой коллекции, массивы или списковые объекты, реализующие интерфейс IEnumerable
  • Пользовательский компонент доступа к данным должен размещаться в папке App_Code или быть скомпилированным в сборку и помещен в папку Bin
  • Выполнение этих правил гарантирует, что привязка данных будет простой и организована без дополнительных усилий с нашей стороны. Отступление от правил требует написания дополнительного кода.

    Простое использование ObjectDataSource

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

  • На корне Web-дерева создайте папку App_Code командой контекстного меню Add ASP.NET Folder
  • На папке Add_Code выполните команду Website/Add Existing Item и скопируйте из папки WebSite8/App_Code все ее содержимое, которое представляет собой описание пользовательского класса EmployeeDB
  • Вспомним, что ранее мы поместили в базу данных с помощью страницы WebSite8/SaveStoredProcedure.aspx хранимые процедуры.

  • Скопируйте в текущее приложение страницу WebSite8/SaveStoredProcedure.aspx, откройте ее и посмотрите, что хранимые процедуры объявляются в ней так
  • // Объявления хранимых процедур
        // Вставка записи
        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";

    В их числе и процедура GetAllEmployees, которую реализует в пользовательском классе EmployeeDB одноименный метод GetAllEmployees(). Эта процедура извлекает из таблицы Employees записи четырех полей в порядке их следования в строке запроса. Если мы укажем помещенному на страницу компоненту ObjectDataSource имя нашего пользовательского класса и имя метода, который реализует в классе нужный SQL-запрос, то ObjectDataSource сам создаст экземпляр этого пользовательского класса и вызовет указанный метод. В результате, объект пользовательского класса будет заполнен данными, которые увидит пользователь через привязанные к компоненту ObjectDataSource элементы отображения.

  • Создайте новую страницу с совмещенным кодом и именем ObjectDataSourceSimple.aspx. Назначьте ее стартовой
  • Перенесите на страницу ObjectDataSourceSimple.aspx из панели Toolbox следующие компоненты
  • ObjectDataSource из вкладки Data
  • ListBox из вкладки Standard
  • GridView из вкладки Data
  • Выполните простые настройки свойств компонентов, чтобы окончательный код страницы стал таким
    (рис ) Код страницы ObjectDataSourceSimple.aspx после декларативных настроек <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees"
                TypeName="EmployeeDB" />
            <asp:ListBox ID="ListBox1" runat="server" 
                DataSourceID="ObjectDataSource1" DataTextField="LastName" />
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="ObjectDataSource1" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceSimple.aspx, чтобы получить следующий результат
  • LastName TitleOfCourtesy EmployeeID FirstName
    Davolio Ms. 1 Nancy
    Fuller Dr. 2 Andrew
    Leverling Ms. 3 Janet
    Peacock Mrs. 4 Margaret
    Buchanan Mr. 5 Steven
    Suyama Mr. 6 Michael
    King Mr. 7 Robert
    Callahan Ms. 8 Laura
    Снетков Доцент 007 9 Владимир

    Исключительно простой получилась привязка данных к элементам отображения через компонент ObjectDataSource. Никакого кода не пришлось писать. Прямо - работа для дизайнеров или кухарок! Но пользовательский-то класс EmployeeDB мы ранее с вами создали. Его просто здесь не видно - он работает за кулисами и вызывается по ссылке, указанной в свойстве TypeName компонента ObjectDataSource. Элементы отображения, в свою очередь, получают свою порцию данных через ссылку на заполненный ObjectDataSource, указанную в свойстве DataSourceID.

    Использование ObjectDataSource с параметризованным конструктором пользовательского класса

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

    Компонент ObjectDataSource имеет богатую модель событий, представленную ниже

  • System.Web.UI.WebControls.ObjectDataSource.Deleted
  • System.Web.UI.WebControls.ObjectDataSource.Deleting
  • System.Web.UI.WebControls.ObjectDataSource.Filtering
  • System.Web.UI.WebControls.ObjectDataSource.Inserted
  • System.Web.UI.WebControls.ObjectDataSource.Inserting
  • System.Web.UI.WebControls.ObjectDataSource.ObjectCreated
  • System.Web.UI.WebControls.ObjectDataSource.ObjectCreating
  • System.Web.UI.WebControls.ObjectDataSource.ObjectDisposing
  • System.Web.UI.WebControls.ObjectDataSource.Selected
  • System.Web.UI.WebControls.ObjectDataSource.Selecting
  • System.Web.UI.WebControls.ObjectDataSource.Updated
  • System.Web.UI.WebControls.ObjectDataSource.Updating
  • Эти события можно разделить на две категории:

  • Заканчивающиеся на ing - происходят во время выполнения задачи и позволяют вмешаться в этот процесс, что-то настроить или прервать
  • Заканчивающиеся на ed - происходят по завершении задачи и удобны для протоколирования действий, синхронизации других элементов управления или обработки ошибок
  • Если мы хотим передать компоненту ObjectDataSource уже инициализированный параметризованным конструктором готовый экземпляр пользовательского класса, то этот экземпляр нужно самим создать с вызовом нужного конструктора. И сделать это нужно в обработчике события ObjectDataSource.ObjectCreating.

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

    using 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;
        }
    }
  • Скопируйте страницу ObjectDataSourceSimple.aspx под именем ObjectDataSourceDesigner.aspx и назначьте ее стартовой
  • Запустите страницу, чтобы проверить, что конструктор по умолчанию работает и извлекает из файла Web.Config строку соединения с именем "Northwind"
  • В файле Web.Config скопируйте строку соединения с идентификатором "Northwind" под другим именем "CloneNorthwind", после чего должно получиться следующее
    (рис ) Файл Web.Config после клонирования строки соединения<?xml version="1.0"?>
    <configuration>
    	<connectionStrings>
    		<add name="Northwind"      connectionString="Data Source=localhost; 
       Initial Catalog=Northwind; user id=sa; password=;" />
    		<add name="CloneNorthwind" connectionString="Data Source=localhost; 
       Initial Catalog=Northwind; user id=sa; password=;" />
    	</connectionStrings>
    	<system.web>
    		<compilation debug="true"/>
    	</system.web>
    </configuration>
  • Измените в файле Web.Config имя строки соединения "Northwind" на несуществующее имя, например "Northwind1" и вновь запустите страницу. Теперь страница выдает ошибку, поскольку конструктор по умолчанию ищет в файле Web.Config строку соединения с именем "Northwind"
  • Пусть теперь это ошибочное имя строки соединений пока побудет в файле Web.Config, а мы попробуем запустить конструктор по умолчанию, чтобы страница искала строку соединения с именем "CloneNorthwind", которая у нас есть.

  • Откройте страницу на редактирование в режиме Design и выделите на ней объект ObjectDataSource1
  • Переведите панель Properties в режим редактирования событий и двойным щелчком на поле
  • Заполните обработчик так, чтобы окончательный код страницы ObjectDataSourceDesigner.aspx был следующим
    (рис ) Окончательный код страницы ObjectDataSourceDesigner.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {
            e.ObjectInstance = new EmployeeDB("CloneNorthwind");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees"
                TypeName="EmployeeDB" 
                OnObjectCreating="ObjectDataSource1_ObjectCreating" />
            <asp:ListBox ID="ListBox1" runat="server" 
                DataSourceID="ObjectDataSource1" DataTextField="LastName" />
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="ObjectDataSource1" />
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу ObjectDataSourceDesigner.aspx и убедитесь, что опять все заработало, поскольку новую строку соединения пользовательский объект теперь получает через параметризованный конструктор в обработчике
  • Верните в файле Web.Config прежнее имя "Northwind" строке соединения, поскольку его используют все наши предыдущие страницы (строку с именем "CloneNorthwind" также оставьте для работы этого примера)
  • Использование ObjectDataSource с параметризованным методом пользовательского класса

    По синтаксису настройки компонента ObjectDataSource мы не можем использовать ни параметризованный конструктор, ни параметризованный метод, поскольку свойствам SelectMethod, InsertMethod, DeleteMethod и UpdateMethod можно присваивать только имя метода. Реальные же методы пользовательского класса могут содержать параметры. Например, в нашем классе EmployeeDB содержатся следующие параметризованные методы:

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public EmployeeDetails GetEmployee(int employeeID) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • public void UpdateEmployee(int employeeID, string firstName,string lastName, string titleOfCourtesy) {...}
  • Мы воспользуемся методом GetEmployee(int employeeID), который ранее был описан так

    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();
            }
        }

    Метод GetEmployee() вызывает хранимую процедуру GetEmployee с параметром, имеющую следующий вид

    // Выбрать запись
        string sql6 =
            "CREATE PROCEDURE GetEmployee "
          + "@EmployeeID        int "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, TitleOfCourtesy "
          + "FROM Employees "
          + "WHERE EmployeeID = @EmployeeID";

    Сейчас наша задача - создать страницу на основе компонента ObjectDataSource и пользовательского класса EmployeeDB, в которой при загрузке автоматически заполнялся бы список значениями EmployeeID из таблицы Employees, а при щелчке пользователя на конкретном элементе списка выдавались бы остальные сведения о выбранном сотруднике.

  • Создайте новую страницу с совмещенным кодом под именем ObjectDataSourceParameters.aspx и назначьте ее стартовой
  • Поместите на страницу из панели Toolbox следующие компоненты в порядке их перечисления:
  • ObjectDataSource из вкладки Data
  • Дескриптор <h2>Выберите ID сотрудника</h2>
  • ListBox из вкладки Standard
  • ObjectDataSource из вкладки Data
  • Дескриптор <h2>Данные о сотруднике</h2>
  • DetailsView из вкладки Data
  • Настроим компоненты страницы ObjectDataSourceParameters.aspx декларативным способом в режиме Design через панель Properties.

  • Выделите первый источник ObjectDataSource1 и установите для него следующие свойства
  • SelectMethod="GetAllEmployees" - автоматически вызываемый метод
  • TypeName="EmployeeDB" - применяемый пользовательский класс
  • Выделите список ListBox1 и установите для него следующие свойства
  • DataSourceID="ObjectDataSource1" - подключенный источник данных
  • DataTextField="EmployeeID" - видимые данные списка
  • AutoPostBack="True" - инициирует обратную отсылку списка
  • Rows=9 - количество видимых строк
  • Width=100 - ширина в пикселах
  • Выделите второй источник ObjectDataSource2 и установите для него следующие свойства
  • SelectMethod="GetEmployee" - автоматически вызываемый метод
  • TypeName="EmployeeDB" - применяемый пользовательский класс
  • Щелчком на кнопке ( ...) в поле свойства
  • Работа этого окна трансформируется в следующий дескрипторный код разметочной части страницы

    <asp:ObjectDataSource ID="ObjectDataSource2" runat="server" 
        SelectMethod="GetEmployee" TypeName="EmployeeDB" 
        OnSelecting="ObjectDataSource2_Selecting" >
        <SelectParameters>
            <asp:ControlParameter ControlID="ListBox1" 
                Name="employeeID" PropertyName="SelectedValue" />
        </SelectParameters>
    </asp:ObjectDataSource>

    Имя для передаваемого в источник ObjectDataSource2 параметра должно повторять имя формального параметра, указанного при объявлении метода

    public EmployeeDetails GetEmployee(int employeeID) {...}

    поскольку источник допускает перегрузку прикрепленного метода на основании анализа числа переданных параметров и их имен в объявлении метода (явного-то вызова метода нет!).

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

  • Выделите элемент отображения данных DetailsView1 и установите для него следующие свойства
  • DataSourceID="ObjectDataSource2"
  • AutoGenerateRows="False" - подавляет самостоятельное генерирование строк для всех полей без разбора
  • Запустите страницу ObjectDataSourceParameters.aspx и получите ошибку выполнения!!!
  • Дело здесь в том, что когда страница запрашивается в первый раз, нет никакого выбранного значения в элементе ListBox1, но элемент ObjectDataSource2 все равно вызовет метод GetEmployee() с неопределенным значением employeeID. Это и вызовет генерацию исключения в поиске данных. Ситуацию можно исправить, если перехватить попытку привязки в элементе ObjectDataSource2 и явно отменить ее, когда параметр employeeID не определен.

    Для этого нужно обработать событие ObjectDataSource.Selecting, которое как раз происходит перед выполнением SQL-запроса по поиску данных в базе данных. Если мы проверим коллекцию InputParameters и не обнаружим в ней параметра employeeID, то работу ObjectDataSource2 нужно будет прервать.

  • Создайте в панели Properties для события ObjectDataSource2.Selecting обработчик, который заполните так, чтобы окончательный код страницы ObjectDataSourceParameters.aspx был следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource2_Selecting(object sender, 
            ObjectDataSourceSelectingEventArgs e)
        {
            if (e.InputParameters["employeeID"] == null)
                e.Cancel = true;
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees" TypeName="EmployeeDB" />
                    
            <h2>
                Выберите ID сотрудника</h2>
            <asp:ListBox ID="ListBox1" runat="server" AutoPostBack="True" 
                DataSourceID="ObjectDataSource1" DataTextField="EmployeeID" 
                Rows="9" Width="100px" />
                
            <asp:ObjectDataSource ID="ObjectDataSource2" runat="server" 
                SelectMethod="GetEmployee" TypeName="EmployeeDB" 
                OnSelecting="ObjectDataSource2_Selecting" >
                <SelectParameters>
                    <asp:ControlParameter ControlID="ListBox1" 
                        Name="employeeID" PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:ObjectDataSource>
                
            <h2>
                Данные о сотруднике</h2>
            <asp:DetailsView ID="DetailsView1" runat="server" 
                Height="50px" Width="125px" DataSourceID="ObjectDataSource2" 
                AutoGenerateRows="False" >
                <Fields>
                    <asp:BoundField DataField="LastName" 
                        HeaderText="LastName" SortExpression="LastName" />
                    <asp:BoundField DataField="TitleOfCourtesy" 
                        HeaderText="TitleOfCourtesy" SortExpression="TitleOfCourtesy" />
                    <asp:BoundField DataField="EmployeeID" 
                        HeaderText="EmployeeID" SortExpression="EmployeeID" />
                    <asp:BoundField DataField="FirstName" 
                        HeaderText="FirstName" SortExpression="FirstName" />
                </Fields>
            </asp:DetailsView>
        </div>
        </form>
    </body>
    </html>

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

  • Исполните страницу ObjectDataSourceParameters.aspx и получите результат
  • Выберите ID сотрудника

    Данные о сотруднике

    LastName Снетков
    TitleOfCourtesy Доцент 007
    EmployeeID 9
    FirstName Владимир

    Аналогичным образом можно задействовать и другие методы нашего пользовательского класса EmployeeDB, имеющие параметры:

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • public void UpdateEmployee(int employeeID, string firstName, string lastName, string titleOfCourtesy) {...}
  • Использование ObjectDataSource для обновления записей

    Продемонстрируем возможность подключения к ObjectDataSource метода UpdateEmployee() пользовательского класса EmployeeDB, предназначенного для обновления записей таблицы Employees. За одно посмотрим, как правильно вызвать перегрузку этого метода, если в качестве параметра используется другой пользовательский тип, экземпляр которого прежде нужно создать, и только потом передать компоненту ObjectDataSource как параметр.

    Применение исходного метода UpdateEmployee()

    Вначале используем метод UpdateEmployee() в том виде с четырьма параметрами, как он был разработан нами ранее. Для сокращения работы используем часть наработок страницы SqlDataSourceUpdates.aspx. Метод UpdateEmployee() реализует SQL-команду хранимой процедуры UpdateEmployee, имеющей следующий вид

    // Обновление записи
    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";

    Сам существующий метод UpdateEmployee() описывается так в файле App_Code/UpdateEmployeeDB.cs

    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();
        }
    }
  • Скопируйте страницу SqlDataSourceUpdates.aspx с именем ObjectDataSourceUpdates1.aspx и назначьте ее стартовой
  • Запустите страницу ObjectDataSourceUpdates1.aspx на выполнение и удостовертесь, что она работоспособна
  • Удалите элемент SqlDataSource1 и на его месте расположите объект ObjectDataSource1
  • Настройте объект ObjectDataSource1 следующим образом
  • TypeName="EmployeeDB"
  • SelectMethod="GetAllEmployees"
  • UpdateMethod="UpdateEmployee"
  • Объект GridView1 подкорректируйте так:
  • DataSourceID="ObjectDataSource1" - поменять подключаемый источник данных
  • <asp:BoundField DataField="City" HeaderText="Проживает" /> - удалить строку с отсутствующим в SQL-команде полем
  • <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" /> - изменить имя поля на имеющееся в SQL-команде
  • В окончательном виде страница ObjectDataSourceUpdates1.aspx должна стать такой

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                TypeName="EmployeeDB"            
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" />
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>

    Метод UpdateEmployee() автоматически получает от объекта отображения GridView1 значения параметров для обновления за счет того, что имена формальных аргументов метода совпадают с именами полей элемента отображения (регистр не учитывается). Если в определении метода изменить имя хотя-бы одного формального аргумента, то страница выдаст ошибку о запросе несуществующего в таблице Employees поля.

  • Исполните страницу ObjectDataSourceUpdates1.aspx и убедитесь, что она функционирует в соответствии с задуманным и в одном из вариантов генерирует такой HTML-результат
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms.
    2 Andrew Fuller Dr.
    3 Janet Leverling Ms.
    4 Margaret Peacock Mrs.
    5 Steven Buchanan Mr.
    6 Michael Suyama Mr.
    7 Robert King Mr.
    8 Laura Callahan Ms.
    9 Владимир Снетков Доцент 007

    Применение перегруженного метода UpdateEmployee()

    Исходный вариант метода UpdateEmployee() имеет четыре аргумента, которые автоматически используются как параметры канала передачи данных от источника отображения к полям таблицы. Но для тех же целей можно использовать любой промежуточный объект, описание (читай - класс, тип ) которого будет удовлетворять следующим условиям:

  • Если имеется параметризованный конструктор, то и обязательно должен быть объявлен конструктор по умолчанию, даже пустой (это стандартное требование языков C++ и C#)
  • Класс должен иметь общедоступные ( public ) и записываемые (не readonly ) свойства с именами, совпадающими с полями таблицы, для передачи данных в которую он будет использоваться
  • Таким условиям удовлетворяет вспомогательный класс EmployeeDetails, который мы разработали ранее и который имеет следующее описание в файле App_Code/EmployeeDetails.cs

    using 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; }
        }
    }

    Объект класса EmployeeDetails вполне может служить каналом передачи данных из элемента GridView в таблицу при выполнении метода обновления, если его использовать в качестве аргумента этого метода. Дополним класс EmployeeDB еще одним перегружаемым методом UpdateEmployee().

  • Откройте на редактирование файл App_Code/UpdateEmployeeDB.cs и дополните его (скопируйте и чуть подправьте!) следующим кодом перегружаемого метода UpdateEmployee()
  • 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();
            }
        }
        
        // Перегружаемый метод с объектом класса EmployeeDetails
        public void UpdateEmployee(EmployeeDetails emp)
        {
            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 = emp.EmployeeID;
            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;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }

    Теперь осталось создать страницу, реализующую этот новый канал обмена данными.

  • Сделайте копию страницы ObjectDataSourceUpdates1.aspx с именем ObjectDataSourceUpdates2.aspx и назначьте ее стартовой
  • Дополните описание объекта ObjectDataSource1 всего-лишь одним м-а-а-а-леньким атрибутом
  • DataObjectTypeName="EmployeeDetails" - для указания того, по описанию какого класса создавать промежуточный объект для передачи параметров в SQL-команду обновления данных в перегруженном методе UpdateEmployee()
  • Окончательный код страницы ObjectDataSourceUpdates2.aspx будет следующим

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                TypeName="EmployeeDB" 
                DataObjectTypeName="EmployeeDetails"            
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" />
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceUpdates2.aspx и убедитесь, что ее функциональность в точности повторяет функциональность страницы ObjectDataSourceUpdates1.aspx
  • Чтобы рассеять сомнения в том, что работает именно новый метод
    public void UpdateEmployee(EmployeeDetails emp){...}
    заключите (временно!) в файле UpdateEmployeeDB.cs исходный метод в комментарные скобки /*...*/
    /*		
     // Исходный метод 
     public void UpdateEmployee(int employeeID, string firstName,
                                string lastName, string titleOfCourtesy)
     {
      ...
     }
    */
  • Вновь запустите страницу ObjectDataSourceUpdates2.aspx. Функциональность полностью сохранилась, значит работает все-таки наш новый метод
  • Раскомментируйте исходный метод UpdateEmployee()
  • Применение иного метода обновления и нестандартных имен аргументов

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

    Такая свобода требует написания дополнительного кода для подмены (в последний момент) в обработчике события ObjectDataSource.Updating значений в терминах GridView, в значения в терминах источника ObjectDataSource, передающего эти значения дальше в таблицу Employees для обновления конечных данных. Ранее мы подобную задачу решали на странице OtherNameParamUpdates.aspx применительно к элементу SqlDataSource.

  • Добавьте в класс EmployeeDB файла App_Code/UpdateEmployeeDB.cs копию исходного метода с четырьмя параметрами и подкорректируйте его так (теперь в файле будут три метода обновления)
    (рис ) Новый метод обновления в классе EmployeeDB файла App_Code/UpdateEmployeeDB.csusing System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        // Исходный метод 
        public void UpdateEmployee(int employeeID, string firstName,
                                   string lastName, string titleOfCourtesy)
        {
         ...
        }
        
        // Перегружаемый метод с объектом класса EmployeeDetails
        public void UpdateEmployee(EmployeeDetails emp)
        {
         ...	
        }
        
        // Новый метод обновления данных
        // Изменены: имя метода, имена аргументов и порядок их следования
        public void UpdateEmployeeStrict(string titleOfCourtesyStrict, string lastNameStrict,
                                   string firstNameStrict, int employeeIDStrict)
        {
            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 = employeeIDStrict;
            cmd.Parameters.Add(new SqlParameter("@FirstName", SqlDbType.NVarChar, 10));
            cmd.Parameters["@FirstName"].Value = firstNameStrict;
            cmd.Parameters.Add(new SqlParameter("@LastName", SqlDbType.NVarChar, 20));
            cmd.Parameters["@LastName"].Value = lastNameStrict;
            cmd.Parameters.Add(new SqlParameter("@TitleOfCourtesy", SqlDbType.NVarChar, 25));
            cmd.Parameters["@TitleOfCourtesy"].Value = titleOfCourtesyStrict;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
  • Сделайте копию страницы ObjectDataSourceUpdates1.aspx с именем ObjectDataSourceUpdates3.aspx и назначьте ее стартовой
  • Сейчас мы воспользуемся мощью оболочки Visual Studio 2005 и одним махом сгенерируем нужный нам код разметочной части страницы

  • Безжалостно (!!!) удалите в режиме Design настроенный (как-то там) объект ObjectDataSource1 и на его место из вкладки Data панели Toolbox скопируйте новый объект типа ObjectDataSource с прежним именем
  • Щелкните мышью на треугольнике в правом верхнем углу объекта
  • Щелкните на единственной, пока, ссылке Configure Data Source (сконфигурировать источник данных), чтобы запустить мастер генерации настроек элемента ObjectDataSource1
  • На первой странице мастера из раскрывающегося списка выберите используемый класс
  • На новой странице вначале установите вкладку SELECT, через которую установите метод
  • Установите вкладку UPDATE, через которую установите наш новый метод обновления данных
  • После щелчка на кнопке Finish, поскольку на странице уже имеется элемент GridView1, не согласованный с новыми настройками источника данных, мастер выдаст предупреждение с предложением автоматически привести их в соответствие (мы занимаемся декларативной стыковкой элементов под неусыпным присмотром среды проектирования - оболочки!)

  • Ответьте отрицательно - нам не нужны новые замены в настройках GridView1
  • Вот какой код сгенерировал мастер настроек объекта ObjectDataSource1 (Ах, какой умный Большой Билл! Даже уточнения типов параметров сгенерировал сам)

    <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
        TypeName="EmployeeDB"
        SelectMethod="CountEmployees" 
        UpdateMethod="UpdateEmployeeStrict">
        <UpdateParameters>
            <asp:Parameter Name="titleOfCourtesyStrict" Type="String" />
            <asp:Parameter Name="lastNameStrict" Type="String" />
            <asp:Parameter Name="firstNameStrict" Type="String" />
            <asp:Parameter Name="employeeIDStrict" Type="Int32" />
        </UpdateParameters>
    </asp:ObjectDataSource>

    Теперь осталось написать код подмены значений параметров из GridView1 в принимающих параметрах источника ObjectDataSource1 перед обновлением конечных данных

  • Выделите в режиме Design объект ObjectDataSource1 и через панель Properties двойным щелчком на поле события Updating создайте для него заготовку обработчика
  • Заполните обработчик так, чтобы окончательный код страницы стал следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource1_Updating(object sender, ObjectDataSourceMethodEventArgs e)
        {
            // Перегрузить данные на других лошадей для
            // продолжения поездки к базе данных
            e.InputParameters["firstNameStrict"] = e.InputParameters["FirstName"];
            e.InputParameters["lastNameStrict"] = e.InputParameters["LastName"];
            e.InputParameters["titleOfCourtesyStrict"] = e.InputParameters["TitleOfCourtesy"];
            e.InputParameters["employeeIDStrict"] = e.InputParameters["EmployeeID"];
        
            // Удалить из коллекции параметров ненужное
            e.InputParameters.Remove("FirstName");
            e.InputParameters.Remove("LastName");
            e.InputParameters.Remove("TitleOfCourtesy");
            e.InputParameters.Remove("EmployeeID");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                TypeName="EmployeeDB"
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployeeStrict" 
                OnUpdating="ObjectDataSource1_Updating">
                    
                <UpdateParameters>
                    <asp:Parameter Name="titleOfCourtesyStrict" Type="String" />
                    <asp:Parameter Name="lastNameStrict" Type="String" />
                    <asp:Parameter Name="firstNameStrict" Type="String" />
                    <asp:Parameter Name="employeeIDStrict" Type="Int32" />
                </UpdateParameters>
                    
            </asp:ObjectDataSource>
                
                    
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                    
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Link" CancelText="Отмена" 
                        EditText="Редакция" HeaderText="Изменить"
                        ShowEditButton="True" ShowHeader="True" UpdateText="Применить">
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
                    
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>

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

  • Запустите на выполнение ObjectDataSourceUpdates3.aspx и получите работающую страницу редактирования данных, стерилизованный (ссылки отключены) вариант HTML-вывода которой приведен ниже
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms. Редакция
    2 Andrew Fuller Dr. Редакция
    3 Janet Leverling Ms. Редакция
    4 Margaret Peacock Mrs. Редакция
    5 Steven Buchanan Mr. Редакция
    6 Michael Suyama Mr. Редакция
    7 Robert King Mr. Редакция
    8 Laura Callahan Ms. Редакция
    9 Владимир Снетков Доцент 007 Редакция

    Использование ObjectDataSource для вставки и удаления записей

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

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • Рассмотрим операцию вставки с применением пользовательского класса EmployeeDB. Эту операцию мы вправе добавить на страницу в дополнение к уже реализованной операции обновления. Особенность реализации метода вставки состоит в том, что он возвращает значение, указывающее содержимое поля EmployeeID. Это поле в таблице является ключевым и имеет свойство AutoIncrement (автоматическое увеличение). Его значение, возвращенное методом, можно проигнорировать, но можно и перехватывать и использовать для каких-нибудь целей, например извещения пользователя.

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

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

  • Добавьте после элемента GridView1 компонент LinkButton и создайте для него обработчик LinkButton1_Click()
  • В режиме Design двойным щелчком на свободном месте Web-формы создайте обработчик Page_Load() страницы, в котором мы будем формировать сообщение о номере добавленной в таблицу записи
  • Поместите после элемента LinkButton1 компонент Label с именем lblInfo, в котором мы будем размещать сообщения для пользователя о добавленной или удаленной записи таблицы Employees
  • В режиме Design вызовите встроенную панель источника
  • После щелчка на кнопке Finish откажитесь от предложения скорректировать объект
  • Вызовите через свойство
  • Выделите элемент ObjectDataSource1 и через панель Properties создайте обработчик ObjectDataSource1_Deleting() для события Deleting, в котором мы будем перехватывать номер удаляемой из таблицы записи
  • В результате таких действий по конфигурированию элемента ObjectDataSource1 оболочка добавит в его дескриптор нужный код (который, кстати, можно ввести и вручную без всяких визуальных помощников)

    <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
        TypeName="EmployeeDB" SelectMethod="GetAllEmployees" 
        UpdateMethod="UpdateEmployee" 
        DeleteMethod="DeleteEmployee"
        OnDeleting="ObjectDataSource1_Deleting">
        <UpdateParameters>
            <asp:Parameter Name="employeeID" Type="Int32" />
            <asp:Parameter Name="firstName" Type="String" />
            <asp:Parameter Name="lastName" Type="String" />
            <asp:Parameter Name="titleOfCourtesy" Type="String" />
        </UpdateParameters>
        <DeleteParameters>
            <asp:ControlParameter ControlID="GridView1" 
                Name="employeeID" Type="int32" />
        </DeleteParameters>
    </asp:ObjectDataSource>

    Теперь скорректируем управляющий столбец объекта GridView1, чтобы добавить в его пользовательский интерфейс кнопку для возможности удаления записи. Кнопку добавим в форме гиперссылки Link.

  • Вызовите встроенный визуальный редактор для объекта
  • Выделите управляющий столбец "Изменить" и в поле CommandField properties установите значения следующих свойств
  • ButtonType=Link
  • DeleteText="Удалить"
  • ShowDeleteButton="True"
  • Вспомним, что в реляционных базах данных должна поддерживаться целостность данных. Это значит, что если таблицы находятся в отношении "главный-подчиненный", то при удалении записи в главной таблице, имеющей первичный ключ, нужно позаботиться об удалении всех записей связанной таблицы с идентичными значениями вторичных ключей.

    ADO.NET отслеживает это положение автоматически, но ей нужно указать первичный ключ. Для этого служит параметр DataKeyNames в элементе управления GridView1 (поскольку мы планируем удалять запись через него), в котором указывается строка с одним именем ключевого поля или строка со списком ключевых полей, разделенных запятыми. В нашем случае первичным ключом в таблице Employees является поле EmployeeID.

  • Переведите редактор страницы в режим Source и добавьте в заголовок элемента GridView1 параметр, назначающий ключевое поле редактируемой таблицы
  • DataKeyNames="EmployeeID"
  • Выделите текстовую метку lblInfo и сбросьте свойство сохранения состояния EnableViewState="False", чтобы при каждом новом запросе ее значение очищалось
  • На этом этап декларативной настройки страницы закончен. Теперь нужно заполнить созданные нами ранее обработчики соответствующим кодом.

  • Заполните обработчики событий так, чтобы окончательный код страницы ObjectDataSourceFull.aspx стал следующим
    (рис ) Окончательный код страницы ObjectDataSourceFull.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void LinkButton1_Click(object sender, EventArgs e)
        {
            EmployeeDB db = new EmployeeDB();
            // Вставляем пустую запись и запоминаем ее номер
            Session["recordNumber"] = 
                db.InsertEmployee(new EmployeeDetails(0, "", "", ""));
            // Обновляем страницу без полного цикла,
            Server.Transfer(Request.FilePath);
        }
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Session["recordNumber"] != null)
            {
                lblInfo.Text = "Добавлена запись "
                    + Session["recordNumber"].ToString();
                Session.Remove("recordNumber");
            }
        }
        
        protected void ObjectDataSource1_Deleting(object sender, 
            ObjectDataSourceMethodEventArgs e)
        {
            lblInfo.Text = "Удалена запись " 
                + e.InputParameters["employeeID"].ToString();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                TypeName="EmployeeDB" SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" 
                DeleteMethod="DeleteEmployee"
                OnDeleting="ObjectDataSource1_Deleting">
                <UpdateParameters>
                    <asp:Parameter Name="employeeID" Type="Int32" />
                    <asp:Parameter Name="firstName" Type="String" />
                    <asp:Parameter Name="lastName" Type="String" />
                    <asp:Parameter Name="titleOfCourtesy" Type="String" />
                </UpdateParameters>
                <DeleteParameters>
                    <asp:ControlParameter ControlID="GridView1" 
                        Name="employeeID" Type="int32" />
                </DeleteParameters>
            </asp:ObjectDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" DataSourceID="ObjectDataSource1" 
                DataKeyNames="EmployeeID">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField 
                        CancelText="Отмена" EditText="Редакция" HeaderText="Изменить"
                        ShowEditButton="True" ShowHeader="True" UpdateText="Применить" 
                        DeleteText="Удалить" ShowDeleteButton="True">
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
            <asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">
                Вставка
            </asp:LinkButton>
            <br />
            <asp:Label ID="lblInfo" runat="server" EnableViewState="False" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceFull.aspx и насладитесь тем, что она имеет функциональность полноценного редактора таблиц, тем более - удаленных (Ах да Большой Билл, ах да мала-ц-ц-а!). Один из экранов, генерируемых на стороне пользователя, будет таким
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms. Редакция/Удалить
    2 Andrew Fuller Dr. Редакция/Удалить
    3 Janet Leverling Ms. Редакция/Удалить
    4 Margaret Peacock Mrs. Редакция/Удалить
    5 Steven Buchanan Mr. Редакция/Удалить
    6 Michael Suyama Mr. Редакция/Удалить
    7 Robert King Mr. Редакция/Удалить
    8 Laura Callahan Ms. Редакция/Удалить
    9 Владимир Снетков Доцент 007 Редакция/Удалить
    10 Редакция/Удалить

    Вставка

    Добавлена запись 10

    Программное изменение запроса пользователя в SqlDataSource

    Ранее мы рассматривали пример SqlDataSourceParameters.aspx выборки данных из таблицы в зависимости от выбранного пользователем города. Первый источник SqlDataSource заполнял городами список, жестко привязанный с помощью спискового свойства DataSourceID. Второй источник SqlDataSource отрабатывал один и тот же SQL-запрос с разными значениями параметра, полученными из списка в зависимости от выбора пользователя.

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

  • Сделайте копию страницы SqlDataSourceParameters.aspx с именем SqlDataSourceChange.aspx и назначьте ее стартовой
  • Выделите элемент DropDownList ID="lstCities" и очистите его свойство DataSourceID, тем самым мы отвяжем список от источника данных
  • Двойным щелчком на свободном месте Web-формы создайте обработчик загрузки страницы Page_Load()
  • Для второго источника SqlDataSource ID="sourceEmployees" через панель Properties создайте обработчик события Selecting
  • Заполните обработчики кодом так, чтобы окончательное описание страницы было следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)// Первый запрос
            {
                // Инициировать запрос и привязать результаты
                lstCities.DataSource = sourceEmployeeCities.Select(
                    DataSourceSelectArguments.Empty);
                lstCities.DataBind();
        
                // Добавить новый элемент в список
                lstCities.Items.Add("Все города");
                lstCities.SelectedIndex = lstCities.Items.Count - 1;
            }
        }
        
        void sourceEmployees_Selecting(object sender, 
                                       SqlDataSourceSelectingEventArgs e)
        {
            if ((string)e.Command.Parameters["@City"].Value == "Все города")
            {
                // Очистить коллекцию параметров
                e.Command.Parameters.Clear();
                    
                // Изменить команду
                e.Command.CommandText = "SELECT EmployeeID, FirstName, LastName, "
                    + "Title, City FROM Employees WHERE City!=''";
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT DISTINCT City FROM Employees" />
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True"
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, 
                    Title, City FROM Employees WHERE City=@City" 
                    OnSelecting="sourceEmployees_Selecting">
                <SelectParameters>
                    <asp:ControlParameter Name="City" ControlID="lstCities" 
                        PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>

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

    Статический результат выполнения страницы SqlDataSourceChange.aspx следующий

    Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    9 Владимир Снетков Доцент ВАК Красноярск
    Страницы:

    Программировать стараться - с конкурентами распрощаться

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

    Профессиональное Web-приложение всегда имеет дело с данными, которые могут храниться в базе данных, XML- или структурированном файле, или где-то еще. Правильное отображение этих данных на странице пользователя так же важно, как и их извлечение. ADO.NET имеет механизм привязки данных, который позволяет декларативно (на этапе проектирования) определить связь между элементами извлечения данных и элементами их отображения. При этом может не потребоваться ни строчки кода.

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

    Привязка одного значения

    Привязка одного значения позволяет связать свойство элемента управления с местом на странице. Это место называется вычислимым выражением привязки данных. Различают вычислимые # - выражения и более общие $ - выражения привязки данных. Выражение привязки данных может располагаться как внутри дескрипторов элементов управления, так и самостоятельно в любом месте страницы, даже вне дескриптора <form>...</form>.

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

  • значение свойства
  • значение, возвращаемое функцией
  • переменную-член класса
  • ссылку на свойство другого элемента управления
  • арифметическое выражение с применением литералов и переменных
  • и т.д.
  • Выражение привязки данных в ASP.NET помещается в ограничительные скобки <%#...%>PHP это будут скобки <?php...?> или <?=...?> ). Если выражение привязки данных используется внутри дескриптора элемента управления в качестве вычисляемого значения атрибута, то оно помещается в одинарные или двойные кавычки. Исполнение выражения привязки данных требует обязательного выполнения метода Page.DataBind() в сценарии страницы. Если мы забудем это сделать, то выражение привязки данных будет проигнорировано в HTML-выводе страницы.

    Пример корректных # - выражений привязки данных:

  • <%# Request.Browser.Browser %>
  • <%# GetUserName(ID) %>
  • <%# variable %>
  • <%# "Дорогой " + "друг " + name %>
  • <%# 2 * PI * radius + cos(alpha) %>
  • Пример интерфейсной части страницы, использующей несколько выражений привязки данных:

    <html>
    <body>
    <form method="post" runat="server">
    	<asp:Image ID="Image1" runat="server" ImageUrl='<%# FilePath %>' /><br />
    	<asp:Label ID="Label1" runat="server" Text="<%# FilePath %>" /><br />
    	<asp:TextBox ID="TextBox1" runat="server" Text='<%# GetFilePath() %>' /><br />
    	<asp:HyperLink ID="HyperLink1" runat="server"
    		NavigateUrl="<%# LogoPath.Value %>" Font-Bold="true" Text="Show logo" /><br />
    	<input type="hidden" ID="LogoPath" runat="server" value="MyMy.png">
    		<b><%# FilePath %></b><br />
    	<img src="<%# GetFilePath() %>">
    </form>
    </body>
    </html>

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

    Приведем простой пример.

  • Создайте новый сайт с именем WebSite9 командой File/New/Web Site (шаблон ASP.NET Web Site )
  • Скопируйте из проекта WebSite8 файл Web.Config, который должен иметь такой вид
    (рис ) Содержимое файла 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>
  • Добавьте к приложению новую страницу с совмещенным кодом и именем SingleValueBinding.aspx, назначьте страницу стартовой
  • Вызовите для корня Web-дерева контекстное меню и создайте каталог Pictures сайта командой New Folder
  • Скопируйте из каталога Pictures данной работы в каталог Pictures сайта два файла: MyPhoto.jpg и MyMy.png (или любые другие рисунки)
  • Наполните страницу SingleValueBinding.aspx следующим кодом
    (рис ) Код страницы SingleValueBinding.aspx <%@ Page Language="C#" %>
        
    <script runat="server">
        // Объявления переменных класса
        string cat = "Васька", photo;
        
        // Методы
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                Label1.Text = "<h1>Заполните поля и пошлите запрос</h1>";
                pict.Src = "Pictures/MyPhoto.jpg";
            }
            else
            {
                Label1.Text = "<h1>Вот Вам такие ответы</h1>";
                photo = "Pictures/MyMy.png";
                pict.Border = 3;
            }
        }
        
        public int AddFun(string a, string b)
        {
            try
            {
                return Convert.ToInt32(a) + Convert.ToInt32(b);
            }
            catch
            {
                Label1.Text = "<h1 style='color:red'>В поля вводите целые числа</h1>";
                Label1.Text += "<hr style='height:10; color:red' />";
                return 0;
            }
        }
        
        protected void Button1_Click(object sender, EventArgs e)
        {
            this.DataBind();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Выражения привязки одного значения</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Label ID="Label1" runat="server" Text="Label" />
                <h2>
                    Кто сказал Мяу...?</h2>
                <b style="color: Red">
                    <%# "Кот " + cat + "!" %>
                </b>
                <h2>
                    Сколько будет дважды-два?</h2>
                <b style="color: Red">
                    <%# 2 * 2 %>
                </b>
                <h2>
                    Введите два <em>целых</em> числа<br />
                    и получите их сумму</h2>
                <asp:TextBox ID="TextBox1" runat="server" />
                +
                <asp:TextBox ID="TextBox2" runat="server" />
                = <b style="color: Red">
                    <%# AddFun(TextBox1.Text, TextBox2.Text) %></b>
                    <h2>Кто здесь изображен?</h2>
                    <img id="pict" runat="server" src="<%# photo %>" />
                <br />
                <br />
                <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Выполнить" />
            </div>
        </form>
    </body>
    </html>
  • При первой загрузке страницы в текстовую метку выводится начальный заголовок <h1> и установленное по умолчанию значение свойства src дескриптора <img>. Все выражения привязки данных при этом игнорируются. При обратной отсылке, инициированной кнопкой, срабатывает метод DataBind() страницы. Он заставляет выполниться все выражения привязки данных, код получения которых размещен в блоке сценариев <script>.

  • Выполните страницу SingleValueBinding.aspx и убедитесь в работоспособности выражений привязки данных
  • Результат при начальной загрузке страницы:

    Результат после обратной отсылки страницы:

    Привязка множественного значения к списковым элементам управления

    Некоторые элементы управления библиотеки .NET Framework, служащие для отображения данных, позволяют привязать сразу несколько своих дочерних элементов к источнику данных. К ним относятся прежде всего списковые элементы управления:

  • Все списковые элементы управления, которые генерируют свой HTML-код с использованием дескриптора <select>, включая HtmlSelect, ListBox и DropDownList
  • Элементы управления CheckBoxList и RadioButtonList, генерирующие HTML-код своих дочерних элементов в виде флажков или переключателей
  • Элемент управления BulletedList, создающий нумерованный или маркированный список
  • Все они наследуют от System.Web.UI.WebControls.ListControl, который содержит свойства, необходимые для привязки множественного значения. Эти свойства приведены в таблице

    Свойства ListControl для привязки множественного значения
    Свойство Описание
    DataSource Подключает ссылку на объект с данными (для загрузки методом DataBind() ), имеющими
    DataSourceID Присоединяется к уже загруженному объекту данных. Используется как альтернатива свойству DataSource. Оба свойства сразу применять нельзя
    DataTextField Содержит наименование поля (столбца) таблицы, значения которого будут отображаться списком
    <select size="3" name="ListBox1" id="ListBox1">
        <option value="1">Text1</option>
        <option value="2">Text2</option>
    </select>
    DataValueField Содержит наименование поля (столбца) таблицы, значения которого будут загружены в список как атрибуты value, но пользователь их не увидит
    <select size="3" name="ListBox1" id="ListBox1">
        <option value="1">Text1</option>
        <option value="2">Text2</option>
    </select>

    В свою очередь, класс ListControl наследует метод DataBind() от класса System.Web.UI.WebControls.BaseDataBoundControl, иницирующий привязку.

    Для примера привязки множественного значения спроектируем страницу, в которой в качестве объекта, имитирующего источник данных, используем класс Hashtable (hash - мусор). Он реализует интерфейс System.Collections.Generic.IDictionary, имеющий шаблонный метод IDictionary<TKey, TValue>.Add(TKey, TValue).

  • Добавьте к приложению страницу с неразделяемым кодом и именем RepeatedValueBinding.aspx
  • Заполните интерфейсную часть страницы следующим кодом
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <select runat="server" id="Select1" 
                datatextfield="Key" datavaluefield="Value" size="3" />
            <select runat="server" id="Select2" 
                datatextfield="Key" datavaluefield="Value" />
            <asp:ListBox runat="server" ID="ListBox1" 
                DataTextField="Key" DataValueField="Value" Rows="3" />
            <asp:DropDownList runat="server" ID="DropDownList1" 
                DataTextField="Key" DataValueField="Value" />
            <asp:RadioButtonList runat="server" ID="RadioButtonList1" 
                DataTextField="Key" DataValueField="Value" />
            <asp:CheckBoxList runat="server" ID="CheckBoxList1"
                DataTextField="Key" DataValueField="Value" />
            <br />
            <asp:Button runat="server" ID="GetSelection" Text="Получить выделенное" />
            <br />
            <br />
            <asp:Literal runat="server" ID="Result" EnableViewState="false" />
        </div>
        </form>
    </body>
    </html>

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

  • Переведите редактор страницы в режим Design и двойным щелчком мыши на свободном месте создайте обработчик Page_Load(), который заполните так
  • protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            // Создать источник данных (коллекцию) с тремя элементами
            Hashtable ht = new Hashtable(3);
            
            // Добавить в коллекцию элементы по схеме: Add(Key, Value)
            ht.Add("Иванов", "Value1");
            ht.Add("Петров", "Value2");
            ht.Add("Сидоров", "Value3");
            
            // Связать элементы управления с источником данных
            Select1.DataSource = ht;
            Select2.DataSource = ht;
            ListBox1.DataSource = ht;
            DropDownList1.DataSource = ht;
            RadioButtonList1.DataSource = ht;
            CheckBoxList1.DataSource = ht;
            
            // Загрузить данные в элементы управления
            this.DataBind();
        }
    }

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

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

  • Создайте обработчик для кнопки Button и заполните его следующим кодом
  • protected void GetSelection_Click(object sender, EventArgs e)
        {
            try
            {
                Result.Text += "<li>В Select1 выбрано: "
                            + "(\""
                            + Select1.Items[Select1.SelectedIndex].Text // Key
                            + "\", \""
                            + Select1.Value + "\")";                    // Value
                Result.Text += "<li>В Select2 выбрано: "
                            + "(\""
                            + Select2.Items[Select2.SelectedIndex].Text // Key
                            + "\", \""
                            + Select2.Value + "\")";                    // Value
                Result.Text += "<li>В ListBox1 выбрано: "
                            + "(\""
                            + ListBox1.SelectedItem.Text                // Key
                            + "\", \""
                            + ListBox1.SelectedItem.Value + "\")";      // Value
                Result.Text += "<li>В DropDownList1 выбрано: "
                            + "(\""
                            + DropDownList1.SelectedItem.Text           // Key
                            + "\", \""
                            + DropDownList1.SelectedItem.Value + "\")"; // Value
                Result.Text += "<li>В RadioButtonList1 выбрано: "
                            + "(\""
                            + RadioButtonList1.SelectedItem.Text          // Key
                            + "\", \""
                            + RadioButtonList1.SelectedItem.Value + "\")";// Value
        
                // Проверяем флажки
                int checkedCount = 0;
                string checkString = "";
                foreach (ListItem check in CheckBoxList1.Items)
                {
                    if (check.Selected)
                    {
                        checkString += "(\""
                                    + check.Text                        // Key
                                    + "\", \""
                                    + check.Value + "\"); ";            // Value
                        checkedCount++;  // Считем выделенные флажки
                    }
                }
                if (checkedCount == 0)
                    Result.Text += "<li>В CheckBoxList1 ничего не выбрано!";
                else
                    Result.Text += "<li>В CheckBoxList1 выбрано: " + checkString;
            }
            catch
            {
                Result.Text = "<h1 style='color:Red'>Выберите в каждом элементе</h1>";
            }
        }

    Разница в коде доступа к свойствам списков между серверными элементами управления и статическими объясняется подгонкой фирмой Microsoft HTML-классов под ранее разработанные дескрипторы <select>.

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

    Предыдущий пример использует в качестве источника данных класс Hashtable, потому что он реализует интерфейс

    System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.

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

  • System.Collections.ArrayList
  • Microsoft.VisualBasic.Collection
  • System.Collections.Hashtable
  • System.Collections.Generic.Dictionary<TKey, TValue>
  • System.Data.DataView
  • Учитывая, что DataReader представляет данные также в виде таблицы с именованными столбцами, мы можем так сформулировать SQL-запрос к базе данных, что в DataReader будут загружены два нужных нам столбца: один из которых можно считать Key, другой - Value. В элементе управления свойству DataTextField нужно присвоить имя поля, отображаемого в списке, а свойству DataValueField - имя поля, неотображаемого в списке.

    Приведем пример. Пусть мы хотим заполнить окно списка ListBox полными именами всех сотрудников, хранящихся в таблице Employees учебной базы данных Northwind. Чтобы получить два столбца DataReader, один из которых будет вычисляемый, нужно сформулировать SQL-запрос таким образом

    SELECT EmployeeID, TitleOfCourtesy + ' ' + FirstName + ' ' + LastName AS FullName FROM Employees
  • Добавьте к проекту новую страницу DataReaderListBinding.aspx с разделяемым кодом и назначьте ее стартовой
  • Оформите и настройте интерфейсную часть страницы DataReaderListBinding.aspx так, чтобы она имела следующее дескрипторное представление
    (рис ) Дескрипторное представление интерфейсной части страницы DataReaderListBinding.aspx<%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="DataReaderListBinding.aspx.cs" 
        Inherits="DataReaderListBinding" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ListBox ID="ListBox1" runat="server" DataTextField="FullName" 
                DataValueField="EmployeeID" SelectionMode="Multiple" Rows="10" />
            <br />
            <br />
            <asp:Button ID="GetSelection" runat="server" Text="Получить выделенное" 
                OnClick="GetSelection_Click" />
            <br />
            <br />
            <asp:Literal ID="Result" runat="server" EnableViewState="False" />
        </div>
        </form>
    </body>
    </html>
  • Заполните файл поддержки DataReaderListBinding.aspx.cs следующим кодом
    (рис ) Код файла поддержки DataReaderListBinding.aspx.csusing System;
    using System.Data;
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Web.UI.WebControls;
        
    public partial class DataReaderListBinding : System.Web.UI.Page
    {
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                // Создать объекты Command и Connection
                string connectionString = WebConfigurationManager.
                    ConnectionStrings["Northwind"].ConnectionString;
                string sql = "SELECT EmployeeID, "
                            + "TitleOfCourtesy + ' ' + FirstName + ' ' + LastName "
                            + "AS FullName FROM Employees";
                SqlConnection con = new SqlConnection(connectionString);
                SqlCommand cmd = new SqlCommand(sql, con);
        
                try
                {
                    // Открыть соединение и получить DataReader
                    con.Open();
                    SqlDataReader reader = cmd.ExecuteReader();
                    // Привязать DataReader к списку и заполнить список
                    ListBox1.DataSource = reader;
                    ListBox1.DataBind();
                    reader.Close();
                }
                finally
                {
                    // Закрыть соединение
                    con.Close();
                }
            }
        }
        
        protected void GetSelection_Click(object sender, EventArgs e)
        {
            Result.Text = "<b>Выделены сотрудники:</b>";
            Result.Text += "<ol>";
            foreach (ListItem item in ListBox1.Items)
                if (item.Selected)
                    Result.Text += 
                        String.Format("<li> (\"{0}\", \"{1}\")</li>", item.Text, item.Value);
            Result.Text += "</ol>";
        }
    }
  • Здесь мы заполнили список только при первом запросе страницы, расчитывая, что далее заполненный список будет сохраняться в состоянии вида. Если в элементе ListBox1 отключить сохранение состояния вида, установив EnableViewState="False", то при обратной отсылке список окажется пустым (попробуйте!).

  • Исполните страницу DataReaderListBinding.aspx для получения следующего результата
  • Выделены сотрудники:

  • ("Dr. Andrew Fuller", "2")
  • ("Mr. Steven Buchanan", "5")
  • ("Ms. Anne Dodsworth", "9")
  • Если для визуализации данных мы хотим использовать DropDownList, CheckListBox или RadioButtonList вместо ListBox, то достаточно изменить объявление элемента управления в интерфейсной части страницы. Остальная часть кода останется прежней.

    Элемент управления GridView с множественной привязкой данных

    Кроме списковых элементов управления, которые можно использовать для отображения множественных данных, существуют более специализированные элементы управления, спроектированные специально для этих целей. К ним относятся GridView, DetailsView и FormView. Кроме того, механизм привязки имеют и такие элементы, как Menu, TreeView и AdRotator, хотя и служат несколько для других целей.

    Здесь мы поговорим об элементе управления GridView, возможных способах его настройки и привязки к нему разных источников данных. Как и списковые элементы управления, GridView имеет свойство DataSource и метод DataBind(). Типовое объявление элемента GridView в интерфейсной части страницы выглядит так:

    <asp:GridView ID="GridView1" runat="server" />

    Рассмотрим пример, в котором данные DataReader будут привязываться не к списковому элементу управления, а к GridView.

  • Создайте новую страницу GridViewDataBind.aspx с разделяемым кодом и назначьте ее стартовой
  • Поместите на страницу из вкладки Data панели Toolbox элемент управления GridView
  • Откройте на редактирование файл GridViewDataBind.aspx.cs и заполните его следующим кодом
    (рис ) Код файла GridViewDataBind.aspx.csusing System;
    using System.Data;
    using System.Web.Configuration;
    using System.Data.SqlClient;
    using System.Web.UI.WebControls;
        
    public partial class DataReaderListBinding : System.Web.UI.Page
    {
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать объекты Command и Connection
            string connectionString = WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
            string sql = "SELECT EmployeeID, FirstName, LastName, "
                        + "Title, City FROM Employees";
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand(sql, con);
        
            try
            {
                // Открыть соединение и получить DataReader
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader();
                // Привязать DataReader к GridView
                GridView1.DataSource = reader;
                GridView1.DataBind();
                reader.Close();
            }
            finally
            {
                // Закрыть соединение
                con.Close();
            }
        }
    }
  • Исполните страницу GridViewDataBind.aspx для получения следующего результат
  • EmployeeID FirstName LastName Title City
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Anne Dodsworth Sales Representative London

    Элементы управления источниками данных

    В библиотеке .NET Framework имеется ряд классов, которые соединяются с любым поставщиком данных ADO.NET, облегчая привязку данных к элементам отображения. Они называются источниками данных и реализуют интерфейс IDataSource.

    К источникам данных относятся следующие классы:

  • System.Web.UI.WebControls.ObjectDataSource - позволяет подключиться к пользовательскому классу доступа к данным
  • System.Web.UI.WebControls.SqlDataSource - позволяет подключиться к любому поставщику ADO.NET
  • System.Web.UI.WebControls.SiteMapDataSource - позволяет подключиться к файлу System.Web.SiteMap, описывающему навигационную структуру Web-сайта
  • System.Web.UI.WebControls.XmlDataSource - позволяет подключиться к XML-файлу
  • Все эти классы представлены как элементы управления на вкладке Data панели Toolbox оболочки Visual Studio 2005 и являются новшеством библиотеки .NET Framework 2.0. Если перенести этот элемент на страницу, то он отображается серым цветом как невизуальный компонент страницы. В то же время в разметочной части страницы появляется соответствующий дескриптор элемента управления.

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

  • System.Data.Common
  • System.Data.SqlClient
  • System.Data.OracleClient
  • System.Data.OleDb
  • System.Data.Odbc
  • Элемент управления SqlDataSource

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

  • строку соединения ConnectionString
  • имя фабрики поставщиков ProviderName
  • тип SQL-команды (встроенная SQL-команда Text или хранимая процедура StoredProcedure ) в свойствах SelectCommandType, InsertCommandType, UpdateCommandType и DeleteCommandType
  • саму SQL-команду или имя хранимой процедуры в свойствах SelectQuery, InsertQuery, UpdateQuery и DeleteQuery (Такие имена имеют свойства в панели Properties, которые в дескрипторном представлении источника данных трансформируются в имена атрибутов SelectCommand, InsertCommand, UpdateCommand и DeleteCommand )
  • Нам нужно определять команды только для тех типов действий, которые мы намерены выполнить. Так, например, если мы используем источник данных только для чтения набора записей, то нам достаточно определить только свойство SelectQuery.

    Строку соединения можно жестко указать в свойствах источника данных, но лучше читать из файла web.config, используя $ - выражение привязки данных.

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

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
        "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />

    Источник данных можно сконфигурировать и с помощью мастера, вызвав его командой Configure Data Source встроенного меню

    Создав и настроив источник данных, мы получаем возможность привязать к нему любой элемент отображения данных: HtmlSelect, ListBox, DropDownList, CheckBoxList, RadioButtonList, BulletedList уже на этапе проектирования, не написав ни одной строки кода в обработчике Page_Load(). Более того, если выполнить команду Refresh Scheme встроенной панели источника данных, то он одноразово соединится с базой данных и заполнится требуемой информацией, которую потом можно выбирать из раскрывающихся списков при настройке привязываемых к нему элементов отображения данных. Если мы используем источник данных, то создавать соединение, объекты команд и вызывать метод Page.DataBind() в коде страницы для заполнения элементов отображения, не нужно - все будет выполняться автоматически при запуске страницы.

  • Создайте страницу с объединенным кодом и именем SqlDataSourceSimple.aspx. Назначьте ее стартовой
  • Из вкладки Data панели Toolbox добавьте на страницу неотображаемый элемент управления SqlDataSource, через который мы привяжем данные к элементу отображения
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления ListBox
  • Из вкладки Data панели Toolbox добавьте на страницу элемент управления GridView
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления Button
  • Настройте объекты страницы: источник данных SqlDataSource1, элемент отображения данных ListBox1, многофункциональный элемент отображения данных GridView1, кнопку обратной отсылки Button1 следующим образом
    (рис ) Разметочная часть страницы SqlDataSourceSimple.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" /></div>
        </form>
    </body>
    </html>
  • Исполните страницу SqlDataSourceSimple.aspx, чтобы получить следующий результат
  • EmployeeID FirstName LastName Title City
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Anne Dodsworth Sales Representative London

    Все работает автоматически без необходимости написания кода (который нам тоже не страшен). Привязка данных при каждой обратной отсылке повторяется заново, и в этом есть некоторый недостаток, поскольку каждый раз должно создаваться соединение с базой данных и производиться выборка данных. К счастью, разработчики ASP.NET 2.0 устранили этот недостаток за счет автоматического кэширования данных источником SqlDataSource на случай, если данные изменяются редко.

    Если изменить свойство DataSourceMode источника данных с DataSet (установлено по умолчанию) на DataReader и исполнить страницу, то в данном случае получим тот же самый результат. Режим DataSourceMode=DataSet работы источника данных почти всегда лучше, чем режим DataSourceMode=DataReader, поскольку он поддерживает расширенную сортировку, фильтрацию и настройку кэширования, имеющиеся в DataSet. Однако, если стоит задача отображать большие экранные таблицы, то лучше использовать режим DataSourceMode=DataReader, потому что он более эффективно использует память сервера.

    Передача параметра источнику данных через элемент управления

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

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

  • Создайте страницу с объединенным кодом и именем SqlDataSourceParameters.aspx. Назначьте ее стартовой
  • Удалите контейнер сценария <script runat="server">...</script> как ненужный для данного примера
  • Из вкладки Data панели Toolbox добавьте на страницу первый источник данных SqlDataSource, который будет поставлять список всех городов из таблицы Employees. Назовите его sourceEmployeeCities
  • Из вкладки Standard панели Toolbox добавьте на страницу элемент управления DropDownList, который будет отображать все города, выбранные источником sourceEmployeeCities из таблицы Employees. Назовите его lstCities
  • Из вкладки Data панели Toolbox добавьте на страницу второй источник данных, который будет выбирать всех сотрудников из таблицы Employees, проживающих в переданном ему из раскрывающегося списка lstCities через параметр городе. Назовите его sourceEmployees
  • Из вкладки Data панели Toolbox добавьте на страницу элемент управления GridView с именем GridView1, который будет отображать список сотрудников, проживающих в выбранном городе и полученных из источника sourceEmployees
  • Настройте объекты страницы так, как показано ниже.
  • При добавлении параметров пользуйтесь удобным редактором оболочки, позволяющим создавать команды и определять параметры. Редактор вызывается при выделенном элементе второго источника sourceEmployees щелчком на кнопке ( ...) свойства SelectQuery в панели Properties

    Обратите внимание, что обратную отсылку мы выполняем за счет включенного свойства AutoPostBack раскрывающегося списка lstCities. Обратите также внимание, что правильный перенос строковой константы в свойстве SelectCommand второго источника не влияет на возвращаемые SQL-командой результаты.

    <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT DISTINCT City FROM Employees" />
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True" DataSourceID="sourceEmployeeCities" 
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, 
                    Title, City FROM Employees WHERE City=@City">
                <SelectParameters>
                    <asp:ControlParameter Name="City" ControlID="lstCities" 
                        PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу SqlDataSourceParameters.aspx для получения следующего результата
  • Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    9 Anne Dodsworth Sales Representative London

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

    Вернемся к редактору команд и параметров, представленному ранее окном

    После набора команды в области SELECT command щелчок на кнопке Refresh Parameters сканирует введенную команду и заполняет другие области и раскрывающиеся списки редактора нужной информацией, из которой можно потом быстро выбрать требуемое значение. В списке Parameter source перечислены типы элементов, которые могут передавать значения параметров в источник данных:

  • Cookie
  • Control
  • Form
  • Profile
  • QueryString
  • Session
  • В нашем случае используется элемент управления DropDownList, поэтому мы установили тип параметра Control.

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

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

  • Создайте новую страницу с объединенным кодом и именем QueryParameter1.aspx. Назначьте ее стартовой
  • Поместите на страницу источник данных SqlDataSource, списочный элемент ListBox и кнопку Button
  • Настройте свойства элементов так, как показано ниже, включая обработчики, полученные двойным щелчком на свободном месте страницы ( Page_Load ) и на кнопке ( cmdGo_Click )
    (рис ) Разметочная часть страницы QueryParameter1.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                lstCities.SelectedIndex = 0;
        }
        
        protected void cmdGo_Click(object sender, EventArgs e)
        {
            Response.Redirect("QueryParameter2.aspx?selectCity="
                + lstCities.SelectedValue);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница выбора города</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server"
                ProviderName="System.Data.SqlClient"
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                SelectCommand="SELECT DISTINCT City FROM Employees">
            </asp:SqlDataSource>
            
            <h2>
                Выберите город</h2>
            <asp:ListBox ID="lstCities" runat="server"
                DataSourceID="sourceEmployeeCities"
                DataTextField="City">
            </asp:ListBox>
            <br />
            <br />
            <asp:Button ID="cmdGo" runat="server" Text="Применить" 
                OnClick="cmdGo_Click" />
        </div>
        </form>
    </body>
    </html>
  • Создайте новую страницу с объединенным кодом и именем QueryParameter2.aspx
  • Поместите на страницу источник данных SqlDataSource и элемент GridView
  • Настройте свойства элементов так, как показано ниже
    (рис ) Разметочная часть страницы QueryParameter2.aspx<%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница проживающих сотрудников</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, 
                    City FROM Employees WHERE City=@City">
                <SelectParameters>
                    <asp:QueryStringParameter Name="City" QueryStringField="selectCity" />
                </SelectParameters>
            </asp:SqlDataSource>
            
            <h2>
                Проживающие сотрудники</h2>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployees" >
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу QueryParameter1.aspx и убедитесь, что запрос, сформированный на ней и направленный странице QueryParameter2.aspx на обработку, дает тот же самый результат, что и в предыдущем примере
  • Передача параметра источнику данных в хранимых процедурах

    Мы уже говорили о достоинствах хранимых процедур, которые представляют собой именованные SQL-команды, хранимые прямо в базе данных. Они более надежны, поскольку заранее проверены и отлажены. Они более безопасны, поскольку недоступны изменениям со стороны кода страницы, а значит недоступны злоумышленнику. Они упрощают часть нашей .aspx -страницы за счет переноса действительного SQl-запроса в базу данных, который может достигать на странице внушительных размеров. Хранимые процедуры позволяют передавать параметры, в том числе в источники данных.

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

  • Скопируйте командой Add Existing Item из приложения WebSite8 разработанную нами ранее страницу SaveStoredProcedure.aspx и переименуйте ее в
  • Откройте на редактирование страницу SaveStoredProcedure1.aspx и откорректируйте ее так
    (рис ) Код страницы SaveStoredProcedure1.aspx для нового примера<%@ Page Language="C#" %>
        
    <script runat="server">
        
        // Объявления хранимых процедур
        // Выборка городов
        string sql1 =
            "CREATE PROCEDURE GetCityes "
          + "AS "
          + "SELECT DISTINCT City FROM Employees";
        
        // Выборка сотрудников для выбранного города
        string sql2 =
            "CREATE PROCEDURE GetEmployeesByCity "
          + "@paramCity		varchar(15) "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, Title, City "
          + "FROM Employees WHERE City=@paramCity";
        
        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 GetCityes";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROCEDURE GetCityes\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = "DROP PROCEDURE GetEmployeesByCity";
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"DROP PROC GetEmployeesByCity\"<br />";
            }
            catch { }
        
            // Выполняем команды в безопасном режиме
            // Добавляем в базу данных хранимые процедуры
            try
            {
                cmd.CommandText = sql1;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetCityes\"<br />";
            }
            catch { }
            try
            {
                cmd.CommandText = sql2;
                cmd.ExecuteNonQuery();
                lblInfo.Text += "Прошли \"CREATE PROCEDURE GetEmployeesByCity\"<br />";
            }
            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>
  • Исполните страницу SaveStoredProcedure1.aspx, чтобы добавить в базу данных Northwind две хранимых процедуры, одна из которых содержит параметр
  • Теперь осталось воспользоваться этими хранимыми процедурами в источниках данных. Не мудрствуя лукаво, мы просто заменим в двух предыдущих примерах строку SQL-запроса источников на имя хранимой процедуры, а также изменим тип команды.

  • Скопируйте страницу SqlDataSourceParameters.aspx и присвойте ей имя StoredProcedureParam.aspx. Назначьте страницу StoredProcedureParam.aspx стартовой
  • Внесите в страницу StoredProcedureParam.aspx небольшие изменения, чтобы она стала такой
    (рис ) Код страницы StoredProcedureParam.aspx<%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetCityes" 
                SelectCommandType="StoredProcedure">
            </asp:SqlDataSource>
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True" DataSourceID="sourceEmployeeCities" 
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetEmployeesByCity" 
                SelectCommandType="StoredProcedure">
                <SelectParameters>
                    <asp:ControlParameter ControlID="lstCities" 
                        Name="paramCity" PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу StoredProcedureParam.aspx и убедитесь, что она выдает точно такие же результаты, как и страница SqlDataSourceParameters.aspx
  • Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    9 Anne Dodsworth Sales Representative London

    Теперь попробуем проверить работу хранимых процедур при передаче параметра через строку запроса из одной страницы в другую.

  • Выделите в панели Solution Explorer сразу две страницы QueryParameter1.aspx и QueryParameter2.aspx и создайте их копии с именами QueryStoredProcParam1.aspx и QueryStoredProcParam2.aspx. Назначьте страницу QueryStoredProcParam1.aspx стартовой
  • Заполните обе новых страницы следующими поправками
    (рис ) Код страницы QueryStoredProcParam1.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                lstCities.SelectedIndex = 0;
        }
        
        protected void cmdGo_Click(object sender, EventArgs e)
        {
            Response.Redirect("QueryStoredProcParam2.aspx?selectCity="
                + lstCities.SelectedValue);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница выбора города</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetCityes" 
                SelectCommandType="StoredProcedure">
            </asp:SqlDataSource>
            
            <h2>
                Выберите город</h2>
            <asp:ListBox ID="lstCities" runat="server"
                DataSourceID="sourceEmployeeCities"
                DataTextField="City">
            </asp:ListBox>
            <br />
            <br />
            <asp:Button ID="cmdGo" runat="server" Text="Применить" 
                OnClick="cmdGo_Click" />
        </div>
        </form>
    </body>
    </html>
    <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница проживающих сотрудников</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ ConnectionStrings:Northwind %>"
                ProviderName="System.Data.SqlClient" 
                SelectCommand="GetEmployeesByCity" 
                SelectCommandType="StoredProcedure">
                <SelectParameters>
                    <asp:QueryStringParameter Name="paramCity" QueryStringField="selectCity" />
                </SelectParameters>
            </asp:SqlDataSource>
            
            <h2>
                Проживающие сотрудники</h2>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="sourceEmployees" >
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу QueryStoredProcParam1.aspx и убедитесь, что получился тот же результат, что и в соответствующем примере без использования хранимых процедур, но код вышел более удобным (все переходим на хранимые процедуры!)
  • Обработка исключений источников данных

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

    Сбой в работе источника данных SqlDataSource сопровождается генерацией события, которое происходит сразу после возникновения ошибки. Сбой при выполнении запроса выбрасывает событие Selected, а при обновлении, вставке или удалении записей - события Updated, Inserted или Deleted соответственно. Мы можем предусмотреть обработку каждого из этих событий в отдельности или запланировать общий обработчик на любой из этих сбоев.

    В обработчике ошибок можно получить доступ к объекту сгенерированного исключения через его свойство SqlDataSourceStatusEventArgs.Exception. Если после выброса исключения мы хотим подавить его далнейшее распространение, то в обработчике должны установить SqlDataSourceStatusEventArgs.ExceptionHandled в true.

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

  • Создайте копию страницы SqlDataSourceSimple.aspx с именем SqlDataSourceException.aspx и назначьте ее стартовой
  • Откройте страницу SqlDataSourceException.aspx на редактирование в режиме Design и двойным щелчком на свободном месте Web-формы создайте обработчик Page_Load()
  • В обработчике
  • Следуйте указаниям подсказчика кода и нажимайте клавишу
  • Указанным способом зарегистрируйте события (и создайте для каждого из них обработчики)
  • Selected
  • Updated
  • Inserted
  • Deleted
  • После проведенных действий страница SqlDataSourceException.aspx должна выглядеть так

    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Selected);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Updated);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Inserted);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Deleted);
        }
        
        void SqlDataSource1_Deleted(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Inserted(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Updated(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        
        void SqlDataSource1_Selected(object sender, SqlDataSourceStatusEventArgs e)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" /></div>
        </form>
    </body>
    </html>
  • Оставьте только один обработчик, а остальные удалите
  • Измените имя оставшегося общего обработчика на SqlDataSource1_Error и внесите это имя в регистрацию каждого события, чтобы блок кода сценария стал таким
  • <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
        }
        
        void SqlDataSource1_Error(object sender, SqlDataSourceStatusEventArgs e)
        {
        }
    </script>

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

  • Поместите в конец страницы текстовую метку Label с именем lblError
  • Заполните обработчик ошибки источника данных предупреждающим кодом, чтобы окончательно страница стала следующей
    (рис ) Окончательный код страницы SqlDataSourceException.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            SqlDataSource1.Selected += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Updated += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Inserted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
            SqlDataSource1.Deleted += new SqlDataSourceStatusEventHandler(SqlDataSource1_Error);
        }
        
        void SqlDataSource1_Error(object sender, SqlDataSourceStatusEventArgs e)
        {
            if (e.Exception != null)
            {
                // Известить пользователя, что команда не была завершена
                lblError.Text = "<h1 style='color:#FF0000'>"
                    + "При исполнении запроса произошло исключение!</h1>";
        
                // Известить систему, что ошибка обработана
                e.ExceptionHandled = true;            
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
            <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1"
                DataTextField="FirstName" />
            <br />
            <br />
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
            <br />
            <asp:Button ID="Button1" runat="server" Text="Обновить" />
            <asp:Label ID="lblError" runat="server" />
        </div>
        </form>
    </body>
    </html>
  • Чтобы испытать обработчик исключения, внесите какую-нибудь ошибку в строку запроса (измените имя поля или исказите SQL-оператор) и запустите страницу. Результат будет таким
  • Редактирование записей в источнике данных

    Чтобы дать возможность пользователю редактировать записи в механизме множественной привязки данных, не обязательно писать дополнительный код. Можно настроить источник данных и элемент отображения так, чтобы редактирование данных происходило автоматически. Списковые элементы отображения не имеют возможностей редактирования, а более сложные элементы, такие как GridView, DetailsView и FormView имеют такие средства, которые можно в них включить.

    Настройка возможностей редактирования состоит из двух шагов:

  • Определения SQL-операций в соответствующих свойствах UpdateQuery, InsertQuery и DeleteQuery
  • Настройки элемента отображения
  • Какие операции с данными мы хотим разрешить пользователю, такие свойства в источнике данных и нужно определять, а остальные можно пропускать.

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

  • Скопируйте страницу SqlDataSourceSimple.aspx с именем SqlDataSourceUpdates.aspx и назначьте ее стартовой
  • Удалите из нее объекты списка ListBox1 и кнопки Button1, чтобы остались только источник данных SqlDataSource1 и элемент отображения GridView1
  • Исполните страницу, чтобы убедиться, что данные, извлеченные источником из таблицы Employees, по-прежнему отображаются в GridView1
  • Теперь настроим источник на обновление данных.

  • Выделите в режиме Design источник данных SqlDataSource1 и в панели Properties щелкните на кнопке с многоточием в поле UpdateQuery, чтобы запустить редактор формирования SQL-команды обновления записей с параметрами
  • Заполните поле UPDATE command редактора строкой SQL-команды с параметрами
    UPDATE Employees SET FirstName=@FirstName, 
           LastName=@LastName, Title=@Title, City=@City 
           FROM Employees WHERE EmployeeID=@EmployeeID
  • После щелчка на кнопке Refresh Parameters автоматически заполнится поле Parameters, как показано на рисунке

    После таких действий редактор параметров сгенерирует дополнительные поправки в настройку источника данных, дескрипторное описание которого станет таким

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
            "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
        UpdateCommand="UPDATE Employees SET FirstName=@FirstName, 
            LastName=@LastName, Title=@Title, City=@City 
            FROM Employees WHERE EmployeeID=@EmployeeID" >
        <UpdateParameters>
            <asp:Parameter Name="FirstName" />
            <asp:Parameter Name="LastName" />
            <asp:Parameter Name="Title" />
            <asp:Parameter Name="City" />
            <asp:Parameter Name="EmployeeID" />
        </UpdateParameters>
    </asp:SqlDataSource>

    Обратите внимание, что имена параметров, предваряемых символом @, мы выбрали совпадающими с именами полей таблицы. Это необходимо потому, что объект GridView1 после привязки к нему источника данных будеть иметь коллекцию столбцов с именами полей, которые и будут выступать в качестве параметров для обмена при обновлении записей. Такое соглашение именования параметров должно выполняться для всех элементов управления данными ASP.NET множественной привязки.

  • Исполните страницу SqlDataSourceUpdates.aspx с внесенными на данном этапе настройками источника данных, чтобы убедиться, что все работает по старому и никаких дополнительных возможностей редактирования пока не появилось
  • Теперь осталось настроить интерфейсный объект GridView1 на возможность редактирования данных. Для этого добавим к GridView1 дополнительный столбец с функциями управления редактированием записей.

  • Вызовите внутреннюю панель настройки объекта GridView1, как показано на рисунке, и выберите пункт Edit Columns
  • Появится диалоговое окно Fields, которое предоставляет в удобном виде настройки столбцов объекта GridView1

  • Используя диалоговое окно Fields выполните следующее:
  • Сбросьте флажок Auto-generate fields, чтобы отключить неконтролируемое отображение всех столбцов
  • Добавьте кнопкой Add в поле Selected fields пять столбцов типа BoundField, которые будут связаны с источником данных
  • Выделяя каждый из пяти добавленных столбцов, определите их свойства DataField (имя связываемого поля в источнике данных) и HeaderText (отображаемый на экране заголовок столбца)
  • Добавьте шестой столбец типа CommandField/Edit, Update, Cancel и задайте ему следующие свойства:
  • EditText="Редакция"
  • CancelText="Отмена"
  • UpdateText="Применить
  • ButtonType="Button"
  • HeaderText="Изменить"
  • HeaderStyle/BackColor="Red"
  • HeaderStyle/ForeColor="Yellow"
  • В результате таких опосредованных действий оболочка сформирует следующее описание объекта GridView1

    <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                AutoGenerateColumns="False" >
        <Columns>
            <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
            <asp:BoundField DataField="FirstName" HeaderText="Имя" />
            <asp:BoundField DataField="LastName" HeaderText="Отчество" />
            <asp:BoundField DataField="Title" HeaderText="Статус" />
            <asp:BoundField DataField="City" HeaderText="Проживает" />
            <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                ShowEditButton="True" ShowHeader="True"
                EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                <HeaderStyle BackColor="Red" ForeColor="Yellow" />
            </asp:CommandField>
        </Columns>
    </asp:GridView>

    В итоге окончательный код страницы будет следующим

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                    "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
                UpdateCommand="UPDATE Employees SET FirstName=@FirstName, 
                    LastName=@LastName, Title=@Title, City=@City 
                    FROM Employees WHERE EmployeeID=@EmployeeID" >
                <UpdateParameters>
                    <asp:Parameter Name="FirstName" />
                    <asp:Parameter Name="LastName" />
                    <asp:Parameter Name="Title" />
                    <asp:Parameter Name="City" />
                    <asp:Parameter Name="EmployeeID" />
                </UpdateParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                        AutoGenerateColumns="False" >
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="Title" HeaderText="Статус" />
                    <asp:BoundField DataField="City" HeaderText="Проживает" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу и испытайте ее функциональность. Можно спокойно редактировать записи, не написав при этом ни строчки кода - все делает встроенный механизм ASP.NET
  • Страница SqlDataSourceUpdates.aspx в действии может выглядеть так

    № п/п Имя Фамилия Статус Проживает Изменить
    1 Nancy Davolio Sales Representative Redmond
    2 Andrew Fuller Vice President, Sales Tacoma
    3 Janet Leverling Sales Representative Kirkland
    4 Margaret Peacock Sales Representative Redmond
    5 Steven Buchanan Sales Manager London
    6 Michael Suyama Sales Representative London
    7 Robert King Sales Representative London
    8 Laura Callahan Inside Sales Coordinator Redmond
    9 Владимир Снетков Доцент 007 Красноярск

    В добавленном столбце управления редактированием (его можно разместить на любом месте, изменив порядок следования дескрипторов) щелчок на кнопке "Применить" инициирует обратную отсылку, которая при поступлении на сервер передает источнику значения коллекции параметров, используя имена полей, и затем вызывает метод SqlDataSource.Update() для внесения изменений в базу данных.

    Можно создать аналогичные параметризованные команды в источнике данных для свойств InsertQuery и DeleteQuery. Чтобы разрешить, например, вставку в элементе отображения GridView, нужно добавить управляющий столбец типа CommandField и установить его отображение в свойстве ShowInsertButton

    Обновление данных при параллельном доступе

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

    Такая ситуация называется конкуренцией данных или конфликтом изменений. Для ее решения применяются разные способы, один из которых опирается на тот факт, что в элементе отображения GridView хранятся как оригинальные значения полей, так и их последние изменения. Сам источник данных может оперировать с двумя видами параметров: оригинальными, носящими имя поля с префиксом original по умолчанию (например, @original_FirstName ), и измененными, носящими имя поля. Источник может понимать имена оригинальных параметров и с другим префиксом, который можно установить в его свойстве OldValuesParameterFormatString (например, xxx{0} ).

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

    UpdateCommand="UPDATE Employees SET FirstName=@FirstName,
        LastName=@LastName, Title=@Title, City=@City
        FROM Employees WHERE EmployeeID=@original_EmployeeID
        AND FirstName=@original_FirstName
        AND LastName=@original_LastName
        AND Title=@original_Title AND City=@original_City"

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

    Подобную проверку конфликта изменений данных не обязательно закладывать в SQL-команду обновления. Эту обязанность можно возложить на источник данных, поменяв в нем значение свойства ConflictDetection с OverwriteChanges (заменять без проверки, установлено по умолчанию) на CompareAllValues (заменять только при совпадении всех оригинальных значений).

    Применение параметров обновления, не совпадающими с именами полей

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

    Рассмотрим этот способ на примере.

  • Скопируйте страницу SqlDataSourceUpdates.aspx с именем OtherNameParamUpdates.aspx и назначьте ее стартовой
  • Проверьте, что страница OtherNameParamUpdates.aspx работает
  • Назовите параметры источника другими именами, отличными от имен полей, например так
    (рис ) Назначение параметрам команды обновления других имен<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ProviderName="System.Data.SqlClient" 
        ConnectionString="<%$ connectionStrings:Northwind %>" 
        SelectCommand=
            "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
        UpdateCommand="UPDATE Employees SET FirstName=@paramFirstName, 
            LastName=@paramLastName, Title=@paramTitle, City=@paramCity 
            FROM Employees WHERE EmployeeID=@paramEmployeeID" >
        <UpdateParameters>
            <asp:Parameter Name="paramFirstName" Type="string" />
            <asp:Parameter Name="paramLastName" Type="string" />
            <asp:Parameter Name="paramTitle" Type="string" />
            <asp:Parameter Name="paramCity" Type="string" />
            <asp:Parameter Name="paramEmployeeID" Type="string" />
        </UpdateParameters>
    </asp:SqlDataSource>
  • Запустите страницу OtherNameParamUpdates.aspx и убедитесь, что конечные обновления в базе данных не выполняются из за того, что измененные поля не передаются из GridView1 в источник данных SqlDataSource1
  • Для решения задачи используем событие SqlDataSource.Updating, которое возбуждается непосредственно перед подтверждением обновлений.

  • Перейдите в режим Design редактирования страницы OtherNameParamUpdates.aspx и двойным щелчком на свободном месте Web-формы создайте обработчик события Page_Load()
  • Зайдите в тело обработчика и начинайте регистрировать событие Updating источника данных, набирая следующий код
    SqlDataSource1.Updating +=
    и далее используя клавишу
  • Оболочка создаст правильную заготовку обработчика события Updating. Теперь можно полностью удалить обработчик Page_Load() как неиспользуемый.

  • Удалите ненужный теперь обработчик Page_Load()
  • Добавьте к описанию источника атрибут OnUpdating="SqlDataSource1_Updating"
  • Здесь мы пошли более длинным, но универсальным путем создания заготовки обработчика SqlDataSource1_Updating(). Знакомый нам более короткий и традиционный путь - создать заготовку обработчика через панель Properties, при этом имя обработчика автоматически добавится в описание источника данных.

  • Заполните обработчик события Updating так, чтобы страница имела следующий вид
    (рис ) Окончательный код страницы OtherNameParamUpdates.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        void SqlDataSource1_Updating(object sender, SqlDataSourceCommandEventArgs e)
        {
            // Передать значения нестандартным именам параметров
            e.Command.Parameters["@paramFirstName"].Value =
                e.Command.Parameters["@FirstName"].Value;
            e.Command.Parameters["@paramLastName"].Value =
                e.Command.Parameters["@LastName"].Value;
            e.Command.Parameters["@paramTitle"].Value =
                e.Command.Parameters["@Title"].Value;
            e.Command.Parameters["@paramCity"].Value =
                e.Command.Parameters["@City"].Value;
            e.Command.Parameters["@paramEmployeeID"].Value =
                e.Command.Parameters["@EmployeeID"].Value;
        
            // Удалить из коллекции стандартные имена параметров
            e.Command.Parameters.Remove(e.Command.Parameters["@FirstName"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@LastName"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@Title"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@City"]);
            e.Command.Parameters.Remove(e.Command.Parameters["@EmployeeID"]);
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ProviderName="System.Data.SqlClient" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                SelectCommand=
                    "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" 
                UpdateCommand="UPDATE Employees SET FirstName=@paramFirstName, 
                    LastName=@paramLastName, Title=@paramTitle, City=@paramCity 
                    FROM Employees WHERE EmployeeID=@paramEmployeeID" 
                    OnUpdating="SqlDataSource1_Updating" >
                <UpdateParameters>
                    <asp:Parameter Name="paramFirstName" Type="string" />
                    <asp:Parameter Name="paramLastName" Type="string" />
                    <asp:Parameter Name="paramTitle" Type="string" />
                    <asp:Parameter Name="paramCity" Type="string" />
                    <asp:Parameter Name="paramEmployeeID" Type="string" />
                </UpdateParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" 
                                                        AutoGenerateColumns="False" >
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="Title" HeaderText="Статус" />
                    <asp:BoundField DataField="City" HeaderText="Проживает" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу OtherNameParamUpdates.aspx и убедитесь, что она сохранила прежние способности обновления данных в базе
  • Переходим к следующей части...

    Старательно сопишь - значит победишь

    Компонент управления источником данных ObjectDataSource

    Компонент ObjectDataSource находится на вкладке Data панели Toolbox. Он позволяет создавать декларативным (на этапе проектирования) способом связь между элементом отображения и компонентом доступа к данным, который запрашивает и обновляет их. Он является более гибким по сравнению с источником данных SqlDataSource. Но для корректного применения пользовательский класс доступа к данным, которым может быть и разработанный нами класс, должен подчиняться нескольким правилам:

  • Пользовательский класс не должен сохранять состояние, поскольку ObjectDataSource создает его экземпляры по необходимости и уничтожает их за собой в конце каждого запроса
  • Пользовательский класс должен иметь конструктор по умолчанию (без аргументов)
  • Компонент должен быть представлен только одним классом. Если для решения разных частей одной задачи используются несколько классов, то они должны быть упакованы в оболочку одного высокоуровневого класса
  • Ни один из основных рабочих методов, используемых для извлечения и обновления записей, не должен быть статическим. Они должны быть только методами экземпляра
  • Каждый тип запроса (извлечение, вставка, удаление и обновление) должен быть представлен единственным методом, возвращающим результаты запроса
  • Результаты запроса должны размещаться в общедоступных свойствах, которые представляют собой коллекции, массивы или списковые объекты, реализующие интерфейс IEnumerable
  • Пользовательский компонент доступа к данным должен размещаться в папке App_Code или быть скомпилированным в сборку и помещен в папку Bin
  • Выполнение этих правил гарантирует, что привязка данных будет простой и организована без дополнительных усилий с нашей стороны. Отступление от правил требует написания дополнительного кода.

    Простое использование ObjectDataSource

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

  • На корне Web-дерева создайте папку App_Code командой контекстного меню Add ASP.NET Folder
  • На папке Add_Code выполните команду Website/Add Existing Item и скопируйте из папки WebSite8/App_Code все ее содержимое, которое представляет собой описание пользовательского класса EmployeeDB
  • Вспомним, что ранее мы поместили в базу данных с помощью страницы WebSite8/SaveStoredProcedure.aspx хранимые процедуры.

  • Скопируйте в текущее приложение страницу WebSite8/SaveStoredProcedure.aspx, откройте ее и посмотрите, что хранимые процедуры объявляются в ней так
  • // Объявления хранимых процедур
        // Вставка записи
        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";

    В их числе и процедура GetAllEmployees, которую реализует в пользовательском классе EmployeeDB одноименный метод GetAllEmployees(). Эта процедура извлекает из таблицы Employees записи четырех полей в порядке их следования в строке запроса. Если мы укажем помещенному на страницу компоненту ObjectDataSource имя нашего пользовательского класса и имя метода, который реализует в классе нужный SQL-запрос, то ObjectDataSource сам создаст экземпляр этого пользовательского класса и вызовет указанный метод. В результате, объект пользовательского класса будет заполнен данными, которые увидит пользователь через привязанные к компоненту ObjectDataSource элементы отображения.

  • Создайте новую страницу с совмещенным кодом и именем ObjectDataSourceSimple.aspx. Назначьте ее стартовой
  • Перенесите на страницу ObjectDataSourceSimple.aspx из панели Toolbox следующие компоненты
  • ObjectDataSource из вкладки Data
  • ListBox из вкладки Standard
  • GridView из вкладки Data
  • Выполните простые настройки свойств компонентов, чтобы окончательный код страницы стал таким
    (рис ) Код страницы ObjectDataSourceSimple.aspx после декларативных настроек <%@ Page Language="C#" %>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees"
                TypeName="EmployeeDB" />
            <asp:ListBox ID="ListBox1" runat="server" 
                DataSourceID="ObjectDataSource1" DataTextField="LastName" />
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="ObjectDataSource1" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceSimple.aspx, чтобы получить следующий результат
  • LastName TitleOfCourtesy EmployeeID FirstName
    Davolio Ms. 1 Nancy
    Fuller Dr. 2 Andrew
    Leverling Ms. 3 Janet
    Peacock Mrs. 4 Margaret
    Buchanan Mr. 5 Steven
    Suyama Mr. 6 Michael
    King Mr. 7 Robert
    Callahan Ms. 8 Laura
    Снетков Доцент 007 9 Владимир

    Исключительно простой получилась привязка данных к элементам отображения через компонент ObjectDataSource. Никакого кода не пришлось писать. Прямо - работа для дизайнеров или кухарок! Но пользовательский-то класс EmployeeDB мы ранее с вами создали. Его просто здесь не видно - он работает за кулисами и вызывается по ссылке, указанной в свойстве TypeName компонента ObjectDataSource. Элементы отображения, в свою очередь, получают свою порцию данных через ссылку на заполненный ObjectDataSource, указанную в свойстве DataSourceID.

    Использование ObjectDataSource с параметризованным конструктором пользовательского класса

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

    Компонент ObjectDataSource имеет богатую модель событий, представленную ниже

  • System.Web.UI.WebControls.ObjectDataSource.Deleted
  • System.Web.UI.WebControls.ObjectDataSource.Deleting
  • System.Web.UI.WebControls.ObjectDataSource.Filtering
  • System.Web.UI.WebControls.ObjectDataSource.Inserted
  • System.Web.UI.WebControls.ObjectDataSource.Inserting
  • System.Web.UI.WebControls.ObjectDataSource.ObjectCreated
  • System.Web.UI.WebControls.ObjectDataSource.ObjectCreating
  • System.Web.UI.WebControls.ObjectDataSource.ObjectDisposing
  • System.Web.UI.WebControls.ObjectDataSource.Selected
  • System.Web.UI.WebControls.ObjectDataSource.Selecting
  • System.Web.UI.WebControls.ObjectDataSource.Updated
  • System.Web.UI.WebControls.ObjectDataSource.Updating
  • Эти события можно разделить на две категории:

  • Заканчивающиеся на ing - происходят во время выполнения задачи и позволяют вмешаться в этот процесс, что-то настроить или прервать
  • Заканчивающиеся на ed - происходят по завершении задачи и удобны для протоколирования действий, синхронизации других элементов управления или обработки ошибок
  • Если мы хотим передать компоненту ObjectDataSource уже инициализированный параметризованным конструктором готовый экземпляр пользовательского класса, то этот экземпляр нужно самим создать с вызовом нужного конструктора. И сделать это нужно в обработчике события ObjectDataSource.ObjectCreating.

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

    using 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;
        }
    }
  • Скопируйте страницу ObjectDataSourceSimple.aspx под именем ObjectDataSourceDesigner.aspx и назначьте ее стартовой
  • Запустите страницу, чтобы проверить, что конструктор по умолчанию работает и извлекает из файла Web.Config строку соединения с именем "Northwind"
  • В файле Web.Config скопируйте строку соединения с идентификатором "Northwind" под другим именем "CloneNorthwind", после чего должно получиться следующее
    (рис ) Файл Web.Config после клонирования строки соединения<?xml version="1.0"?>
    <configuration>
    	<connectionStrings>
    		<add name="Northwind"      connectionString="Data Source=localhost; 
       Initial Catalog=Northwind; user id=sa; password=;" />
    		<add name="CloneNorthwind" connectionString="Data Source=localhost; 
       Initial Catalog=Northwind; user id=sa; password=;" />
    	</connectionStrings>
    	<system.web>
    		<compilation debug="true"/>
    	</system.web>
    </configuration>
  • Измените в файле Web.Config имя строки соединения "Northwind" на несуществующее имя, например "Northwind1" и вновь запустите страницу. Теперь страница выдает ошибку, поскольку конструктор по умолчанию ищет в файле Web.Config строку соединения с именем "Northwind"
  • Пусть теперь это ошибочное имя строки соединений пока побудет в файле Web.Config, а мы попробуем запустить конструктор по умолчанию, чтобы страница искала строку соединения с именем "CloneNorthwind", которая у нас есть.

  • Откройте страницу на редактирование в режиме Design и выделите на ней объект ObjectDataSource1
  • Переведите панель Properties в режим редактирования событий и двойным щелчком на поле
  • Заполните обработчик так, чтобы окончательный код страницы ObjectDataSourceDesigner.aspx был следующим
    (рис ) Окончательный код страницы ObjectDataSourceDesigner.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {
            e.ObjectInstance = new EmployeeDB("CloneNorthwind");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees"
                TypeName="EmployeeDB" 
                OnObjectCreating="ObjectDataSource1_ObjectCreating" />
            <asp:ListBox ID="ListBox1" runat="server" 
                DataSourceID="ObjectDataSource1" DataTextField="LastName" />
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="ObjectDataSource1" />
        </div>
        </form>
    </body>
    </html>
  • Запустите страницу ObjectDataSourceDesigner.aspx и убедитесь, что опять все заработало, поскольку новую строку соединения пользовательский объект теперь получает через параметризованный конструктор в обработчике
  • Верните в файле Web.Config прежнее имя "Northwind" строке соединения, поскольку его используют все наши предыдущие страницы (строку с именем "CloneNorthwind" также оставьте для работы этого примера)
  • Использование ObjectDataSource с параметризованным методом пользовательского класса

    По синтаксису настройки компонента ObjectDataSource мы не можем использовать ни параметризованный конструктор, ни параметризованный метод, поскольку свойствам SelectMethod, InsertMethod, DeleteMethod и UpdateMethod можно присваивать только имя метода. Реальные же методы пользовательского класса могут содержать параметры. Например, в нашем классе EmployeeDB содержатся следующие параметризованные методы:

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public EmployeeDetails GetEmployee(int employeeID) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • public void UpdateEmployee(int employeeID, string firstName,string lastName, string titleOfCourtesy) {...}
  • Мы воспользуемся методом GetEmployee(int employeeID), который ранее был описан так

    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();
            }
        }

    Метод GetEmployee() вызывает хранимую процедуру GetEmployee с параметром, имеющую следующий вид

    // Выбрать запись
        string sql6 =
            "CREATE PROCEDURE GetEmployee "
          + "@EmployeeID        int "
          + "AS "
          + "SELECT EmployeeID, FirstName, LastName, TitleOfCourtesy "
          + "FROM Employees "
          + "WHERE EmployeeID = @EmployeeID";

    Сейчас наша задача - создать страницу на основе компонента ObjectDataSource и пользовательского класса EmployeeDB, в которой при загрузке автоматически заполнялся бы список значениями EmployeeID из таблицы Employees, а при щелчке пользователя на конкретном элементе списка выдавались бы остальные сведения о выбранном сотруднике.

  • Создайте новую страницу с совмещенным кодом под именем ObjectDataSourceParameters.aspx и назначьте ее стартовой
  • Поместите на страницу из панели Toolbox следующие компоненты в порядке их перечисления:
  • ObjectDataSource из вкладки Data
  • Дескриптор <h2>Выберите ID сотрудника</h2>
  • ListBox из вкладки Standard
  • ObjectDataSource из вкладки Data
  • Дескриптор <h2>Данные о сотруднике</h2>
  • DetailsView из вкладки Data
  • Настроим компоненты страницы ObjectDataSourceParameters.aspx декларативным способом в режиме Design через панель Properties.

  • Выделите первый источник ObjectDataSource1 и установите для него следующие свойства
  • SelectMethod="GetAllEmployees" - автоматически вызываемый метод
  • TypeName="EmployeeDB" - применяемый пользовательский класс
  • Выделите список ListBox1 и установите для него следующие свойства
  • DataSourceID="ObjectDataSource1" - подключенный источник данных
  • DataTextField="EmployeeID" - видимые данные списка
  • AutoPostBack="True" - инициирует обратную отсылку списка
  • Rows=9 - количество видимых строк
  • Width=100 - ширина в пикселах
  • Выделите второй источник ObjectDataSource2 и установите для него следующие свойства
  • SelectMethod="GetEmployee" - автоматически вызываемый метод
  • TypeName="EmployeeDB" - применяемый пользовательский класс
  • Щелчком на кнопке ( ...) в поле свойства
  • Работа этого окна трансформируется в следующий дескрипторный код разметочной части страницы

    <asp:ObjectDataSource ID="ObjectDataSource2" runat="server" 
        SelectMethod="GetEmployee" TypeName="EmployeeDB" 
        OnSelecting="ObjectDataSource2_Selecting" >
        <SelectParameters>
            <asp:ControlParameter ControlID="ListBox1" 
                Name="employeeID" PropertyName="SelectedValue" />
        </SelectParameters>
    </asp:ObjectDataSource>

    Имя для передаваемого в источник ObjectDataSource2 параметра должно повторять имя формального параметра, указанного при объявлении метода

    public EmployeeDetails GetEmployee(int employeeID) {...}

    поскольку источник допускает перегрузку прикрепленного метода на основании анализа числа переданных параметров и их имен в объявлении метода (явного-то вызова метода нет!).

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

  • Выделите элемент отображения данных DetailsView1 и установите для него следующие свойства
  • DataSourceID="ObjectDataSource2"
  • AutoGenerateRows="False" - подавляет самостоятельное генерирование строк для всех полей без разбора
  • Запустите страницу ObjectDataSourceParameters.aspx и получите ошибку выполнения!!!
  • Дело здесь в том, что когда страница запрашивается в первый раз, нет никакого выбранного значения в элементе ListBox1, но элемент ObjectDataSource2 все равно вызовет метод GetEmployee() с неопределенным значением employeeID. Это и вызовет генерацию исключения в поиске данных. Ситуацию можно исправить, если перехватить попытку привязки в элементе ObjectDataSource2 и явно отменить ее, когда параметр employeeID не определен.

    Для этого нужно обработать событие ObjectDataSource.Selecting, которое как раз происходит перед выполнением SQL-запроса по поиску данных в базе данных. Если мы проверим коллекцию InputParameters и не обнаружим в ней параметра employeeID, то работу ObjectDataSource2 нужно будет прервать.

  • Создайте в панели Properties для события ObjectDataSource2.Selecting обработчик, который заполните так, чтобы окончательный код страницы ObjectDataSourceParameters.aspx был следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource2_Selecting(object sender, 
            ObjectDataSourceSelectingEventArgs e)
        {
            if (e.InputParameters["employeeID"] == null)
                e.Cancel = true;
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                SelectMethod="GetAllEmployees" TypeName="EmployeeDB" />
                    
            <h2>
                Выберите ID сотрудника</h2>
            <asp:ListBox ID="ListBox1" runat="server" AutoPostBack="True" 
                DataSourceID="ObjectDataSource1" DataTextField="EmployeeID" 
                Rows="9" Width="100px" />
                
            <asp:ObjectDataSource ID="ObjectDataSource2" runat="server" 
                SelectMethod="GetEmployee" TypeName="EmployeeDB" 
                OnSelecting="ObjectDataSource2_Selecting" >
                <SelectParameters>
                    <asp:ControlParameter ControlID="ListBox1" 
                        Name="employeeID" PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:ObjectDataSource>
                
            <h2>
                Данные о сотруднике</h2>
            <asp:DetailsView ID="DetailsView1" runat="server" 
                Height="50px" Width="125px" DataSourceID="ObjectDataSource2" 
                AutoGenerateRows="False" >
                <Fields>
                    <asp:BoundField DataField="LastName" 
                        HeaderText="LastName" SortExpression="LastName" />
                    <asp:BoundField DataField="TitleOfCourtesy" 
                        HeaderText="TitleOfCourtesy" SortExpression="TitleOfCourtesy" />
                    <asp:BoundField DataField="EmployeeID" 
                        HeaderText="EmployeeID" SortExpression="EmployeeID" />
                    <asp:BoundField DataField="FirstName" 
                        HeaderText="FirstName" SortExpression="FirstName" />
                </Fields>
            </asp:DetailsView>
        </div>
        </form>
    </body>
    </html>

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

  • Исполните страницу ObjectDataSourceParameters.aspx и получите результат
  • Выберите ID сотрудника

    Данные о сотруднике

    LastName Снетков
    TitleOfCourtesy Доцент 007
    EmployeeID 9
    FirstName Владимир

    Аналогичным образом можно задействовать и другие методы нашего пользовательского класса EmployeeDB, имеющие параметры:

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • public void UpdateEmployee(int employeeID, string firstName, string lastName, string titleOfCourtesy) {...}
  • Использование ObjectDataSource для обновления записей

    Продемонстрируем возможность подключения к ObjectDataSource метода UpdateEmployee() пользовательского класса EmployeeDB, предназначенного для обновления записей таблицы Employees. За одно посмотрим, как правильно вызвать перегрузку этого метода, если в качестве параметра используется другой пользовательский тип, экземпляр которого прежде нужно создать, и только потом передать компоненту ObjectDataSource как параметр.

    Применение исходного метода UpdateEmployee()

    Вначале используем метод UpdateEmployee() в том виде с четырьма параметрами, как он был разработан нами ранее. Для сокращения работы используем часть наработок страницы SqlDataSourceUpdates.aspx. Метод UpdateEmployee() реализует SQL-команду хранимой процедуры UpdateEmployee, имеющей следующий вид

    // Обновление записи
    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";

    Сам существующий метод UpdateEmployee() описывается так в файле App_Code/UpdateEmployeeDB.cs

    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();
        }
    }
  • Скопируйте страницу SqlDataSourceUpdates.aspx с именем ObjectDataSourceUpdates1.aspx и назначьте ее стартовой
  • Запустите страницу ObjectDataSourceUpdates1.aspx на выполнение и удостовертесь, что она работоспособна
  • Удалите элемент SqlDataSource1 и на его месте расположите объект ObjectDataSource1
  • Настройте объект ObjectDataSource1 следующим образом
  • TypeName="EmployeeDB"
  • SelectMethod="GetAllEmployees"
  • UpdateMethod="UpdateEmployee"
  • Объект GridView1 подкорректируйте так:
  • DataSourceID="ObjectDataSource1" - поменять подключаемый источник данных
  • <asp:BoundField DataField="City" HeaderText="Проживает" /> - удалить строку с отсутствующим в SQL-команде полем
  • <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" /> - изменить имя поля на имеющееся в SQL-команде
  • В окончательном виде страница ObjectDataSourceUpdates1.aspx должна стать такой

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                TypeName="EmployeeDB"            
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" />
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>

    Метод UpdateEmployee() автоматически получает от объекта отображения GridView1 значения параметров для обновления за счет того, что имена формальных аргументов метода совпадают с именами полей элемента отображения (регистр не учитывается). Если в определении метода изменить имя хотя-бы одного формального аргумента, то страница выдаст ошибку о запросе несуществующего в таблице Employees поля.

  • Исполните страницу ObjectDataSourceUpdates1.aspx и убедитесь, что она функционирует в соответствии с задуманным и в одном из вариантов генерирует такой HTML-результат
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms.
    2 Andrew Fuller Dr.
    3 Janet Leverling Ms.
    4 Margaret Peacock Mrs.
    5 Steven Buchanan Mr.
    6 Michael Suyama Mr.
    7 Robert King Mr.
    8 Laura Callahan Ms.
    9 Владимир Снетков Доцент 007

    Применение перегруженного метода UpdateEmployee()

    Исходный вариант метода UpdateEmployee() имеет четыре аргумента, которые автоматически используются как параметры канала передачи данных от источника отображения к полям таблицы. Но для тех же целей можно использовать любой промежуточный объект, описание (читай - класс, тип ) которого будет удовлетворять следующим условиям:

  • Если имеется параметризованный конструктор, то и обязательно должен быть объявлен конструктор по умолчанию, даже пустой (это стандартное требование языков C++ и C#)
  • Класс должен иметь общедоступные ( public ) и записываемые (не readonly ) свойства с именами, совпадающими с полями таблицы, для передачи данных в которую он будет использоваться
  • Таким условиям удовлетворяет вспомогательный класс EmployeeDetails, который мы разработали ранее и который имеет следующее описание в файле App_Code/EmployeeDetails.cs

    using 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; }
        }
    }

    Объект класса EmployeeDetails вполне может служить каналом передачи данных из элемента GridView в таблицу при выполнении метода обновления, если его использовать в качестве аргумента этого метода. Дополним класс EmployeeDB еще одним перегружаемым методом UpdateEmployee().

  • Откройте на редактирование файл App_Code/UpdateEmployeeDB.cs и дополните его (скопируйте и чуть подправьте!) следующим кодом перегружаемого метода UpdateEmployee()
  • 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();
            }
        }
        
        // Перегружаемый метод с объектом класса EmployeeDetails
        public void UpdateEmployee(EmployeeDetails emp)
        {
            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 = emp.EmployeeID;
            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;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }

    Теперь осталось создать страницу, реализующую этот новый канал обмена данными.

  • Сделайте копию страницы ObjectDataSourceUpdates1.aspx с именем ObjectDataSourceUpdates2.aspx и назначьте ее стартовой
  • Дополните описание объекта ObjectDataSource1 всего-лишь одним м-а-а-а-леньким атрибутом
  • DataObjectTypeName="EmployeeDetails" - для указания того, по описанию какого класса создавать промежуточный объект для передачи параметров в SQL-команду обновления данных в перегруженном методе UpdateEmployee()
  • Окончательный код страницы ObjectDataSourceUpdates2.aspx будет следующим

    <%@ Page Language="C#" %>
        
    <script runat="server">
        // Здесь мы не написали ни строчки кода!!!
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
                TypeName="EmployeeDB" 
                DataObjectTypeName="EmployeeDetails"            
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" />
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Button" HeaderText="Изменить" 
                        ShowEditButton="True" ShowHeader="True"
                        EditText="Редакция" CancelText="Отмена" UpdateText="Применить" >
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceUpdates2.aspx и убедитесь, что ее функциональность в точности повторяет функциональность страницы ObjectDataSourceUpdates1.aspx
  • Чтобы рассеять сомнения в том, что работает именно новый метод
    public void UpdateEmployee(EmployeeDetails emp){...}
    заключите (временно!) в файле UpdateEmployeeDB.cs исходный метод в комментарные скобки /*...*/
    /*		
     // Исходный метод 
     public void UpdateEmployee(int employeeID, string firstName,
                                string lastName, string titleOfCourtesy)
     {
      ...
     }
    */
  • Вновь запустите страницу ObjectDataSourceUpdates2.aspx. Функциональность полностью сохранилась, значит работает все-таки наш новый метод
  • Раскомментируйте исходный метод UpdateEmployee()
  • Применение иного метода обновления и нестандартных имен аргументов

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

    Такая свобода требует написания дополнительного кода для подмены (в последний момент) в обработчике события ObjectDataSource.Updating значений в терминах GridView, в значения в терминах источника ObjectDataSource, передающего эти значения дальше в таблицу Employees для обновления конечных данных. Ранее мы подобную задачу решали на странице OtherNameParamUpdates.aspx применительно к элементу SqlDataSource.

  • Добавьте в класс EmployeeDB файла App_Code/UpdateEmployeeDB.cs копию исходного метода с четырьмя параметрами и подкорректируйте его так (теперь в файле будут три метода обновления)
    (рис ) Новый метод обновления в классе EmployeeDB файла App_Code/UpdateEmployeeDB.csusing System;
    using System.Data;
        
    using System.Data.SqlClient;
        
    public partial class EmployeeDB
    {
        // Исходный метод 
        public void UpdateEmployee(int employeeID, string firstName,
                                   string lastName, string titleOfCourtesy)
        {
         ...
        }
        
        // Перегружаемый метод с объектом класса EmployeeDetails
        public void UpdateEmployee(EmployeeDetails emp)
        {
         ...	
        }
        
        // Новый метод обновления данных
        // Изменены: имя метода, имена аргументов и порядок их следования
        public void UpdateEmployeeStrict(string titleOfCourtesyStrict, string lastNameStrict,
                                   string firstNameStrict, int employeeIDStrict)
        {
            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 = employeeIDStrict;
            cmd.Parameters.Add(new SqlParameter("@FirstName", SqlDbType.NVarChar, 10));
            cmd.Parameters["@FirstName"].Value = firstNameStrict;
            cmd.Parameters.Add(new SqlParameter("@LastName", SqlDbType.NVarChar, 20));
            cmd.Parameters["@LastName"].Value = lastNameStrict;
            cmd.Parameters.Add(new SqlParameter("@TitleOfCourtesy", SqlDbType.NVarChar, 25));
            cmd.Parameters["@TitleOfCourtesy"].Value = titleOfCourtesyStrict;
        
            try
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
            catch
            {
                throw new ApplicationException("Ошибка данныx.");
            }
            finally
            {
                con.Close();
            }
        }
    }
  • Сделайте копию страницы ObjectDataSourceUpdates1.aspx с именем ObjectDataSourceUpdates3.aspx и назначьте ее стартовой
  • Сейчас мы воспользуемся мощью оболочки Visual Studio 2005 и одним махом сгенерируем нужный нам код разметочной части страницы

  • Безжалостно (!!!) удалите в режиме Design настроенный (как-то там) объект ObjectDataSource1 и на его место из вкладки Data панели Toolbox скопируйте новый объект типа ObjectDataSource с прежним именем
  • Щелкните мышью на треугольнике в правом верхнем углу объекта
  • Щелкните на единственной, пока, ссылке Configure Data Source (сконфигурировать источник данных), чтобы запустить мастер генерации настроек элемента ObjectDataSource1
  • На первой странице мастера из раскрывающегося списка выберите используемый класс
  • На новой странице вначале установите вкладку SELECT, через которую установите метод
  • Установите вкладку UPDATE, через которую установите наш новый метод обновления данных
  • После щелчка на кнопке Finish, поскольку на странице уже имеется элемент GridView1, не согласованный с новыми настройками источника данных, мастер выдаст предупреждение с предложением автоматически привести их в соответствие (мы занимаемся декларативной стыковкой элементов под неусыпным присмотром среды проектирования - оболочки!)

  • Ответьте отрицательно - нам не нужны новые замены в настройках GridView1
  • Вот какой код сгенерировал мастер настроек объекта ObjectDataSource1 (Ах, какой умный Большой Билл! Даже уточнения типов параметров сгенерировал сам)

    <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
        TypeName="EmployeeDB"
        SelectMethod="CountEmployees" 
        UpdateMethod="UpdateEmployeeStrict">
        <UpdateParameters>
            <asp:Parameter Name="titleOfCourtesyStrict" Type="String" />
            <asp:Parameter Name="lastNameStrict" Type="String" />
            <asp:Parameter Name="firstNameStrict" Type="String" />
            <asp:Parameter Name="employeeIDStrict" Type="Int32" />
        </UpdateParameters>
    </asp:ObjectDataSource>

    Теперь осталось написать код подмены значений параметров из GridView1 в принимающих параметрах источника ObjectDataSource1 перед обновлением конечных данных

  • Выделите в режиме Design объект ObjectDataSource1 и через панель Properties двойным щелчком на поле события Updating создайте для него заготовку обработчика
  • Заполните обработчик так, чтобы окончательный код страницы стал следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void ObjectDataSource1_Updating(object sender, ObjectDataSourceMethodEventArgs e)
        {
            // Перегрузить данные на других лошадей для
            // продолжения поездки к базе данных
            e.InputParameters["firstNameStrict"] = e.InputParameters["FirstName"];
            e.InputParameters["lastNameStrict"] = e.InputParameters["LastName"];
            e.InputParameters["titleOfCourtesyStrict"] = e.InputParameters["TitleOfCourtesy"];
            e.InputParameters["employeeIDStrict"] = e.InputParameters["EmployeeID"];
        
            // Удалить из коллекции параметров ненужное
            e.InputParameters.Remove("FirstName");
            e.InputParameters.Remove("LastName");
            e.InputParameters.Remove("TitleOfCourtesy");
            e.InputParameters.Remove("EmployeeID");
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                TypeName="EmployeeDB"
                SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployeeStrict" 
                OnUpdating="ObjectDataSource1_Updating">
                    
                <UpdateParameters>
                    <asp:Parameter Name="titleOfCourtesyStrict" Type="String" />
                    <asp:Parameter Name="lastNameStrict" Type="String" />
                    <asp:Parameter Name="firstNameStrict" Type="String" />
                    <asp:Parameter Name="employeeIDStrict" Type="Int32" />
                </UpdateParameters>
                    
            </asp:ObjectDataSource>
                
                    
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" 
                DataSourceID="ObjectDataSource1">
                    
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField ButtonType="Link" CancelText="Отмена" 
                        EditText="Редакция" HeaderText="Изменить"
                        ShowEditButton="True" ShowHeader="True" UpdateText="Применить">
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
                    
            </asp:GridView>
        </div>
        </form>
    </body>
    </html>

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

  • Запустите на выполнение ObjectDataSourceUpdates3.aspx и получите работающую страницу редактирования данных, стерилизованный (ссылки отключены) вариант HTML-вывода которой приведен ниже
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms. Редакция
    2 Andrew Fuller Dr. Редакция
    3 Janet Leverling Ms. Редакция
    4 Margaret Peacock Mrs. Редакция
    5 Steven Buchanan Mr. Редакция
    6 Michael Suyama Mr. Редакция
    7 Robert King Mr. Редакция
    8 Laura Callahan Ms. Редакция
    9 Владимир Снетков Доцент 007 Редакция

    Использование ObjectDataSource для вставки и удаления записей

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

  • public int InsertEmployee(EmployeeDetails emp) {...}
  • public void DeleteEmployee(int employeeID) {...}
  • Рассмотрим операцию вставки с применением пользовательского класса EmployeeDB. Эту операцию мы вправе добавить на страницу в дополнение к уже реализованной операции обновления. Особенность реализации метода вставки состоит в том, что он возвращает значение, указывающее содержимое поля EmployeeID. Это поле в таблице является ключевым и имеет свойство AutoIncrement (автоматическое увеличение). Его значение, возвращенное методом, можно проигнорировать, но можно и перехватывать и использовать для каких-нибудь целей, например извещения пользователя.

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

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

  • Добавьте после элемента GridView1 компонент LinkButton и создайте для него обработчик LinkButton1_Click()
  • В режиме Design двойным щелчком на свободном месте Web-формы создайте обработчик Page_Load() страницы, в котором мы будем формировать сообщение о номере добавленной в таблицу записи
  • Поместите после элемента LinkButton1 компонент Label с именем lblInfo, в котором мы будем размещать сообщения для пользователя о добавленной или удаленной записи таблицы Employees
  • В режиме Design вызовите встроенную панель источника
  • После щелчка на кнопке Finish откажитесь от предложения скорректировать объект
  • Вызовите через свойство
  • Выделите элемент ObjectDataSource1 и через панель Properties создайте обработчик ObjectDataSource1_Deleting() для события Deleting, в котором мы будем перехватывать номер удаляемой из таблицы записи
  • В результате таких действий по конфигурированию элемента ObjectDataSource1 оболочка добавит в его дескриптор нужный код (который, кстати, можно ввести и вручную без всяких визуальных помощников)

    <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
        TypeName="EmployeeDB" SelectMethod="GetAllEmployees" 
        UpdateMethod="UpdateEmployee" 
        DeleteMethod="DeleteEmployee"
        OnDeleting="ObjectDataSource1_Deleting">
        <UpdateParameters>
            <asp:Parameter Name="employeeID" Type="Int32" />
            <asp:Parameter Name="firstName" Type="String" />
            <asp:Parameter Name="lastName" Type="String" />
            <asp:Parameter Name="titleOfCourtesy" Type="String" />
        </UpdateParameters>
        <DeleteParameters>
            <asp:ControlParameter ControlID="GridView1" 
                Name="employeeID" Type="int32" />
        </DeleteParameters>
    </asp:ObjectDataSource>

    Теперь скорректируем управляющий столбец объекта GridView1, чтобы добавить в его пользовательский интерфейс кнопку для возможности удаления записи. Кнопку добавим в форме гиперссылки Link.

  • Вызовите встроенный визуальный редактор для объекта
  • Выделите управляющий столбец "Изменить" и в поле CommandField properties установите значения следующих свойств
  • ButtonType=Link
  • DeleteText="Удалить"
  • ShowDeleteButton="True"
  • Вспомним, что в реляционных базах данных должна поддерживаться целостность данных. Это значит, что если таблицы находятся в отношении "главный-подчиненный", то при удалении записи в главной таблице, имеющей первичный ключ, нужно позаботиться об удалении всех записей связанной таблицы с идентичными значениями вторичных ключей.

    ADO.NET отслеживает это положение автоматически, но ей нужно указать первичный ключ. Для этого служит параметр DataKeyNames в элементе управления GridView1 (поскольку мы планируем удалять запись через него), в котором указывается строка с одним именем ключевого поля или строка со списком ключевых полей, разделенных запятыми. В нашем случае первичным ключом в таблице Employees является поле EmployeeID.

  • Переведите редактор страницы в режим Source и добавьте в заголовок элемента GridView1 параметр, назначающий ключевое поле редактируемой таблицы
  • DataKeyNames="EmployeeID"
  • Выделите текстовую метку lblInfo и сбросьте свойство сохранения состояния EnableViewState="False", чтобы при каждом новом запросе ее значение очищалось
  • На этом этап декларативной настройки страницы закончен. Теперь нужно заполнить созданные нами ранее обработчики соответствующим кодом.

  • Заполните обработчики событий так, чтобы окончательный код страницы ObjectDataSourceFull.aspx стал следующим
    (рис ) Окончательный код страницы ObjectDataSourceFull.aspx<%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void LinkButton1_Click(object sender, EventArgs e)
        {
            EmployeeDB db = new EmployeeDB();
            // Вставляем пустую запись и запоминаем ее номер
            Session["recordNumber"] = 
                db.InsertEmployee(new EmployeeDetails(0, "", "", ""));
            // Обновляем страницу без полного цикла,
            Server.Transfer(Request.FilePath);
        }
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Session["recordNumber"] != null)
            {
                lblInfo.Text = "Добавлена запись "
                    + Session["recordNumber"].ToString();
                Session.Remove("recordNumber");
            }
        }
        
        protected void ObjectDataSource1_Deleting(object sender, 
            ObjectDataSourceMethodEventArgs e)
        {
            lblInfo.Text = "Удалена запись " 
                + e.InputParameters["employeeID"].ToString();
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                TypeName="EmployeeDB" SelectMethod="GetAllEmployees" 
                UpdateMethod="UpdateEmployee" 
                DeleteMethod="DeleteEmployee"
                OnDeleting="ObjectDataSource1_Deleting">
                <UpdateParameters>
                    <asp:Parameter Name="employeeID" Type="Int32" />
                    <asp:Parameter Name="firstName" Type="String" />
                    <asp:Parameter Name="lastName" Type="String" />
                    <asp:Parameter Name="titleOfCourtesy" Type="String" />
                </UpdateParameters>
                <DeleteParameters>
                    <asp:ControlParameter ControlID="GridView1" 
                        Name="employeeID" Type="int32" />
                </DeleteParameters>
            </asp:ObjectDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                AutoGenerateColumns="False" DataSourceID="ObjectDataSource1" 
                DataKeyNames="EmployeeID">
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="№ п/п" />
                    <asp:BoundField DataField="FirstName" HeaderText="Имя" />
                    <asp:BoundField DataField="LastName" HeaderText="Фамилия" />
                    <asp:BoundField DataField="TitleOfCourtesy" HeaderText="Статус" />
                    <asp:CommandField 
                        CancelText="Отмена" EditText="Редакция" HeaderText="Изменить"
                        ShowEditButton="True" ShowHeader="True" UpdateText="Применить" 
                        DeleteText="Удалить" ShowDeleteButton="True">
                        <HeaderStyle BackColor="Red" ForeColor="Yellow" />
                    </asp:CommandField>
                </Columns>
            </asp:GridView>
            <asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">
                Вставка
            </asp:LinkButton>
            <br />
            <asp:Label ID="lblInfo" runat="server" EnableViewState="False" />
        </div>
        </form>
    </body>
    </html>
  • Исполните страницу ObjectDataSourceFull.aspx и насладитесь тем, что она имеет функциональность полноценного редактора таблиц, тем более - удаленных (Ах да Большой Билл, ах да мала-ц-ц-а!). Один из экранов, генерируемых на стороне пользователя, будет таким
  • № п/п Имя Фамилия Статус Изменить
    1 Nancy Davolio Ms. Редакция/Удалить
    2 Andrew Fuller Dr. Редакция/Удалить
    3 Janet Leverling Ms. Редакция/Удалить
    4 Margaret Peacock Mrs. Редакция/Удалить
    5 Steven Buchanan Mr. Редакция/Удалить
    6 Michael Suyama Mr. Редакция/Удалить
    7 Robert King Mr. Редакция/Удалить
    8 Laura Callahan Ms. Редакция/Удалить
    9 Владимир Снетков Доцент 007 Редакция/Удалить
    10 Редакция/Удалить

    Вставка

    Добавлена запись 10

    Программное изменение запроса пользователя в SqlDataSource

    Ранее мы рассматривали пример SqlDataSourceParameters.aspx выборки данных из таблицы в зависимости от выбранного пользователем города. Первый источник SqlDataSource заполнял городами список, жестко привязанный с помощью спискового свойства DataSourceID. Второй источник SqlDataSource отрабатывал один и тот же SQL-запрос с разными значениями параметра, полученными из списка в зависимости от выбора пользователя.

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

  • Сделайте копию страницы SqlDataSourceParameters.aspx с именем SqlDataSourceChange.aspx и назначьте ее стартовой
  • Выделите элемент DropDownList ID="lstCities" и очистите его свойство DataSourceID, тем самым мы отвяжем список от источника данных
  • Двойным щелчком на свободном месте Web-формы создайте обработчик загрузки страницы Page_Load()
  • Для второго источника SqlDataSource ID="sourceEmployees" через панель Properties создайте обработчик события Selecting
  • Заполните обработчики кодом так, чтобы окончательное описание страницы было следующим
  • <%@ Page Language="C#" %>
        
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)// Первый запрос
            {
                // Инициировать запрос и привязать результаты
                lstCities.DataSource = sourceEmployeeCities.Select(
                    DataSourceSelectArguments.Empty);
                lstCities.DataBind();
        
                // Добавить новый элемент в список
                lstCities.Items.Add("Все города");
                lstCities.SelectedIndex = lstCities.Items.Count - 1;
            }
        }
        
        void sourceEmployees_Selecting(object sender, 
                                       SqlDataSourceSelectingEventArgs e)
        {
            if ((string)e.Command.Parameters["@City"].Value == "Все города")
            {
                // Очистить коллекцию параметров
                e.Command.Parameters.Clear();
                    
                // Изменить команду
                e.Command.CommandText = "SELECT EmployeeID, FirstName, LastName, "
                    + "Title, City FROM Employees WHERE City!=''";
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <h2>Выберите город</h2>
            <asp:SqlDataSource ID="sourceEmployeeCities" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT DISTINCT City FROM Employees" />
            <asp:DropDownList ID="lstCities" runat="server" 
                AutoPostBack="True"
                DataTextField="City" />
                
            <h2>Проживающие сотрудники</h2>
            <asp:SqlDataSource ID="sourceEmployees" runat="server" 
                ConnectionString="<%$ connectionStrings:Northwind %>" 
                ProviderName="System.Data.SqlClient" 
                SelectCommand="SELECT EmployeeID, FirstName, LastName, 
                    Title, City FROM Employees WHERE City=@City" 
                    OnSelecting="sourceEmployees_Selecting">
                <SelectParameters>
                    <asp:ControlParameter Name="City" ControlID="lstCities" 
                        PropertyName="SelectedValue" />
                </SelectParameters>
            </asp:SqlDataSource>
            <asp:GridView ID="GridView1" runat="server" 
                DataSourceID="sourceEmployees" />
        </div>
        </form>
    </body>
    </html>

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

    Статический результат выполнения страницы SqlDataSourceChange.aspx следующий

    Выберите город

    Проживающие сотрудники

    EmployeeID FirstName LastName Title City
    9 Владимир Снетков Доцент ВАК Красноярск
    Вернуться к учебному плану