Реализация оптимистического параллелизма с помощью элемента управления SqlDataSource (VB)

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

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

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

Введение

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

Наряду с флажком Создать INSERTинструкции , UPDATEи DELETE диалоговое окно Дополнительные параметры создания SQL включает параметр Использовать оптимистичный параллелизм (см. рис. 1). Если этот флажок установлен, WHERE предложения в автоматически созданных UPDATE инструкциях и изменяются DELETE , чтобы выполнить обновление или удаление, только если базовые данные базы данных не были изменены с момента последней загрузки данных пользователем в сетку.

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

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

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

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

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

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

На рисунке 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 с панели элементов на Designer, задав для его ID свойства значение ProductsDataSourceWithOptimisticConcurrency. Затем щелкните ссылку Настройка источника данных в смарт-теге элемента управления. На первом экране мастера выберите для работы с NORTHWINDConnectionString и нажмите кнопку Далее.

Выберите для работы с NORTHWINDConnectionString

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

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

Из таблицы Products верните столбцы ProductID, ProductName, UnitPrice и Неподдерживаемые.

Рис. 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 предложений свойств и DeleteCommand (и добавления дополнительных UpdateCommand параметров в соответствующие коллекции параметров), при выборе параметра Использовать оптимистичный параллелизм настраиваются два других свойства:

Когда веб-элемент управления данными вызывает метод или Delete() SqlDataSourceUpdate(), он передает исходные значения. Если свойство 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, WHERE часть [UnitPrice] = @original_UnitPrice предложения всегда будет иметь значение False, так как NULL = NULL всегда возвращает значение False. Таким образом, записи, содержащие NULL значения, нельзя изменять или удалять, так как UPDATE предложения и DELETEWHERE не возвращают строки для обновления или удаления.

Примечание

Эта ошибка была впервые зарегистрирована в корпорации Майкрософт в июне 2004 года в sqlDataSource Generates Incorrect SQL Statements и, как сообщается, будет исправлена в следующей версии 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 с панели элементов на Designer и задайте для нее ID значение Products. Из смарт-тега GridView привяжите его к элементу ProductsDataSourceWithOptimisticConcurrency управления 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 в 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. Изменения во втором окне браузера были потеряны без уведомления (щелкните для просмотра полноразмерного изображения)

Причина, по которой изменения второго браузера не были зафиксированы, заключалась в UPDATE том, что предложение оператора WHERE отфильтровывало все записи и, следовательно, не влияло ни на какие строки. Рассмотрим 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 s WHERE , удаление автоматически завершается ошибкой.

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

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

Так как нарушение параллелизма отклоняет внесенные изменения, было бы неплохо оповещать пользователя о нарушении параллелизма. Чтобы предупредить пользователя, добавьте веб-элемент управления Метка в верхнюю часть страницы ConcurrencyViolationMessage , свойство которой Text отображает следующее сообщение: Вы попытались обновить или удалить запись, которая была одновременно обновлена другим пользователем. Просмотрите изменения другого пользователя, а затем повторите обновление или удаление. Присвойте свойству Label control s CssClass значение Warning, которое представляет собой класс CSS, определенный в Styles.css , который отображает текст красным, курсивным, полужирным и крупным шрифтом. Наконец, задайте для свойств Label s 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, задайте свойству ConcurrencyViolationMessage Label s Visible значение True. В обработчике RowUpdated событий мы также предписываем GridView оставаться в режиме редактирования, задав KeepInEditMode для свойства значение true. При этом необходимо повторно привязать данные к сетке, чтобы данные другого пользователя загружались в интерфейс редактирования. Это достигается путем вызова метода GridView.DataBind()

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

Сообщение отображается перед лицом нарушения параллелизма

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

Сводка

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

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

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

Об авторе

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