Создание уровня доступа к данным (C#)

Скотт Митчелл

Загрузить PDF-файл

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

Введение

Как веб-разработчики, наша жизнь вращается вокруг работы с данными. Мы создаем базы данных для хранения данных, код для их извлечения и изменения, а также веб-страницы для сбора и формирования итогов. Это первое руководство из длинной серии, в которую будут рассмотрены методы реализации этих распространенных шаблонов в ASP.NET 2.0. Начнем с создания программной архитектуры , состоящей из уровня доступа к данным (DAL) с помощью типизированных наборов данных, уровня бизнес-логики (BLL), который применяет пользовательские бизнес-правила, и уровня представления, состоящего из ASP.NET страниц, которые совместно используют общий макет страницы. После создания основы серверной части мы перейдем к отчетам, демонстрируя, как отображать, суммировать, собирать и проверять данные из веб-приложения. Эти руководства являются краткими и содержат пошаговые инструкции с большим количеством снимков экрана, чтобы наглядно провести процесс. Каждое руководство доступно в версиях C# и Visual Basic и включает скачивание полного используемого кода. (Это первое руководство довольно длинное, но остальные представлены гораздо более удобоваримыми фрагментами.)

Для работы с этими учебниками мы будем использовать версию базы данных Northwind microsoft SQL Server 2005, экспресс-выпуск, размещенную в каталоге App_Data. В дополнение к файлу базы данных папка App_Data также содержит скрипты SQL для создания базы данных на случай, если вы хотите использовать другую версию базы данных. Если вы используете другую SQL Server версию базы данных Northwind, необходимо обновить параметр NORTHWNDConnectionString в файлеWeb.config приложения. Веб-приложение было создано с помощью Visual Studio 2005 Professional Edition в качестве проекта веб-сайта на основе файловой системы. Однако все учебники будут одинаково хорошо работать с бесплатной версией Visual Studio 2005 Visual Web Developer.

В этом руководстве мы начнем с самого начала и создадим уровень доступа к данным (DAL), а затем создадим уровень бизнес-логики (BLL) во втором руководстве, а в третьем — над макетом страницы и навигацией. Учебники после третьего будут основываться на фундаменте, заложенном в первых трех. В этом первом руководстве мы рассмотрели многое, поэтому давайте приступим к работе с Visual Studio.

Шаг 1. Создание веб-проекта и подключение к базе данных

Прежде чем создать уровень доступа к данным (DAL), сначала необходимо создать веб-сайт и настроить базу данных. Начните с создания веб-сайта ASP.NET на основе файловой системы. Для этого перейдите в меню Файл и выберите Новый веб-сайт, открыв диалоговое окно Новый веб-сайт. Выберите шаблон веб-сайт ASP.NET, выберите в раскрывающемся списке Расположение значение Файловая система, выберите папку для размещения веб-сайта и задайте язык C#.

Создание веб-сайта System-Based файла

Рис. 1. Создание нового файла System-Based веб-сайт (щелкните для просмотра полноразмерного изображения)

При этом будет создан новый веб-сайт с Default.aspx ASP.NET страницей и папкой App_Data .

После создания веб-сайта следующим шагом является добавление ссылки на базу данных в серверном Обозреватель Visual Studio. Добавляя базу данных в серверную Обозреватель вы можете добавлять таблицы, хранимые процедуры, представления и т. д. из Visual Studio. Вы также можете просматривать данные таблицы или создавать собственные запросы вручную или графически с помощью построителя запросов. Кроме того, при создании типизированных наборов данных для DAL необходимо указать Visual Studio на базу данных, из которой должны быть созданы типизированные наборы данных. Хотя мы можем предоставить эти сведения о подключении на данный момент времени, Visual Studio автоматически заполняет раскрывающийся список баз данных, уже зарегистрированных в Обозреватель сервера.

Действия по добавлению базы данных Northwind на сервер Обозреватель зависят от того, хотите ли вы использовать базу данных SQL Server 2005, экспресс-выпуск в папке App_Data или если вы хотите использовать сервер базы данных Microsoft SQL Server 2000 или 2005.

Использование базы данных в папке App_Data

Если у вас нет сервера базы данных SQL Server 2000 или 2005 для подключения или вы просто хотите избежать добавления базы данных на сервер базы данных, можно использовать SQL Server 2005, экспресс-выпуск версию базы данных Northwind, которая находится в папке App_Data загруженного веб-сайта (NORTHWND. MDF).

База данных, помещенная в папку App_Data, автоматически добавляется в Обозреватель сервера. Предположим, что на компьютере установлен SQL Server 2005, экспресс-выпуск, вы увидите узел с именем NORTHWND. MDF в серверном Обозреватель, который можно расширять и просматривать его таблицы, представления, хранимые процедуры и т. д. (см. рис. 2).

Папка App_Data также может содержать файлы .mdb Microsoft Access, которые, как и их SQL Server аналоги, автоматически добавляются в Обозреватель сервера. Если вы не хотите использовать какие-либо из SQL Server параметров, вы всегда можете установить базу данных и приложения Northwind Traders и перейти в каталог App_Data. Однако имейте в виду, что базы данных Access не так многофункциональы, как SQL Server, и не предназначены для использования в сценариях веб-сайтов. Кроме того, в нескольких из более чем 35 учебников будут использоваться некоторые функции уровня базы данных, которые не поддерживаются Access.

Подключение к базе данных на сервере базы данных Microsoft SQL Server 2000 или 2005

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

После установки базы данных перейдите к Обозреватель сервера в Visual Studio, щелкните правой кнопкой мыши узел Data Connections и выберите Добавить подключение. Если серверная Обозреватель не отображается, перейдите на Обозреватель Представление или сервер или нажмите клавиши CTRL+ALT+S. Откроется диалоговое окно Добавление подключения, в котором можно указать сервер для подключения, сведения о проверке подлинности и имя базы данных. После успешной настройки сведений о подключении к базе данных и нажатия кнопки ОК база данных будет добавлена в качестве узла под узлом Data Connections. Вы можете развернуть узел базы данных, чтобы просмотреть его таблицы, представления, хранимые процедуры и т. д.

Добавление подключения к базе данных Northwind сервера базы данных

Рис. 2. Добавление подключения к базе данных Northwind сервера базы данных

Шаг 2. Создание уровня доступа к данным

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

Весь код, относящееся к базовому источнику данных, например создание подключения к базе данных, выполнение команд SELECT, INSERT, UPDATE и DELETE и т. д., должен находиться в DAL. Уровень представления не должен содержать ссылок на такой код доступа к данным, а должен выполнять вызовы DAL для всех запросов данных. Слои доступа к данным обычно содержат методы для доступа к базовым данным базы данных. Например, в базе данных Northwind есть таблицы Products и Categories , в которых записываются продукты для продажи и категории, к которым они относятся. В DAL мы будем использовать такие методы, как:

  • GetCategories(), который возвращает сведения обо всех категориях.
  • GetProducts() — возвращает сведения обо всех продуктах.
  • GetProductsByCategoryID(categoryID) — возвращает все продукты, относящиеся к указанной категории.
  • GetProductByProductID(productID) — возвращает сведения об определенном продукте.

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

Например, DataReader и DataSet (по умолчанию) являются слабо типизированными объектами, так как их схема определяется столбцами, возвращаемыми запросом базы данных, используемым для их заполнения. Чтобы получить доступ к определенному столбцу из слабо типизированной dataTable, необходимо использовать такой синтаксис, как DataTable. Rows[index]["columnName"]. Свободная типизация DataTable в этом примере проявляется тем фактом, что нам нужно получить доступ к имени столбца с помощью строки или порядкового индекса. Строго типизированный dataTable, с другой стороны, будет иметь каждый из своих столбцов, реализованных в виде свойств, в результате чего появится код, который выглядит следующим образом: DataTable. Строки[индекс].columnName.

Чтобы вернуть строго типизированные объекты, разработчики могут создавать собственные пользовательские бизнес-объекты или использовать типизированные наборы данных. Бизнес-объект реализуется разработчиком как класс, свойства которого обычно отражают столбцы базовой таблицы базы данных, которую представляет бизнес-объект. Typed DataSet — это класс, созданный Visual Studio на основе схемы базы данных и члены которого строго типизированы в соответствии с этой схемой. Типизированный набор данных состоит из классов, расширяющих ADO.NET классы DataSet, DataTable и DataRow. Помимо строго типизированных таблиц DataTable, typed DataSets теперь также включают TableAdapters, которые представляют собой классы с методами для заполнения таблиц DataSet и распространения изменений в таблицах DataTable обратно в базу данных.

Примечание

Дополнительные сведения о преимуществах и недостатках использования типизированных наборов данных по сравнению с пользовательскими бизнес-объектами см. в статье Проектирование компонентов уровня данных и передача данных через уровни.

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

Весь код доступа к данным низведен до уровня DAL

Рис. 3. Весь код доступа к данным относится к DAL (щелкните для просмотра полноразмерного изображения)

Создание типизированного набора данных и адаптера таблицы

Чтобы приступить к созданию DAL, мы начнем с добавления typed DataSet в наш проект. Для этого щелкните правой кнопкой мыши узел проекта в Обозреватель решений и выберите Добавить новый элемент. Выберите параметр DataSet в списке шаблонов и назовите его Northwind.xsd.

Выберите добавление нового набора данных в проект

Рис. 4. Выберите добавление нового набора данных в проект (щелкните для просмотра полноразмерного изображения)

После нажатия кнопки Добавить при появлении запроса на добавление DataSet в папку App_Code нажмите кнопку Да. Затем отобразится Designer для типизированного набора данных, и запустится мастер настройки TableAdapter, позволяющий добавить первый TableAdapter в typed DataSet.

Типизированный набор данных служит строго типизированной коллекцией данных; он состоит из строго типизированных экземпляров DataTable, каждый из которых, в свою очередь, состоит из строго типизированных экземпляров DataRow. Мы создадим строго типизированную таблицу DataTable для каждой из базовых таблиц базы данных, с которыми нам нужно работать в этой серии руководств. Начнем с создания таблицы DataTable для таблицы Products .

Имейте в виду, что строго типизированные таблицы DataTable не содержат никаких сведений о том, как получить доступ к данным из базовой таблицы базы данных. Чтобы получить данные для заполнения DataTable, мы используем класс TableAdapter, который функционирует как уровень доступа к данным. Для таблицы DataTable products TableAdapter будет содержать методы GetProducts(), GetProductByCategoryID(categoryID) и т. д., которые мы будем вызывать из уровня представления. Роль DataTable заключается в том, чтобы служить строго типизированными объектами, используемыми для передачи данных между слоями.

Мастер настройки TableAdapter начинается с запроса на выбор базы данных для работы. В раскрывающемся списке эти базы данных отображаются в Обозреватель сервера. Если вы не добавили базу данных Northwind в Обозреватель сервера, для этого можно нажать кнопку Создать подключение.

Выбор базы данных Northwind в списке Drop-Down

Рис. 5. Выбор базы данных Northwind в списке Drop-Down (щелкните для просмотра полноразмерного изображения)

После выбора базы данных и нажатия кнопки Далее появится запрос на сохранение строка подключения в файлеWeb.config. Сохранив строка подключения, вы избежите его жесткого кода в классах TableAdapter, что упрощает работу, если строка подключения информация изменится в будущем. Если вы решили сохранить строка подключения в файле конфигурации, он помещается в <раздел connectionStrings>, который можно дополнительно зашифровать для повышения безопасности или изменить позже с помощью новой страницы свойств ASP.NET 2.0 в средстве Администратор пользовательского интерфейса IIS, который лучше подходит для администраторов.

Сохраните строку подключения в Web.config

Рис. 6. Сохранение строки подключения в Web.config (щелкните для просмотра полноразмерного изображения)

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

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

Запрос данных с помощью нерегламентированной инструкции SQL

Рис. 7. Запрос данных с помощью нерегламентированной инструкции SQL (щелкните для просмотра полноразмерного изображения)

На этом этапе можно ввести SQL-запрос вручную. При создании первого метода в TableAdapter обычно требуется, чтобы запрос возвращал столбцы, которые должны быть выражены в соответствующей таблице DataTable. Это можно сделать, создав запрос, который возвращает все столбцы и все строки из таблицы Products :

Введите SQL-запрос в текстовое поле

Рис. 8. Введите SQL-запрос в текстовое поле (щелкните для просмотра полноразмерного изображения)

Кроме того, можно использовать построитель запросов и графически создать запрос, как показано на рис. 9.

Создание запроса в графическом виде с помощью Редактор запросов

Рис. 9. Графическое создание запроса с помощью Редактор запросов (щелкните для просмотра полноразмерного изображения)

После создания запроса, но перед переходом на следующий экран нажмите кнопку Дополнительные параметры. В проектах веб-сайтов единственным дополнительным параметром, выбранным по умолчанию, является "Генерировать инструкции insert, update и delete" (Создать инструкции insert, update и delete). При запуске этого мастера из библиотеки классов или проекта Windows также будет выбран параметр "Использовать оптимистичный параллелизм". Пока не устанавливайте флажок "Использовать оптимистичный параллелизм". Мы рассмотрим оптимистичный параллелизм в будущих руководствах.

Выберите параметр Только создать инструкции Insert, Update и Delete.

Рис. 10. Выберите только параметр Создать инструкции Insert, Update и Delete (Щелкните для просмотра полноразмерного изображения)

После проверки дополнительных параметров нажмите кнопку Далее, чтобы перейти к последнему экрану. Здесь нам предлагается выбрать методы для добавления в TableAdapter. Существует два шаблона заполнения данных:

  • Заполните DataTable с помощью этого подхода. Создается метод , который принимает DataTable в качестве параметра и заполняет его на основе результатов запроса. Например, класс dataAdapter ADO.NET реализует этот шаблон с помощью метода Fill().
  • При таком подходе метод возвращает DataTable, который создает и заполняет dataTable и возвращает его в качестве возвращаемого значения методов.

TableAdapter может реализовать один или оба этих шаблона. Вы также можете переименовать методы, указанные здесь. Давайте оставим оба флажка флажками, хотя в этих руководствах мы будем использовать только последний шаблон. Кроме того, давайте переименуем довольно универсальный метод GetData в GetProducts.

Если этот флажок установлен, последний флажок GenerateDBDirectMethods создает методы Insert(), Update() и Delete() для TableAdapter. Если оставить этот флажок снятым, все обновления необходимо выполнить с помощью единственного метода Update() TableAdapter, который принимает типизированный набор данных, таблицу DataTable, один объект DataRow или массив DataRows. (Если вы снимите флажок "Создать инструкции insert, update и delete" в дополнительных свойствах на рис. 9, этот параметр флажка не будет действовать.) Оставим этот флажок установленным.

Измените имя метода с GetData на GetProducts.

Рис. 11. Изменение имени метода с GetData на GetProducts (щелкните для просмотра полноразмерного изображения)

Завершите работу мастера, нажав кнопку Готово. После закрытия мастера мы вернемся к Designer DataSet, на котором отображается только что созданная таблицу DataTable. Список столбцов можно просмотреть в таблицу DataTable products (ProductID, ProductName и т. д.), а также методы ProductsTableAdapter (Fill() и GetProducts()).

Таблицы Данных Products и ProductsTableAdapter добавлены в типизированный набор данных.

Рис. 12. Таблицы данных Products и ProductsTableAdapter добавлены в типизированный набор данных (щелкните для просмотра полноразмерного изображения)

На этом этапе у нас есть typed DataSet с одним DataTable (Northwind.Products) и строго типизированный класс DataAdapter (NorthwindTableAdapters.ProductsTableAdapter) с методом GetProducts( ). Эти объекты можно использовать для доступа к списку всех продуктов из кода, например:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;
products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow productRow in products)
    Response.Write("Product: " + productRow.ProductName + "<br />");

Этот код не требует написания одного бита кода для доступа к данным. Нам не нужно было создавать экземпляры классов ADO.NET, нам не нужно было ссылаться на строки подключения, SQL-запросы или хранимые процедуры. Вместо этого TableAdapter предоставляет низкоуровневый код доступа к данным.

Каждый объект, используемый в этом примере, также является строго типизированным, что позволяет Visual Studio предоставлять IntelliSense и проверку типов во время компиляции. И лучше всего таблицы DataTable, возвращаемые TableAdapter, можно привязать к веб-элементам управления ASP.NET данных, таким как GridView, DetailsView, DropDownList, CheckBoxList и некоторые другие. В следующем примере показана привязка DataTable, возвращаемая методом GetProducts(), к GridView всего за скудные три строки кода в обработчике событий Page_Load .

AllProducts.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AllProducts.aspx.cs"
    Inherits="AllProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>View All Products in a GridView</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>
            All Products</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

AllProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class AllProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource = productsAdapter.GetProducts();
        GridView1.DataBind();
    }
}

Список продуктов отображается в GridView

Рис. 13. Список продуктов отображается в GridView (щелкните для просмотра полноразмерного изображения)

Хотя в этом примере требуется написать три строки кода в обработчике событий Page_Load страницы ASP.NET, в последующих руководствах мы рассмотрим, как использовать ObjectDataSource для декларативного извлечения данных из DAL. С помощью ObjectDataSource нам не придется писать код, и мы получим поддержку разбиения по страницам и сортировки.

Шаг 3. Добавление параметризованных методов на уровень доступа к данным

На данный момент класс ProductsTableAdapter имеет только один метод GetProducts(), который возвращает все продукты в базе данных. Хотя возможность работать со всеми продуктами, безусловно, полезна, бывают случаи, когда мы хотим получить информацию о конкретном продукте или обо всех продуктах, которые относятся к определенной категории. Чтобы добавить такие функции в уровень доступа к данным, можно добавить параметризованные методы в TableAdapter.

Давайте добавим метод GetProductsByCategoryID(categoryID). Чтобы добавить новый метод в DAL, вернитесь к Designer DataSet, щелкните правой кнопкой мыши в разделе ProductsTableAdapter и выберите команду Добавить запрос.

Щелкните правой кнопкой мыши TableAdapter и выберите Добавить запрос.

Рис. 14. Right-Click в объекте TableAdapter и выберите Добавить запрос

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

Выберите для создания инструкции SELECT, которая возвращает строки

Рис. 15. Выберите для создания инструкции SELECT , которая возвращает строки (щелкните для просмотра полноразмерного изображения)

Следующим шагом является определение SQL-запроса, используемого для доступа к данным. Так как мы хотим возвращать только те продукты, которые относятся к определенной категории, я использую ту же инструкцию SELECT из GetProducts(), но добавляю следующее предложение WHERE : WHERE CategoryID = @CategoryID. Параметр @CategoryID указывает мастеру TableAdapter, что для создаваемого метода потребуется входной параметр соответствующего типа (а именно, целое число, допускающее значение NULL).

Ввод запроса для возврата только продуктов в указанной категории

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

На последнем шаге можно выбрать, какие шаблоны доступа к данным использовать, а также настроить имена созданных методов. Для шаблона Fill измените имя на FillByCategoryID , а для возвращаемого шаблона DataTable (методы GetX ) — GetProductsByCategoryID.

Выбор имен для методов TableAdapter

Рис. 17. Выбор имен для методов TableAdapter (Щелкните для просмотра полноразмерного изображения)

После завершения работы мастера Designer DataSet включает новые методы TableAdapter.

Продукты теперь можно запрашивать по категориям

Рис. 18. Продукты теперь можно запрашивать по категориям

Уделите немного времени, чтобы добавить метод GetProductByProductID(productID) с помощью того же метода.

Эти параметризованные запросы можно тестировать непосредственно из Designer DataSet. Щелкните правой кнопкой мыши метод в TableAdapter и выберите Просмотр данных. Затем введите значения для параметров и нажмите кнопку Предварительный просмотр.

Показаны продукты, относящиеся к категории

Рис. 19. Показаны продукты, относящиеся к категории "Напитки" (щелкните для просмотра полноразмерного изображения)

С помощью метода GetProductsByCategoryID(categoryID) в DAL теперь можно создать ASP.NET страницу, на которой отображаются только те продукты в указанной категории. В следующем примере показаны все продукты, которые находятся в категории Напитки с идентификатором CategoryID 1.

Beverages.asp

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Beverages.aspx.cs"
    Inherits="Beverages" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>Beverages</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Beverages.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class Beverages : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource =
          productsAdapter.GetProductsByCategoryID(1);
        GridView1.DataBind();
    }
}

Эти продукты в категории

Рис. 20. Эти продукты в категории "Напитки" отображаются (щелкните для просмотра полноразмерного изображения)

Шаг 4. Вставка, обновление и удаление данных

Существует два шаблона, которые обычно используются для вставки, обновления и удаления данных. Первый шаблон, который я назову прямым шаблоном базы данных, включает в себя создание методов, которые при вызове выполняют команду INSERT, UPDATE или DELETE в базе данных, которая работает с одной записью базы данных. Такие методы обычно передаются в последовательность скалярных значений (целые числа, строки, логические значения, dateTimes и т. д.), которые соответствуют значениям для вставки, обновления или удаления. Например, при использовании этого шаблона для таблицы Products метод delete принимает целочисленный параметр, указывающий ProductID удаляемой записи, в то время как метод insert принимает строку для ProductName, десятичный разделитель для UnitPrice, целое число для UnitsOnStock и т. д.

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

Рис. 21. Каждый запрос на вставку, обновление и удаление отправляется в базу данных немедленно (щелкните для просмотра полноразмерного изображения)

Другой шаблон, который я буду называть шаблоном пакетного обновления, заключается в обновлении всего Набора данных, DataTable или коллекции DataRows в одном вызове метода. С помощью этого шаблона разработчик удаляет, вставляет и изменяет DataRows в DataTable, а затем передает эти данные DataRows или DataTable в метод обновления. Затем этот метод перечисляет переданные объекты DataRow, определяет, были ли они изменены, добавлены или удалены (с помощью значения свойства RowState DataRow), и выдает соответствующий запрос базы данных для каждой записи.

Все изменения синхронизируются с базой данных при вызове метода update.

Рис. 22. Все изменения синхронизированы с базой данных при вызове метода обновления (щелкните для просмотра полноразмерного изображения)

TableAdapter использует шаблон пакетного обновления по умолчанию, но также поддерживает прямой шаблон базы данных. Так как мы выбрали параметр "Создать инструкции Insert, Update и Delete" в разделе "Дополнительные свойства" при создании объекта TableAdapter, ProductsTableAdapter содержит метод Update(), который реализует шаблон пакетного обновления. В частности, TableAdapter содержит метод Update(), который может быть передан типизированному набору данных, строго типизированной таблице DataTable или одному или нескольким dataRows. Если установить флажок GenerateDBDirectMethods при первом создании TableAdapter, прямой шаблон базы данных также будет реализован с помощью методов Insert(), Update() и Delete( ).

Оба шаблона изменения данных используют свойства InsertCommand, UpdateCommand и DeleteCommand TableAdapter для выдачи своих команд INSERT, UPDATE и DELETE в базе данных. Вы можете проверить и изменить свойства InsertCommand, UpdateCommand и DeleteCommand, щелкнув TableAdapter в Designer DataSet и перейдя к окно свойств. (Убедитесь, что выбран объект TableAdapter и объект ProductsTableAdapter выбран в раскрывающемся списке в окно свойств.)

TableAdapter имеет свойства InsertCommand, UpdateCommand и DeleteCommand

Рис. 23. TableAdapter имеет свойства InsertCommand, UpdateCommand и DeleteCommand (Щелкните для просмотра полноразмерного изображения)

Чтобы проверить или изменить любое из этих свойств команд базы данных, щелкните вложенное свойство CommandText , чтобы открыть построитель запросов.

Настройка инструкций INSERT, UPDATE и DELETE в построителе запросов

Рис. 24. Настройка инструкций INSERT, UPDATE и DELETE в построителе запросов (щелкните для просмотра полноразмерного изображения)

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

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
  new NorthwindTableAdapters.ProductsTableAdapter();
// For each product, double its price if it is not discontinued and
// there are 25 items in stock or less
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
   if (!product.Discontinued && product.UnitsInStock <= 25)
      product.UnitPrice *= 2;
// Update the products
productsAdapter.Update(products);

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

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Delete the product with ProductID 3
productsAdapter.Delete(3);
// Update Chai (ProductID of 1), setting the UnitsOnOrder to 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags",
  18.0m, 39, 15, 10, false, 1);
// Add a new product
productsAdapter.Insert("New Product", 1, 1,
  "12 tins per carton", 14.95m, 15, 0, 10, false);

Создание пользовательских методов вставки, обновления и удаления

Методы Insert(), Update() и Delete(), созданные прямым методом базы данных, могут быть немного громоздкими, особенно для таблиц с большим количеством столбцов. Если взглянуть на предыдущий пример кода, то без помощи IntelliSense не совсем ясно, какой столбец таблицы Products сопоставляется с каждым входным параметром с методами Update() и Insert(). Иногда может потребоваться обновить только один или два столбца или требуется настраиваемый метод Insert(), который, возможно, возвращает значение поля IDENTITY (автоматический приращение) только что вставленной записи.

Чтобы создать такой пользовательский метод, вернитесь к Designer DataSet. Щелкните правой кнопкой мыши TableAdapter и выберите Добавить запрос, вернувшись в мастер TableAdapter. На втором экране можно указать тип создаваемого запроса. Давайте создадим метод, который добавляет новый продукт, а затем возвращает значение productID новой записи. Поэтому вы можете создать запрос INSERT .

Создание метода для добавления новой строки в таблицу Products

Рис. 25. Создание метода для добавления новой строки в таблицу Products (щелкните для просмотра полноразмерного изображения)

На следующем экране появится commandTextinsertCommand. Дополнить этот запрос, добавив SELECT SCOPE_IDENTITY() в конце запроса, который вернет последнее значение идентификатора, вставленное в столбец IDENTITY в том же область. (Дополнительные сведения о SCOPE_IDENTITY() и причинах использования SCOPE_IDENTITY() вместо @@IDENTITY см. в техническойдокументации.) Перед добавлением инструкции SELECT убедитесь, что инструкция INSERT заканчивается точкой с запятой.

Расширение запроса для возврата значения SCOPE_IDENTITY()

Рис. 26. Расширение запроса для возврата значения SCOPE_IDENTITY() (щелкните для просмотра полноразмерного изображения)

Наконец, присвойте новому методу имя InsertProduct.

Задайте для нового имени метода значение InsertProduct.

Рис. 27. Присвойте новому имени метода значение InsertProduct (щелкните для просмотра полноразмерного изображения)

Когда вы вернетесь к Designer DataSet вы увидите, что ProductsTableAdapter содержит новый метод InsertProduct. Если этот новый метод не имеет параметра для каждого столбца в таблице Products , скорее всего, вы забыли завершить инструкцию INSERT точкой с запятой. Настройте метод InsertProduct и убедитесь, что у вас есть точка с запятой, разделяющая инструкции INSERT и SELECT .

По умолчанию методы insert выдают методы, не относящиеся к запросам, то есть возвращают количество затронутых строк. Однако мы хотим, чтобы метод InsertProduct возвращал значение, возвращаемое запросом, а не количество затронутых строк. Для этого измените свойство ExecuteMode метода InsertProduct на Scalar.

Измените свойство ExecuteMode на Scalar

Рис. 28. Изменение свойства ExecuteMode на Скалярное (щелкните для просмотра полноразмерного изображения)

В следующем коде показан новый метод InsertProduct в действии:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Add a new product
int new_productID = Convert.ToInt32(productsAdapter.InsertProduct
    ("New Product", 1, 1, "12 tins per carton", 14.95m, 10, 0, 10, false));
// On second thought, delete the product
productsAdapter.Delete(new_productID);

Шаг 5. Завершение уровня доступа к данным

Обратите внимание, что класс ProductsTableAdapters возвращает значения CategoryID и SupplierID из таблицы Products , но не включает столбец CategoryName из таблицы Categories или Столбец CompanyName из таблицы Поставщики , хотя, скорее всего, это столбцы, которые нужно отобразить при отображении сведений о продукте. Мы можем дополнить исходный метод TableAdapter GetProducts() для включения значений столбцов CategoryName и CompanyName , что приведет к обновлению строго типизированной таблицы DataTable для включения этих новых столбцов.

Однако это может представлять проблему, так как методы TableAdapter для вставки, обновления и удаления данных основаны на этом исходном методе. К счастью, автоматически созданные методы вставки, обновления и удаления не затрагиваются вложенными запросами в предложении SELECT . Заботясь о добавлении запросов в категории и поставщики в качестве вложенных запросов, а не JOIN , мы избежим необходимости переделать эти методы для изменения данных. Щелкните правой кнопкой мыши метод GetProducts() в ProductsTableAdapter и выберите Настроить. Затем измените предложение SELECT так, чтобы оно выглядело следующим образом:

SELECT     ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM         Products

Обновление инструкции SELECT для метода GetProducts()

Рис. 29. Обновление инструкции SELECT для метода GetProducts() (Щелкните для просмотра полноразмерного изображения)

После обновления метода GetProducts() для использования этого нового запроса DataTable будет включать два новых столбца: CategoryName и SupplierName.

В таблицы данных Products есть два новых столбца

Рис. 30. В таблицы данных Products есть два новых столбца

Уделите некоторое время обновлению предложения SELECT в методе GetProductsByCategoryID(categoryID).

При обновлении GetProducts()SELECT с помощью синтаксиса JOIN Designer DataSet не сможет автоматически создать методы вставки, обновления и удаления данных базы данных с помощью прямого шаблона базы данных. Вместо этого вам придется вручную создавать их так же, как это было с методом InsertProduct ранее в этом руководстве. Кроме того, необходимо вручную указать значения свойств InsertCommand, UpdateCommand и DeleteCommand , если вы хотите использовать шаблон пакетного обновления.

Добавление оставшихся адаптеров tableadapters

До сих пор мы рассмотрели только работу с одним адаптером TableAdapter для одной таблицы базы данных. Однако база данных Northwind содержит несколько связанных таблиц, с которыми необходимо работать в нашем веб-приложении. Типизированный набор данных может содержать несколько связанных данных. Поэтому для выполнения DAL необходимо добавить таблицы DataTable для других таблиц, которые мы будем использовать в этих руководствах. Чтобы добавить новый TableAdapter в типизированный набор данных, откройте Designer DataSet, щелкните правой кнопкой мыши Designer и выберите Добавить / TableAdapter. При этом будут созданы новые таблицы DataTable и TableAdapter, а также приведены инструкции по работе с мастером, который мы рассмотрели ранее в этом руководстве.

Уделите несколько минут, чтобы создать следующие адаптеры и методы TableAdapters с помощью следующих запросов. Обратите внимание, что запросы в ProductsTableAdapter включают вложенные запросы для получения названий категорий и поставщиков каждого продукта. Кроме того, если вы следили за этим, вы уже добавили методы GetProducts() и GetProductsByCategoryID(categoryID) класса ProductsTableAdapter.

  • ProductsTableAdapter

    • GetProducts:

      SELECT     ProductID, ProductName, SupplierID, 
      CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, 
      UnitsOnOrder, ReorderLevel, Discontinued, 
      (SELECT CategoryName FROM Categories WHERE
      Categories.CategoryID = Products.CategoryID) as 
      CategoryName, (SELECT CompanyName FROM Suppliers
      WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      
    • GetProductsByCategoryID:

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName,
      (SELECT CompanyName FROM Suppliers WHERE
      Suppliers.SupplierID = Products.SupplierID)
      as SupplierName
      FROM         Products
      WHERE      CategoryID = @CategoryID
      
    • GetProductsBySupplierID:

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE 
      Suppliers.SupplierID = Products.SupplierID) as SupplierName
      FROM         Products
      WHERE SupplierID = @SupplierID
      
    • GetProductByProductID:

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName 
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      WHERE ProductID = @ProductID
      
  • CategoriesTableAdapter

    • GetCategories:

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      
    • GetCategoryByCategoryID:

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      WHERE CategoryID = @CategoryID
      
  • SuppliersTableAdapter

    • GetSuppliers:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      
    • GetSuppliersByCountry:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE Country = @Country
      
    • GetSupplierBySupplierID:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE SupplierID = @SupplierID
      
  • EmployeesTableAdapter

    • GetEmployees:

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      
    • GetEmployeesByManager:

      SELECT     EmployeeID, LastName, FirstName, Title, 
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE ReportsTo = @ManagerID
      
    • GetEmployeeByEmployeeID:

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE EmployeeID = @EmployeeID
      

Набор данных Designer после добавления четырех адаптеров таблиц

Рис. 31. Набор данных Designer после добавления четырех адаптеров таблиц (щелкните для просмотра полноразмерного изображения)

Добавление пользовательского кода в DAL

Классы TableAdapters и DataTables, добавленные в typed DataSet, выражаются в виде файла определения схемы XML (Northwind.xsd). Чтобы просмотреть эти сведения о схеме, щелкните правой кнопкой мыши файл Northwind.xsd в Обозреватель решений и выберите Просмотреть код.

Файл определения схемы XML (XSD) для типизированного набора данных Northwinds

Рис. 32. Файл определения схемы XML (XSD) для набора типизированных данных Northwinds (щелкните для просмотра полноразмерного изображения)

Эти сведения о схеме преобразуются в код C# или Visual Basic во время разработки при компиляции или во время выполнения (при необходимости), после чего их можно выполнить с помощью отладчика. Чтобы просмотреть этот автоматически созданный код, перейдите в представление классов и выполните детализацию до классов TableAdapter или Typed DataSet. Если представление классов не отображается на экране, перейдите в меню Вид и выберите его или нажмите клавиши CTRL+SHIFT+C. В представлении классов можно просмотреть свойства, методы и события классов Typed DataSet и TableAdapter. Чтобы просмотреть код для определенного метода, дважды щелкните имя метода в представлении классов или щелкните его правой кнопкой мыши и выберите перейти к определению.

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

Рис. 33. Проверка автоматически созданного кода путем выбора перехода к определению в представлении классов

Хотя автоматически созданный код может значительно сэкономить время, код часто является универсальным и его необходимо настроить в соответствии с уникальными потребностями приложения. Однако риск расширения автоматически созданного кода заключается в том, что средство, создающее код, может решить, что пришло время повторно создать и перезаписать настройки. Благодаря новой концепции разделяемого класса .NET 2.0 можно легко разделить класс на несколько файлов. Это позволяет добавлять собственные методы, свойства и события в автоматически создаваемые классы, не беспокоясь о перезаписи настроек в Visual Studio.

Чтобы продемонстрировать, как настроить DAL, давайте добавим метод GetProducts() в класс SuppliersRow . Класс SuppliersRow представляет одну запись в таблице Поставщики ; Каждый поставщик может предоставить ноль для многих продуктов, поэтому GetProducts() вернет эти продукты указанного поставщика. Для этого создайте файл класса в папке App_Codeс именем SuppliersRow.cs и добавьте следующий код:

using System;
using System.Data;
using NorthwindTableAdapters;
public partial class Northwind
{
    public partial class SuppliersRow
    {
        public Northwind.ProductsDataTable GetProducts()
        {
            ProductsTableAdapter productsAdapter =
             new ProductsTableAdapter();
            return
              productsAdapter.GetProductsBySupplierID(this.SupplierID);
        }
    }
}

Этот разделяемый класс указывает компилятору, что при создании класса Northwind.SuppliersRow необходимо включить только что определенный метод GetProducts(). Если вы выполните сборку проекта, а затем вернетесь в представление классов, вы увидите GetProducts() в списке как метод Northwind.SuppliersRow.

Метод GetProducts() теперь является частью класса Northwind.SuppliersRow

Рис. 34. Метод GetProducts() теперь является частью класса Northwind.SuppliersRow

Метод GetProducts() теперь можно использовать для перечисления набора продуктов для конкретного поставщика, как показано в следующем коде:

NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter =
    new NorthwindTableAdapters.SuppliersTableAdapter();
// Get all of the suppliers
Northwind.SuppliersDataTable suppliers =
  suppliersAdapter.GetSuppliers();
// Enumerate the suppliers
foreach (Northwind.SuppliersRow supplier in suppliers)
{
    Response.Write("Supplier: " + supplier.CompanyName);
    Response.Write("<ul>");
    // List the products for this supplier
    Northwind.ProductsDataTable products = supplier.GetProducts();
    foreach (Northwind.ProductsRow product in products)
        Response.Write("<li>" + product.ProductName + "</li>");
    Response.Write("</ul><p> </p>");
}

Эти данные также можно отобразить в любом из ASP. Веб-элементы управления данными NET. На следующей странице используется элемент управления GridView с двумя полями:

  • BoundField, отображающий имена каждого поставщика;
  • TemplateField, содержащий элемент управления BulletedList, привязанный к результатам, возвращаемым методом GetProducts() для каждого поставщика.

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

SuppliersAndProducts.aspx

<%@ Page Language="C#" CodeFile="SuppliersAndProducts.aspx.cs"
    AutoEventWireup="true" Inherits="SuppliersAndProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>
            Suppliers and Their Products</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             AutoGenerateColumns="False"
             CssClass="DataWebControlStyle">
                <HeaderStyle CssClass="HeaderStyle" />
                <AlternatingRowStyle CssClass="AlternatingRowStyle" />
                <Columns>
                    <asp:BoundField DataField="CompanyName"
                      HeaderText="Supplier" />
                    <asp:TemplateField HeaderText="Products">
                        <ItemTemplate>
                            <asp:BulletedList ID="BulletedList1"
                             runat="server" DataSource="<%# ((Northwind.SuppliersRow) ((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>"
                                 DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

SuppliersAndProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class SuppliersAndProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SuppliersTableAdapter suppliersAdapter = new
          SuppliersTableAdapter();
        GridView1.DataSource = suppliersAdapter.GetSuppliers();
        GridView1.DataBind();
    }
}

Название компании поставщика указано в левом столбце, а его продукты — справа.

Рис. 35. Название компании поставщика указано в левом столбце, а их продукты — справа (щелкните, чтобы просмотреть изображение в полном размере)

Сводка

При создании веб-приложения создание DAL должно быть одним из первых шагов, прежде чем приступить к созданию уровня презентации. В Visual Studio создание DAL на основе типизированных наборов данных — это задача, которую можно выполнить за 10–15 минут без написания строки кода. Учебники будут основываться на этом DAL. В следующем руководстве мы определим ряд бизнес-правил и посмотрим, как реализовать их в отдельном уровне бизнес-логики.

Счастливого программирования!

Дополнительные материалы

Дополнительные сведения о темах, рассмотренных в этом руководстве, см. в следующих ресурсах:

Видеоурок по темам, содержащимся в этом руководстве

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Особая благодарность

Эта серия учебников была рассмотрена многими полезными рецензентами. Ведущие рецензенты этого руководства: Рон Грин, Хилтон Гисеноу, Деннис Паттерсон, Лиз Шулок, Абель Гомес и Карлос Сантос. Хотите просмотреть предстоящие статьи MSDN? Если да, опустите мне строку на mitchell@4GuysFromRolla.com.