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

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

Скачивание примера приложения или Загрузка PDF-файла

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

Введение

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

В этих руководствах мы будем использовать версию базы данных Northwind Microsoft SQL Server 2005 Express Edition, размещенную в каталоге 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, в раскрывающемся списке Расположение выберите Файловая система, укажите папку для размещения веб-сайта и задайте для параметра Language значение Visual Basic.

создать новый веб-сайт на основе файловой системы

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

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

После создания веб-сайта необходимо добавить ссылку на базу данных в обозреватель сервера 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, экспресс-выпуск, вы увидите узел с именем НОРСВНД. MDF в обозреватель сервера, который можно развернуть и исследовать его таблицы, представления, хранимые процедуры и т. д. (см. рис. 2).

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

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

Кроме того, вы можете подключиться к базе данных Northwind, установленной на сервере базы данных. Если на сервере базы данных еще нет базы данных «Борей», сначала необходимо добавить ее к серверу базы данных, запустив сценарий установки, включенный в загрузку этого руководства, или загрузить версию SQL Server 2000 для Northwind и сценарий установки непосредственно с веб-сайта корпорации Майкрософт.

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

Добавление подключения к базе данных 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), возвращающий сведения о конкретном продукте

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

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

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

Note

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

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

все коды доступа к данным переведен DAL

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

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

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

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

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

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

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

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

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

выберите базу данных Northwind из раскрывающегося списка.

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

После выбора базы данных и нажатия кнопки "Далее" вам будет предложено сохранить строку подключения в Web.config файле. Сохранив строку подключения, вы не сможете жестко кодировать ее в классах TableAdapter, что упрощает работу при изменении данных строки подключения в будущем. Если вы хотите сохранить строку подключения в файле конфигурации, она помещается в раздел <connectionStrings>, который можно дополнительно зашифровать для повышения безопасности или изменить позже с помощью новой страницы свойств ASP.NET 2,0 в средстве администрирования графического ПОЛЬЗОВАТЕЛЬСКОГО интерфейса IIS, что является более идеальным для администраторов.

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

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

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

Чтобы приступить к определению запроса SQL, необходимо сначала указать, как TableAdapter должен выдавать запрос. Можно использовать специальный оператор SQL, создать новую хранимую процедуру или использовать существующую хранимую процедуру. В этих руководствах мы будем использовать специальные инструкции SQL. Пример использования хранимых процедур см. в статье Брайан нойз. Создайте слой доступа к данным с помощью конструктора наборов данных Visual Studio 2005 .

запроса данных с помощью специального оператора SQL

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

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

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

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

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

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

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

После создания запроса, но до перехода на следующий экран нажмите кнопку Дополнительные параметры. В проектах веб-сайтов «Создание инструкций INSERT, Update и Delete» является единственным дополнительным параметром, выбранным по умолчанию. Если запустить этот мастер из библиотеки классов или проекта Windows, будет также выбран параметр "использовать оптимистичный параллелизм". Снимите флажок "использовать оптимистичный параллелизм" сейчас. В следующих учебных курсах мы рассмотрим оптимистичный параллелизм.

выбрать только параметр создать инструкции INSERT, Update и DELETE

Рис. 10. Выбор только параметра создать инструкции INSERT, Update и Delete (щелкните, чтобы просмотреть изображение с полным размером)

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

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

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

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

изменить имя метода с GetData на Products

Рис. 11. изменение имени метода с GetData на GetProducts (щелкните, чтобы просмотреть изображение с полным размером)

Завершите работу мастера, нажав кнопку Готово. После завершения работы мастера мы возвращаемся в конструктор наборов данных, который показывает только что созданный объект DataTable. Можно просмотреть список столбцов в Products DataTable (ProductID, ProductNameи т. д.), а также методы ProductsTableAdapter (Fill() и GetProducts()).

таблицы Products и Продуктстаблеадаптер были добавлены в типизированный набор данных

Рис. 12. Products datatable и ProductsTableAdapter были добавлены в типизированный набор данных (щелкните, чтобы просмотреть изображение с полным размером)

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

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products as Northwind.ProductsDataTable
products = productsAdapter.GetProducts()
For Each productRow As Northwind.ProductsRow In products
    Response.Write("Product: " & productRow.ProductName & "<br />")
Next

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

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

Аллпродуктс. aspx

<%@ Page Language="VB" AutoEventWireup="true" CodeFile="AllProducts.aspx.vb"
    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>
        <h1>
            All Products</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Аллпродуктс. aspx. vb

Imports NorthwindTableAdapters
Partial Class AllProducts
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim productsAdapter As New ProductsTableAdapter
        GridView1.DataSource = productsAdapter.GetProducts()
        GridView1.DataBind()
    End Sub
End Class

список продуктов отображается в элементе управления GridView

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

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

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

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

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

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

Рис. 14. Щелкните правой кнопкой мыши 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 (щелкните, чтобы просмотреть изображение с полным размером)

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

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

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

Потратьте время на Добавление метода GetProductByProductID(productID), используя тот же метод.

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

Отображаются продукты, относящиеся к категории «напитки»

Рис. 19. показаны продукты, принадлежащие категории «напитки» (щелкните, чтобы просмотреть изображение с полным размером).

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

Напитки. aspx

<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Beverages.aspx.vb"
    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>
        <h1>Beverages</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Напитки. aspx. vb

Imports NorthwindTableAdapters
Partial Class Beverages
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim productsAdapter As New ProductsTableAdapter
        GridView1.DataSource =
         productsAdapter.GetProductsByCategoryID(1)
        GridView1.DataBind()
    End Sub
End Class

эти продукты отображаются в категории «напитки»

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

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

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

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

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

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

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

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

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

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

TableAdapter содержит свойства InsertCommand, UpdateCommand и DeleteCommand.

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

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

настроить инструкции INSERT, UPDATE и DELETE в конструктор запросов

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

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

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products As Northwind.ProductsDataTable = productsAdapter.GetProducts()
For Each product As Northwind.ProductsRow In products
   If Not product.Discontinued AndAlso product.UnitsInStock <= 25 Then
      product.UnitPrice *= 2
   End if
Next
productsAdapter.Update(products)

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

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
productsAdapter.Delete(3)
productsAdapter.Update( _
    "Chai", 1, 1, "10 boxes x 20 bags", 18.0, 39, 15, 10, false, 1)
productsAdapter.Insert( _
    "New Product", 1, 1, "12 tins per carton", 14.95, 15, 0, 10, false)

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

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

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

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

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

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

дополнить запрос, чтобы возвращалось значение SCOPE_IDENTITY ()

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

Наконец, назовите новый метод InsertProduct.

задайте для нового имени метода значение Инсертпродукт.

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

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

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

изменить свойство Ексекутемоде на Scalar

Рис. 28. изменение свойства ExecuteMode на Scalar (щелкните, чтобы просмотреть изображение с полным размером)

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

Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim new_productID As Integer = Convert.ToInt32(productsAdapter.InsertProduct( _
    "New Product", 1, 1, "12 tins per carton", 14.95, 10, 0, 10, false))
productsAdapter.Delete(new_productID)

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

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

Однако это может представлять проблему, так как методы TableAdapter для вставки, обновления и удаления данных основаны на этом начальном методе. К счастью, автоматически создаваемые методы для вставки, обновления и удаления не затрагиваются вложенными запросами в предложении SELECT. Добавляя запросы к Categories и Suppliers в качестве вложенных запросов, а не 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 для метода Products ()

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

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

Таблица данных Products содержит два новых столбца

Рис. 30. Products DataTable содержит два новых столбца

Потратьте время на обновление предложения SELECT в методе GetProductsByCategoryID(categoryID).

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

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

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

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

  • продуктстаблеадаптер

    • Продукты:

      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
      
    • Жетпродуктсбисупплиерид:

      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
      
    • Жетпродуктбипродуктид:

      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
      
  • категориестаблеадаптер

    • Типы категорий:

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      
    • Жеткатегорибикатегорид:

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      WHERE CategoryID = @CategoryID
      
  • супплиерстаблеадаптер

    • Поставщики:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      
    • Жетсупплиерсбикаунтри:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE Country = @Country
      
    • Жетсупплиербисупплиерид:

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE SupplierID = @SupplierID
      
  • емплойистаблеадаптер

    • Сотрудники:

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      
    • Жетемплойисбиманажер:

      SELECT     EmployeeID, LastName, FirstName, Title, 
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE ReportsTo = @ManagerID
      
    • Жетемплойибемплойиид:

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

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

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

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

TableAdapter и DataTables, добавленные в типизированный набор данных, выражаются как файл определения схемы XML (Northwind.xsd). Эту информацию о схеме можно просмотреть, щелкнув правой кнопкой мыши файл Northwind.xsd в обозреватель решений и выбрав пункт Просмотреть код.

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

Рис. 32. XML-файл определения схемы (XSD) для типизированного набора данных Northwind (щелкните, чтобы просмотреть изображение с полным размером)

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

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

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

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

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

Imports NorthwindTableAdapters
Partial Public Class Northwind
    Partial Public Class SuppliersRow
        Public Function GetProducts() As Northwind.ProductsDataTable
            Dim productsAdapter As New ProductsTableAdapter
            Return productsAdapter.GetProductsBySupplierID(Me.SupplierID)
        End Function
    End Class
End Class

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

Метод Products () теперь является частью класса Northwind. Супплиерсров

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

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

Dim suppliersAdapter As New NorthwindTableAdapters.SuppliersTableAdapter()
Dim suppliers As Northwind.SuppliersDataTable = suppliersAdapter.GetSuppliers()
For Each supplier As Northwind.SuppliersRow In suppliers
    Response.Write("Supplier: " & supplier.CompanyName)
    Response.Write("<ul>")
    Dim products As Northwind.ProductsDataTable = supplier.GetProducts()
    For Each product As Northwind.ProductsRow In products
        Response.Write("<li>" & product.ProductName & "</li>")
    Next
    Response.Write("</ul><p> </p>")
Next

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

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

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

Супплиерсандпродуктс. aspx

<%@ Page Language="VB" CodeFile="SuppliersAndProducts.aspx.vb"
    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>
        <h1>
            Suppliers and Their Products</h1>
        <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="<%# CType(CType(Container.DataItem, System.Data.DataRowView).Row, Northwind.SuppliersRow).GetProducts() %>"
                                 DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Супплиерсандпродуктс. aspx. vb

Imports NorthwindTableAdapters
Partial Class SuppliersAndProducts
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim suppliersAdapter As New SuppliersTableAdapter
        GridView1.DataSource = suppliersAdapter.GetSuppliers()
        GridView1.DataBind()
    End Sub
End Class

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

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

Сводка

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

Поздравляем с программированием!

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

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

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

Об авторе

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

Специальная благодарность

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