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


Реализация оптимистического параллелизма с SqlDataSource (VB)

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

Скачивание PDF

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

Введение

В предыдущем руководстве мы рассмотрели, как добавить возможности вставки, обновления и удаления возможностей в элемент управления SqlDataSource. Короче говоря, для предоставления этих функций необходимо указать соответствующую инструкцию SQL в свойствах элемента управления INSERT, UPDATE или DELETE, а также соответствующие параметры в коллекциях InsertParameters, UpdateParameters и DeleteParameters. Хотя эти свойства и коллекции можно указать вручную, кнопка "Дополнительно" мастера настройки источника данных предлагает флажок "Создать операторы INSERT, UPDATE, и DELETE", который автоматически создаст эти инструкции на основе инструкции SELECT.

Наряду с флажком "Генерация INSERT, UPDATE, и DELETE инструкций", диалоговое окно "Расширенные параметры создания SQL-запросов" включает параметр "Использовать оптимистическую конкурентность" (см. рис. 1). При проверке, условия в автоматически созданных инструкциях UPDATE и DELETE изменяются таким образом, чтобы выполнять обновление или удаление только если исходные данные базы данных не были изменены с тех пор, как пользователь последний раз загружал данные в таблицу.

Поддержку оптимистического параллелизма можно добавить в диалоговом окне

Рис. 1. Вы можете добавить поддержку оптимистического параллелизма из диалогового окна "Расширенные параметры создания SQL"

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

Краткое описание оптимистической согласованности

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

Представьте, что два пользователя, Jisun и Sam, посетили страницу в приложении, которое позволило посетителям обновлять и удалять продукты с помощью элемента управления GridView. Оба одновременно нажимают кнопку "Изменить" для Chai. Jisun изменяет имя продукта на Chai Tea и нажимает кнопку "Обновить". Чистый UPDATE результат — это инструкция, которая отправляется в базу данных, которая задает все обновляемые поля продукта (несмотря на то, что Jisun обновил только одно поле, ProductName). На данный момент времени база данных имеет значения Chai Tea, категория "Напитки", поставщик экзотических жидкости и т. д. для этого конкретного продукта. Тем не менее, GridView на экране Sam по-прежнему отображает имя продукта в редактируемой строке GridView как Chai. Через несколько секунд после фиксации изменений Jisun Сэм обновляет категорию на Condiments и нажимает кнопку "Обновить". Это приводит к отправке UPDATE инструкции в базу данных, которая задает имя продукта Chai, CategoryID соответствующий идентификатор категории Приправы и т. д. Изменения в имени продукта, внесённые Джисуном, были перезаписаны.

Рисунок 2 иллюстрирует это взаимодействие.

При одновременном обновлении записи двумя пользователями существует вероятность того, что изменения одного пользователя могут перезаписать изменения другого.

Рис. 2. Когда два пользователя одновременно обновляют запись, есть вероятность того, что изменения одного пользователя перезапишут изменения другого (нажмите, чтобы просмотреть изображение полного размера)

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

Замечание

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

Управление оптимистичным параллелизмом работает, проверяя, что обновляемая или удаляемая запись сохраняет те же значения, что и в момент старта процесса обновления или удаления. Например, при нажатии кнопки "Изменить" в редактируемом GridView значения записи считываются из базы данных и отображаются в TextBoxes и других веб-элементах управления. Эти исходные значения сохраняются в GridView. Позже, после того как пользователь внесет изменения и нажмет кнопку UPDATE "Обновить", используемая инструкция должна учитывать как исходные, так и новые значения и обновлять запись в базе данных только в том случае, если исходные значения, которые пользователь начал редактировать, остаются идентичными значениям, которые все еще записаны в базе данных. На рисунке 3 показана эта последовательность событий.

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

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

Существуют различные подходы к реализации оптимистического параллелизма (см. Питер А. Бромбергоптимистическая логика обновления параллельного выполнения для краткого знакомства с рядом вариантов). Метод, используемый SqlDataSource (а также типизированными наборами данных ADO.NET, используемыми в нашем уровне доступа к данным), расширяет WHERE предложение, чтобы включить сравнение всех исходных значений. Например, следующая UPDATE инструкция обновляет имя и цену продукта, только если текущие значения базы данных равны значениям, которые изначально были получены при обновлении записи в GridView. @ProductName Параметры @UnitPrice содержат новые значения, введенные пользователем, в то время как @original_ProductName@original_UnitPrice они содержат значения, которые изначально загружались в GridView при нажатии кнопки "Изменить":

UPDATE Products SET
    ProductName = @ProductName,
    UnitPrice = @UnitPrice
WHERE
    ProductID = @original_ProductID AND
    ProductName = @original_ProductName AND
    UnitPrice = @original_UnitPrice

Как мы увидим в этом руководстве, включение управления оптимистичным параллелизмом с помощью SqlDataSource — это всего лишь вопрос установки флажка.

Шаг 1. Создание SqlDataSource, поддерживающего оптимистичную параллельность.

Начните с открытия OptimisticConcurrency.aspx страницы из SqlDataSource папки. Перетащите элемент управления SqlDataSource из панели элементов в конструктор и установите его свойство ID в ProductsDataSourceWithOptimisticConcurrency. Затем щелкните ссылку "Настроить источник данных" из смарт-тега элемента управления. На первом экране мастера выберите работу с NORTHWINDConnectionString, а затем нажмите "Далее".

Выберите работу с NORTHWINDConnectionString

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

В этом примере мы добавим GridView, который позволяет пользователям изменять таблицу Products . Поэтому на экране "Настройка инструкции выбора" выберите таблицу Products из раскрывающегося списка и выберите столбцы ProductID, ProductName, UnitPrice и Discontinued, как показано на рис. 5.

Выберите из таблицы

Рис. 5. Из Products таблицы верните столбцы ProductID, ProductName, UnitPrice и Discontinued (щелкните, чтобы просмотреть изображение полного размера)

После выбора столбцов нажмите кнопку "Дополнительно", чтобы открыть диалоговое окно "Дополнительные параметры создания SQL". Проверьте команды "Создать INSERT", "UPDATE" и "DELETE", установите флажки "Использовать оптимистическую конкуренцию" и нажмите "ОК" (см. рисунок 1 для снимка экрана). Завершите работу мастера, нажав кнопку "Далее", а затем "Готово".

После завершения работы мастера настройки источника данных, ознакомьтесь с результирующими свойствами DeleteCommand и UpdateCommand, а также коллекциями DeleteParameters и UpdateParameters. Самый простой способ сделать это — щелкнуть вкладку "Источник" в левом нижнем углу, чтобы увидеть декларативный синтаксис страницы. В этом месте вы найдете значение UpdateCommand:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

В коллекции UpdateParameters семь параметров.

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
      ...
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
    ...
</asp:SqlDataSource>

Аналогичным образом DeleteCommand свойство и DeleteParameters коллекция должны выглядеть следующим образом:

DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        ...
    </UpdateParameters>
    ...
</asp:SqlDataSource>

Помимо расширения положений в свойствах WHERE и UpdateCommand (и добавления дополнительных параметров в соответствующие коллекции параметров), выбор параметра "Использовать оптимистичную согласованность" настраивает два других свойства:

Когда веб-элемент управления данными вызывает sqlDataSource s Update() или Delete() метод, он передает исходные значения. Если для свойства SqlDataSource ConflictDetection задано CompareAllValuesзначение, эти исходные значения добавляются в команду. Свойство OldValuesParameterFormatString предоставляет шаблон именования, используемый для этих исходных параметров значения. Мастер настройки источника данных использует original_{0} и присваивает имена каждому исходному параметру в свойствах UpdateCommand и DeleteCommand и коллекциях UpdateParameters и DeleteParameters соответствующим образом.

Замечание

Так как мы не используем возможности вставки объекта управления SqlDataSource, вы можете удалить свойство InsertCommand и его коллекцию InsertParameters.

Правильная обработкаNULLзначений

К сожалению, дополненные UPDATE и DELETE автоматически созданные мастером настройки источника данных инструкции при использовании оптимистического параллелизма не работают с записями, содержащими NULL значения. Чтобы понять причину, взгляните на наш SqlDataSource:UpdateCommand

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

Столбец UnitPrice в Products таблице может иметь NULL значения. Если для определенной записи имеется значение NULL, часть условия [UnitPrice] = @original_UnitPrice всегда будет оцениваться как False, так как NULL = NULL всегда возвращает значение False. Поэтому записи, содержащие значения NULL, не могут быть изменены или удалены, так как операторы UPDATE и выражения DELETEWHERE не возвращают ни одной строки для обновления или удаления.

Замечание

Эта ошибка была впервые сообщена корпорации Майкрософт в июне 2004 года в SqlDataSource Создает неправильные инструкции SQL и, как сообщается, будет исправлена в следующей версии ASP.NET.

Чтобы устранить эту проблему, необходимо вручную обновить условия WHERE в обоих свойствах UpdateCommand и DeleteCommand для всех столбцов, которые могут иметь NULL значения. В общем случае измените значение [ColumnName] = @original_ColumnName :

(
   ([ColumnName] IS NULL AND @original_ColumnName IS NULL)
     OR
   ([ColumnName] = @original_ColumnName)
)

Это изменение можно внести непосредственно с помощью декларативной разметки, с помощью параметров UpdateQuery или DeleteQuery из окна "Свойства" или с помощью вкладок UPDATE и DELETE в параметре "Указать настраиваемую инструкцию SQL" или параметр хранимой процедуры в мастере настройки источника данных. Это изменение должно быть сделано для каждого столбца в предложениях UpdateCommand, DeleteCommand и WHERE, которые могут содержать значения NULL.

Применение этого к нашему примеру приводит к следующим измененным значениям UpdateCommand и DeleteCommand.

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued
DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

Шаг 2. Добавление GridView с параметрами редактирования и удаления

При настройке SqlDataSource для поддержки оптимистического параллелизма все, что остается, заключается в добавлении веб-элемента управления данными на страницу, использующую этот элемент управления параллелизмом. В этом руководстве мы добавим GridView, предоставляющий функции редактирования и удаления. Для этого перетащите GridView из панели элементов в конструктор и задайте для её свойства ID значение Products. Из смарт-тега GridView привяжите его к контролу SqlDataSource, добавленному на шаге 1. Наконец, проверьте параметры включения редактирования и включения удаления из смарт-тега.

Привязка GridView к SqlDataSource и включение редактирования и удаления

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

После добавления GridView настройте его внешний вид, удалив ProductID BoundField, изменив ProductName свойство BoundField HeaderText на Product, и обновив UnitPrice BoundField, чтобы его HeaderText свойство было просто Price. В идеале, мы улучшили бы интерфейс редактирования, чтобы включить RequiredFieldValidator для значения ProductName и CompareValidator для значения UnitPrice (чтобы убедиться, что оно правильно отформатировано числовым значением). Дополнительные сведения о настройке интерфейса редактирования GridView см. в руководстве по настройке интерфейса редактирования GridView.

Замечание

Состояние представления GridView должно быть включено, так как исходные значения, передаваемые из GridView в SqlDataSource, хранятся в состоянии представления.

После внесения этих изменений в GridView декларативная разметка GridView и SqlDataSource должны выглядеть следующим образом:

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ConflictDetection="CompareAllValues"
    ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
    DeleteCommand=
        "DELETE FROM [Products]
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
              OR ([UnitPrice] = @original_UnitPrice))
         AND [Discontinued] = @original_Discontinued"
    OldValuesParameterFormatString=
        "original_{0}"
    SelectCommand=
        "SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
         FROM [Products]"
    UpdateCommand=
        "UPDATE [Products]
         SET [ProductName] = @ProductName, [UnitPrice] = @UnitPrice,
            [Discontinued] = @Discontinued
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
            OR ([UnitPrice] = @original_UnitPrice))
        AND [Discontinued] = @original_Discontinued">
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
</asp:SqlDataSource>
<asp:GridView ID="Products" runat="server"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ProductsDataSourceWithOptimisticConcurrency">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Price"
            SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

Чтобы увидеть оптимистичное управление параллелизмом в действии, откройте два окна браузера и загрузите страницу OptimisticConcurrency.aspx в обоих окнах. Нажмите кнопки "Изменить" для первого продукта в обоих браузерах. В одном браузере измените имя продукта и нажмите кнопку "Обновить". Браузер выполнит обратную передачу, и GridView вернется в режим предварительного редактирования, где отображается новое имя продукта для записи, только что измененной.

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

Изменения во втором окне браузера были автоматически потеряны

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

Причина, по которой изменения второго браузера не были применены, заключается в том, что условие в операторе UPDATEWHERE отфильтровало все записи и поэтому не воздействовало на строки. Давайте рассмотрим инструкцию UPDATE еще раз:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL) OR
        ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

Когда второе окно браузера обновляет запись, исходное имя продукта, указанное в WHERE предложении, не соответствует существующему имени продукта (так как оно было изменено первым браузером). Поэтому инструкция [ProductName] = @original_ProductName возвращает значение False и UPDATE не влияет на записи.

Замечание

Удаление работает так же. Откройте два окна браузера, начните с редактирования заданного продукта с одним, а затем сохраните его изменения. После сохранения изменений в одном браузере нажмите кнопку "Удалить" для того же продукта в другом. Так как исходные значения не совпадают в DELETE предложении инструкции WHERE, удаление стихийно терпит неудачу.

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

Шаг 3. Определение возникновения нарушения параллелизма

Так как нарушение параллелизма отклоняет внесенные изменения, было бы приятно предупредить пользователя о возникновении нарушения параллелизма. Чтобы предупредить пользователя, давайте добавим веб-элемент управления Label Web в верхней части страницы с именем ConcurrencyViolationMessage , свойство которой Text отображает следующее сообщение: вы попытались обновить или удалить запись, которая была одновременно обновлена другим пользователем. Просмотрите изменения другого пользователя, а затем повторите обновление или удаление. Задайте свойству элемента управления Label CssClass значение Warning, что является классом CSS, определенным в Styles.css, для отображения текста красным цветом, курсивным, полужирным и большим шрифтом. Наконец, задайте для меток Visible и EnableViewState значение свойств False. При этом Метка будет скрыта, за исключением тех обратных вызовов, в которых явно задано его Visible свойство True.

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

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

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

Создайте обработчик событий для RowUpdated событий и RowDeleted добавьте следующий код:

Protected Sub Products_RowUpdated(sender As Object, e As GridViewUpdatedEventArgs) _
    Handles Products.RowUpdated
    If e.AffectedRows = 0 Then
        ConcurrencyViolationMessage.Visible = True
        e.KeepInEditMode = True
        ' Rebind the data to the GridView to show the latest changes
        Products.DataBind()
    End If
End Sub
Protected Sub Products_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
    Handles Products.RowDeleted
    If e.AffectedRows = 0 Then
        ConcurrencyViolationMessage.Visible = True
    End If
End Sub

В обоих обработчиках событий мы проверяем свойство e.AffectedRows, и, если оно равно 0, устанавливаем для свойства Visible элемента Label значение True. В обработчике событий мы также настраиваем GridView на оставление в режиме редактирования, установив значение свойства KeepInEditMode равным true. При этом необходимо повторно привязать данные к сетке, чтобы данные другого пользователя загружались в интерфейс редактирования. Это достигается путем вызова метода GridView DataBind() .

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

Сообщение отображается при нарушении конкурентности

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

Сводка

При создании веб-приложения, в котором несколько одновременных пользователей могут изменять одни и те же данные, важно учитывать параметры управления параллелизмом. По умолчанию ASP.NET веб-элементы управления данными и элементы управления источниками данных не используют никаких элементов управления параллелизмом. Как мы видели в этом руководстве, реализация управления оптимистическим контролем параллелизма с SqlDataSource является относительно быстрой и легкой. SqlDataSource выполняет основную работу по добавлению дополненных WHERE к автогенерированным UPDATE и DELETE инструкциям, но есть несколько тонкостей в обработке столбцов значений NULL, как описано в разделе "Правильная обработка значений NULL".

В этом руководстве мы завершаем изучение SqlDataSource. Оставшиеся руководства вернутся к работе с данными с помощью ObjectDataSource и многоуровневой архитектуры.

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

Сведения о авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Самостоятельное изучение ASP.NET 2.0 за 24 часа от Sams. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.