Поделиться через


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

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

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

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

Введение

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

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

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

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

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

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

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

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

После установки базы данных перейдите к Обозреватель сервера в Visual Studio, щелкните правой кнопкой мыши узел 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.Rows(index).columnName.

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

Примечание

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

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

Весь код доступа к данным переключен на DAL

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

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

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

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

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

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

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

Имейте в виду, что строго типизированные таблицы DataTable не содержат никаких сведений о том, как получить доступ к данным из базовой таблицы базы данных. Чтобы получить данные для заполнения DataTable, мы используем класс TableAdapter, который функционирует как уровень доступа к данным. Для таблицы Products DataTable 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. Список столбцов можно просмотреть в Products DataTable (ProductID, ProductNameи т. д.), а также методы ProductsTableAdapter (Fill() и GetProducts()).

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

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

На этом этапе у нас есть typed DataSet с одним 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 событий.

AllProducts.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>

AllProducts.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, вернитесь к 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. Ввод запроса для возврата только продуктов в указанной категории (щелкните, чтобы просмотреть полноразмерное изображение)

На последнем шаге можно выбрать, какие шаблоны доступа к данным использовать, а также настроить имена созданных методов. Для шаблона заливки давайте изменим имя на 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.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>

Beverages.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 в базе данных, которая работает с одной записью базы данных. Такие методы обычно передаются в последовательность скалярных значений (целые числа, строки, логические значения, dateTimes и т. д.), которые соответствуют значениям для вставки, обновления или удаления. Например, при использовании этого шаблона для Products таблицы метод delete принимает целочисленный параметр, указывающий на запись, удаляемую ProductID , в то время как метод insert принимает строку для ProductName, десятичный разделитель для UnitPrice, целое число для UnitsOnStockи т. д.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Рис. 27. Задайте для параметра New Method Name (InsertProductЩелкните для просмотра полноразмерного изображения)

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

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

Измените свойство ExecuteMode на 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 , что также обновит строго типизированную таблицу DataTable, чтобы включить эти новые столбцы.

Однако это может представлять проблему, так как методы 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 для метода GetProducts()

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

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

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

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

Уделите немного времени, чтобы обновить SELECT предложение в методе GetProductsByCategoryID(categoryID) .

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

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

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

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

  • 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, добавленные в типизированный набор данных, выражаются в виде файла определения схемы 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 представляет одну запись в 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

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

Рис. 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, содержащий элемент управления BulletedList, привязанный к результатам, возвращаемым методом GetProducts() для каждого поставщика.

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

SuppliersAndProducts.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>

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

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

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

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

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

Об авторе

Скотт Митчелл (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.