Нашли ошибку или опечатку? Выделите текст и нажмите

Поменять цветовую

гамму сайта?

Поменять
Обновления сайта
и новые разделы

Рекомендовать в Google +1

Элемент управления данными SqlDataSource

168

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

Как вы вскоре убедитесь, часто существует пропасть между тем, что вы можете делать, и тем, что должны делать. В более профессиональных, масштабных приложениях вам по-прежнему придется писать и тонко настраивать код доступа к данным для достижения оптимальной производительности, агрегирования данных, обработки ошибок, протоколирования и т.д. Но даже если вы делаете все это, то все равно можете использовать элементы управления источниками данных. Просто не рассчитывайте, что удастся избежать написания кода!

Элементы управления источниками данных включают любые элементы управления, реализующие интерфейс IDataSource. В этой статье мы рассмотрим один из них - SqlDataSource (существуют также другие источники данных: ObjectDataSource, XmlDataSource, SiteMapDataSource и LinqDataSource, их мы рассмотрим позже). Этот элемент позволяет подключаться к любому источнику данных, который имеет поставщика данных ADO.NET. Сюда относятся SQL Server, Oracle и OLE DB или ODBC. Используя этот элемент, писать код доступа к данным не понадобится.

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

Жизненный цикл страницы с привязкой данных

Элементы управления источниками данных могут решать две основных задачи:

  • извлекать данные из источника и применять к связанным элементам управления;

  • обновлять источник данных, когда в связанных элементах управления выполняется редактирование.

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

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

Задачи привязки данных возникают в перечисленном ниже порядке:

  1. Создается объект страницы.

  2. Начинается жизненный цикл страницы, инициируются события Page.Init и Page.Load.

  3. Происходят все остальные события элементов управления.

  4. Элементы управления источниками данных выполняют любые обновления. Если обновляется строка, генерируются события Updating и Updated. Если строка вставляется — то события Inserting и Inserted. Если строка удаляется — Deleting и Deleted.

  5. Генерируется событие Page.PreRender.

  6. Элементы управления источниками данных выполняют необходимые запросы и вставляют полученные данные в связанные элементы управления. Здесь генерируются события Selecting и Selected.

  7. Страница отображается и освобождается.

SqlDataSource

Элементы управления источниками данных появляются в разметочной части веб-страницы подобно обычным элементам управления, например:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ... />

SqlDataSource представляет подключение к базе данных, использующее поставщика ADO.NET. Однако здесь есть ловушка. SqlDataSource требует обобщенного способа создания объектов Connection, Command и DataReader, которые ему потребуются. Единственный способ сделать это — через фабрику поставщика данных, как описано в статье «Код, не зависимый от поставщика». Фабрика отвечает за создание специфичных для поставщика данных объектов, в которых нуждается SqlDataSource для доступа к источнику данных.

Как известно, платформа .NET включает следующие четыре фабрики поставщиков:

  • System.Data.SqlClient

  • System.Data.OracleClient

  • System.Data.OleDb

  • System.Data.Odbc

Они зарегистрированы в файле machine.config, в результате чего их можно использовать с SqlDataSource. Источник данных выбирается установкой имени поставщика. Следующий шаг предполагает применение требуемой строки соединения — без нее установка соединения невозможна. Хотя можно жестко закодировать строку соединения в дескрипторе SqlDataSource, всегда лучше помещать ее в раздел <connectionStrings> файла web.config, чтобы обеспечить большую гибкость и гарантировать, что вы не измените строку подключения по неосторожности, что снизит эффективность пула соединений. Например, если вы создадите следующую строку соединения:

<configuration>
  <connectionStrings>
    <add name="Northwind" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True" />
  </connectionStrings>
</configuration>

то должны будете указать ее в SqlDataSource с использованием примерно такого $-выражения:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ProviderName="System.Data.SqlClient" 
	ConnectionString="<%$ ConnectionStrings:Northwind %>" />

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

Извлечение записей

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

Логика команд SqlDataSource применяется через четыре свойства: SelectCommand, InsertCommand, UpdateCommand и DeleteCommand; все они принимают строку. Строка может быть SQL-кодом (в этом случае соответствующее свойство SelectCommandType, InsertCommandType, UpdateCommandType или DeleteCommandType должно быть установлено в Text — значение по умолчанию) либо именем хранимой процедуры (в этом случае типом команды является StoredProcedure). Необходимо определить команды только для типов действий, который вы хотите выполнять. Другими словами, если вы применяете источник данных только для чтения набора записей, то придется определить только одно свойство SelectCommand.

Если вы конфигурируете команду в окне свойств Visual Studio, то увидите свойство по имени SelectQuery вместо SelectCommand. На самом деле SelectQuery — это виртуальное свойство, отображаемое для удобства во время проектирования. При редактировании SelectQuery (щелкнув на троеточии рядом с именем свойства) можно пользоваться специальным графическим конструктором для написания текста команды (SelectCommand) и добавления параметров команды (SelectParameters).

Ниже показан полный код SqlDataSource, который определяет команду SELECT для извлечения записей из таблицы Employees:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ProviderName="System.Data.SqlClient" 
	ConnectionString="<%$ ConnectionStrings:Northwind %>" 
	SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />

Логику источника данных можно написать вручную или же воспользоваться визуальным конструктором, который позволит создать соединение, а затем логику команды в графическом построителе запросов. Чтобы вызвать этот инструмент, выберите элемент управления источником данных, а затем пункт Configure Data Source (Конфигурировать источник данных) в смарт-теге.

Создав источник данных, вы можете "пожинать плоды", а именно — получить возможность привязывать элементы управления во время проектирования вместо того, чтобы писать логику в обработчике события Page.Load. Вот как это работает:

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

  • Добавьте на форму ListBox. Установите свойство ListBox.DataSourceID в элемент управления источником данных. Его можно выбрать в раскрывающемся списке со всеми источниками данных формы.

  • Установите ListBox.DataTextField в столбец, который должен отображаться (например, EmployeeID).

  • Те же шаги можно использовать для привязки многофункционального элемента управления данными. Добавьте GridView на страницу и установите необходимую информацию о столбцах, поскольку GridView может отображать множество столбцов. Вы немедленно увидите заголовки столбцов запроса на поверхности проектирования страницы.

  • Запустите страницу. Не беспокойтесь о выполнении команд и вызове DataBind() на странице. ASP.NET решает обе эти задачи автоматически. Вы увидите привязанную к данным страницу, подобную показанной на рисунке ниже.

Вот код страницы, который у меня получился и результат:

<body>    
    <form id="form1" runat="server">
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" ProviderName="System.Data.SqlClient" 
            ConnectionString="<%$ ConnectionStrings:Northwind %>" 
            SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees" />
        <asp:ListBox ID="ListBox1" runat="server" DataSourceID="SqlDataSource1" 
            DataTextField="EmployeeID" /><br />
        <asp:GridView ID="grid" runat="server" DataSourceID="SqlDataSource1" />
    </form>
</body>
Простая привязанная к данным страница без кода

Ясно, что огромное преимущество этих элементов управления источниками данных в том, что они позволяют конфигурировать привязку данных во время проектирования, не требуя написания рутинного кода. Более того, результат запроса появляется (в ограниченной степени) в визуальном конструкторе Visual Studio, так что можно получить представление о том, как будет выглядеть форма.

Как упоминалось ранее, можно привязываться к элементу DataReader или к DataView. Поэтому стоит спросить, какой подход использует элемент управления SqlDataSource? На самом деле выбор за вами, в зависимости от установленного значения свойства DataSourceMode — SqlDataSourceMode.DataSet (по умолчанию) или SqlDataSourceMode.DataReader.

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

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

Во-первых, вы должны рассмотреть вопрос кэширования, которое SqlDataSource поддерживает автоматически через свойства EnableCaching, CacheExpirationPolicy и CacheDuration. Во-вторых, учтите, что большую часть времени вы не будете привязывать более одного элемента управления к источнику данных. Причина в том, что многофункциональные элементы управления данными — GridView, DetailsView и FormsView — имеют возможность гибко представлять множество частей данных. Если вы используете эти элементы управления, то придется привязать только один из них, легко соблюдая это ограничение.

Кроме того, важно отметить, что такая привязка данных выполняется в конце обработки веб-страницы, непосредственно перед ее отображением. Это значит, что будет сгенерировано событие Page.Load, за которым последуют любые события элементов управления, за ними — событие Page.PreRender и только затем произойдет собственно привязка данных. Если необходимо написать код, который вступает в действие после завершения привязки данных, можете переопределить метод Page.OnPreRenderComplete(). Этот метод вызывается немедленно после стадии PreRender, но перед сериализацией состояния представления и генерацией действительной HTML-разметки.

Параметризованные команды

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

В следующем примере создается форма типа "главная-детальная" с применением параметров. Для этого примера понадобится два источника данных. Первый представляет список городов (где живут сотрудники). Вот определение SqlDataSource и привязка его к раскрывающемуся списку:

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="SELECT DISTINCT City FROM Employees" />
<asp:DropDownList ID="DropDownList1" runat="server" DataTextField="City"
	DataSourceID="SqlDataSource1"  AutoPostBack="true" />

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

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

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees 
		WHERE City=@City">
		<SelectParameters>
			<asp:ControlParameter ControlID="DropDownList1" Name="City" PropertyName="SelectedValue" />
		</SelectParameters>
</asp:SqlDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource2" />

Трюк состоит в том, что запрос записан с использованием параметра. Параметры всегда отмечаются символом @, в рассматриваемом случае это @City. Определить можно столько параметров, сколько необходимо, но каждый должен быть отображен на отдельное значение. В этом примере значение параметра @City выбирается из свойства DropDownList1.SelectedValue. Однако легко модифицировать дескриптор ControlParameter, привязав его к другому свойству или элементу управления.

При создании параметризованной команды в дескрипторе SqlDataSource параметры соответствующим образом кодируются для противодействия атаками внедрением SQL. Если теперь запустить страницу, можно будет увидеть список сотрудников из указанного города:

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

Хранимые процедуры

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

CREATE PROCEDURE GetEmployeesByCity
	@City varchar(15)
AS
	SELECT EmployeeID, FirstName, LastName, Title, City
	FROM Employees WHERE City = @City

Тогда источник данных sourceEmployees можно изменить, как показано ниже:

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="GetEmployeesByCity" SelectCommandType="StoredProcedure">
	<SelectParameters>
		<asp:ControlParameter ControlID="DropDownList1" Name="City" PropertyName="SelectedValue" />
	</SelectParameters>
</asp:SqlDataSource>

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

Дополнительные сведения о типах параметров

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

Типы параметров
Источник Управляющий дескриптор Описание
Свойство элемента управления <asp:ControlParameter>

Свойство другого элемента управления на странице

Строковое значение запроса <asp:QueryStringParameter>

Значение из текущей строки запроса

Значение состояния сеанса <asp:SessionParameter>

Значение, сохраненное в текущем сеансе пользователя

Значение cookie-набора <asp:CookieParameter>

Значение из любого cookie-набора, присоединенного к текущему запросу.

Значение профиля <asp:ProfileParameter>

Значение из текущего пользовательского профиля

Переменная формы <asp:FormParameter>

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

Значение маршрута <asp:RouteParameter>

Значение из маршрутизированного URL

Программная установка <asp:Parameter>

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

Обработка ошибок

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

В том, что касается правильного освобождения всех ресурсов (таких как соединения) в случае возникновения ошибки, можно рассчитывать на SqlDataSource. Однако лежащее в основе исключение не может быть обработано. Вместо этого оно появится на странице и нарушит обработку. Как это бывает с любым другим необработанным исключением, пользователь получит довольно непонятное сообщение или страницу ошибки. Это неизбежно — если бы SqlDataSource подавлял исключения, он мог бы скрыть потенциальные проблемы и чрезвычайно затруднить отладку. Тем не менее, неплохо обработать проблему в коде веб-страницы и отобразить более подходящее сообщение об ошибке.

Чтобы сделать это, необходимо обработать событие источника данных, которое происходит немедленно после ошибки. Если выполняется запрос, то это будет событие Selected. Если же выполняется операция обновления, удаления или вставки, то понадобится обработать события Updated, Deleted или Inserted. (Если вы не хотите предоставлять настраиваемые сообщения об ошибках, можно обработать все эти события в одном обработчике.)

В обработчике ошибок можно получить доступ к объекту исключения через свойство SqlDataSourceStatusEventArgs.Exception. Чтобы предотвратить дальнейшее распространение ошибки, просто установите значение свойства SqlDataSourceStatusEventArgs.ExceptionHandled в true. Затем отобразите на веб-странице соответствующее сообщение об ошибке, чтобы проинформировать пользователя о том, что команда не была завершена:

protected void SqlDataSource2_Selected(object sender, SqlDataSourceStatusEventArgs e)
{
	if (e.Exception != null)
	{
		// Замаскировать ошибку общим сообщением
		LabelError.Text = "Запрос не выполнен";

		// Предположить, что ошибка обработана
		e.ExceptionHandled = true;
	}
}

Обновление записей

Извлечение данных — это только полдела. SqlDataSource также может применять изменения. Единственная ловушка в том, что не все элементы управления поддерживают обновление. Так, например, простой ListBox не предоставляет никакой возможности пользователю редактировать значения, удалять существующие элементы или вставлять новые. Многофункциональные элементы управления ASP.NET, включая GridView, DetailsView и FormView, имеют средства редактирования, которые можно включить.

Первый шаг состоит в определении подходящих команд для операций, которые необходимо выполнить — вставки (InsertCommand), удаления (DeleteCommand) и обновления (UpdateCommand). Если известно, что пользователю нужно разрешить выполнять одни операции (например, обновление), а другие запретить (например, вставку и удаление), то можно безопасно пропустить команды, которые не требуются.

Команды InsertCommand, DeleteCommand и UpdateCommand определяются таким же образом, как команда для свойства SelectCommnad — с использованием параметризованного запроса или вызова хранимой процедуры. Например, вот как выглядит элемент SqlDataSource, который определяет базовую команду обновления каждого столбца:

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees"
	UpdateCommand="UPDATE Employees SET FirstName=@FirstName, LastName=@LastName, Title=@Title,
		City=@City FROM Employees WHERE EmployeeID=@EmployeeID" />

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

Чтобы протестировать это, создайте страницу с показанным ранее элементом SqlDataSource и привязанным элементом управления GridView. Чтобы разрешить редактирование, установите свойство GridView.AutoGenerateEditButton в true. Слева в экранной таблице появится новый столбец. GridView использует этот столбец для отображения ссылок для управления процессом редактирования.

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

Редактирование a GridView

Ссылка Cancel возвращает строку в ее начальное состояние. Ссылка Update передает значения коллекции SqlDataSource.UpdateParameters (используя имена полей) и затем вызывает метод SqlDataSource.Update() для внесения изменений в базу данных. Опять-таки, никакого кода писать не понадобится.

Можно создать аналогичные параметризованные команды для DeleteCommand и InsertCommand. Чтобы разрешить удаление и вставку, вы должны добавить столбец к GridView, в котором свойства ShowInsertButton и ShowDeleteButton установлены в true.

Строгая проверка параллелизма

Команда обновления в предыдущем примере сравнивает запись на основе ее идентификатора. Проблема такого подхода связана с тем, что команда обновления обновляет все поля без разбора — она не имеет возможности отличить поля, которые изменились, от полей, оставшихся неизменными. В результате могут быть аннулированы изменения другого пользователя, если они выполнены между моментом запроса страницы и моментом ее обновления.

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

Для противостояния такого рода проблемам можно реализовать строгую проверку параллелизма. Один из способов сделать это — создать команду, которая выполняет обновление, только если каждое поле совпадает, используя более подробную конструкцию WHERE. Вот как выглядит такая команда:

UPDATE Employees 
	SET FirstName=@FirstName, LastName=@LastName, Title=@Title, City=@City
	FROM Employees 
	WHERE EmployeeID=@original_EmployeeID AND FirstName=@original_FirstName
		AND LastName=@original_LastName AND Title=@original_Title 
		AND City=@original_City

В этой команде присутствует одно важное изменение. Конструкция WHERE не пытается сравнивать параметры @FirstName, @LastName и т.д., потому что эти параметры отражают текущие значения (которые могут не совпадать с исходными). Вместо этого она использует параметры по имени @original_FirstName, @original_LastName и т.д. Это порождает очевидный вопрос: откуда возьмутся значения этих параметров? Чтобы получить доступ к этим исходным значениям, потребуется выполнить некоторые шаги.

Для начала необходимо сообщить SqlDataSource о том, что нужен доступ к исходным значениям, установив свойство ConflictDetection в ConflictOptions.CompareAllValues вместо ConflictOptions.OverwriteChanges (принятого по умолчанию).

Второй шаг состоит в том, чтобы сообщить SqlDataSource, как следует именовать параметры, хранящие исходные значения. По умолчанию исходные значения получают те же имена параметров, что и измененные значения. Фактически они переопределяют исходные значения параметров. Чтобы предотвратить такое поведение, необходимо установить свойство OldValuesParameterFormatString. Это свойство принимает строку, содержащую заполнитель {0}, где {0} указывает на имя исходного параметра.

Например, если вы установите OldValuesParameterFormatString в original_{0} (как это часто делается), то параметры, хранящие исходные значения, получат префикс original. Например, FirstName станет original_FirstName, а LastName - @original_LastName, как в команде обновления, показанной выше. Зная эти детали, вы можете создать полностью сконфигурированный SqlDataSource, который реализует этот прием:

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
    ConnectionString="<%$ ConnectionStrings:Northwind %>"
    SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees"
    UpdateCommand="UPDATE Employees SET FirstName=@FirstName, LastName=@LastName, Title=@Title,
        City=@City FROM Employees WHERE EmployeeID=@original_EmployeeID AND FirstName=@original_FirstName
        AND LastName=@original_LastName AND Title=@original_Title AND City=@original_City"
    ConflictDetection="CompareAllValues"
    OldValuesParameterFormatString="original_{0}" />

Кстати, существует другой способ получения доступа к исходным значениям — установка свойства DataKeyNames привязанного элемента управления. Например, если вы установите GridView.DataKeyNames в EmployeeID, то получите доступ как к текущему, так и исходному значению EmployeeID (хотя все равно придется применять OldValuesParameterFormatString, если исходное и новое значения нужно различать).

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

Удаление записей

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

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees"
	DeleteCommand="DELETE Employees WHERE EmployeeID=@EmployeeID" />

Также необходимо внести небольшие изменения в GridView. Сначала установите свойство GridView.AutoGenerateDeleteButton в true для добавления столбца со ссылками Delete (Удалить) рядом с каждой записью.

Если вы используете стандартный режим ConflictOptions.OverwriteChanges, также нужно установить в GridView.DataKeyNames разделенный запятыми список имен полей, составляющих первичный ключ. Если вы забудете выполнить этот шаг, то GridView не передаст эти параметры в SqlDataSource, и SqlDataSource не сможет найти запись, подлежащую удалению.

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

<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource2"
	AutoGenerateDeleteButton="true" DataKeyNames="EmployeeID" />

Вставка записей

Элемент GridView поддерживает редактирование и удаление записей, но не их вставку. Однако DetailsView и FormView поддерживают вставку записей. Базовый процесс тот же самый. Удостоверьтесь, что вы определили InsertCommand, как показано ниже:

<asp:SqlDataSource ID="SqlDataSource2" runat="server" ProviderName="System.Data.SqlClient"
	ConnectionString="<%$ ConnectionStrings:Northwind %>"
	SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees"
	InsertCommand="INSERT INTO Employees (FirstName,LastName,Title,City) VALUES (@FirstName, @LastName, @Title, @City)" />

Вам не нужно беспокоиться о свойствах ConflictDetection и OldValuesParameterFormatString, поскольку они никак не влияют на вставку записей. Далее можно сконфигурировать привязанный элемент управления данных для поддержки вставки. Для DetailsView просто необходимо установить свойство AutoGenerateInsertButton в true. В качестве альтернативы можно добавить CommandField к DetailsView и установить его свойство ShowInsertButton в true.

Недостатки SqlDataSource

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

  • Логика доступа к данным встраивается в страницу.

    Чтобы создать элемент управления SqlDataSource, приходится жестко кодировать SQL-операторы на веб-странице. Это значит, что вы не сможете тонко настроить запрос без модификации веб-страницы. В приложении масштаба предприятия это ограничение неприемлемо, потому что часто приходится пересматривать запросы уже после развертывания приложения в ответ на профилирование, добавление индексов и изменения ожидаемой нагрузки.

    Эту ситуацию можно немного улучшить, ограничив использование SqlDataSource только с хранимыми процедурами. Однако в крупномасштабном веб-приложении код доступа к данным будет сопровождаться, тестироваться и совершенствоваться отдельно от бизнес-логики (он даже может кодироваться другими разработчиками). SqlDataSource просто не обеспечивает такой степени гибкости.

  • Сопровождение больших приложений.

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

  • Недостаток гибкости.

    Каждая задача доступа к данным требует отдельного элемента SqlDataSource. Если вы хотите предоставить пользователю несколько способов просмотра запрошенных данных, то это может просто "утопить" страницу в объектах доступа к данным — по одному для каждого варианта команды. В таком случае страница быстро усложнится.

  • Неприменимость для других задач.

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

Чтобы обойти все эти ограничения, можно рассмотреть возможность применения ObjectDataSource. Элемент ObjectDataSource позволяет привязать страницу к пользовательскому компоненту доступа к данным. Лучше всего то, что вы получаете почти все те же удобства вроде привязки данных во время проектирования и избавляетесь от необходимости писать код для веб-страницы.

Пройди тесты