Книги, научные публикации Pages:     | 1 |   ...   | 7 | 8 | 9 | 10 | 11 |

David Sceppa Microsoft' ADO.NET Microsoft Press Дэвид Сеппа Microsoft ADO.NET ...

-- [ Страница 9 ] --

" Dim cmd As New SqlXmlComfnand(strConn) Dim strPathToSchema As String = "C:\MySchema.XSD" cmd.SchemaPath = strPathToSchema cmd.CommandText = "Orders[CustomerID='GROSR']" cmd.CommandType = SqlXmlCommandType.XPath cmd.RootTag = "ROOT" Dim da As New SqlXmlAdapter(cmd) Dim ds As New DataSetO da.FillCds) ds.Tables("Orders").Rows(0)("CiJstomerID") = "ALFKI" ds.Tables("Orders").Rows(1)("CiJStonierrD"} = "ALFKI" Dim strPathToDiffGram As String = "C:\MyDiffGram.XML" ds.WriteXml(strPathToDiffGram, XffllWriteMode.DiffGram) cmd = New SqlXmlCommand(strConn) cmd.SchemaPath = strPathToSchema cmd.CommandType = SqlXmlCommandType.DiffGram cmd.CommandStream = New FileStreamfstrPathToDiffGram, FileMode.Open, FileAccess.Read) cmd,ExecuteNonQuery() 'Отмена изменений Dim strSQL As String = "UPDATE Orders SET CustomerlD = 'GROSR' " & _ "WHERE OrderlD = 10268 OR OrderlD = 10785" cmd = New SqlXmlCommand(strConn) cmd.CommandText = strSQL cmd.CommandType = SqlXmlCommandType.Sql cmd. ExecuteNonQueryO Visual C#.NET //Добавьте в начало модуля кода следующие строки jsing Microsoft.Data.SqlXml;

using System.Xml;

using System.10;

string strConn = "Provider=SQLOLEDB;

Oata Source=(locat)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

SqlXmlCommand cmd = new SqlXmlConrniand(strConn);

string strPathToSchema = "C:\\MySchema.XSD";

cmd.SchemaPath = strPathToSchema;

cmd.CommandText = "Qrders[CustomerID='GROSR']";

cmd.CommandType = SqlXmlCommandType.XPath;

cmd.RootTag = "ROOT";

SqlXmlAdapter da = new SqlXmlAdapter(cmd);

DataSet ds = new DataSetO;

da.Fill(ds);

Работа с XML-данными ГЛАВА ds.Tables["Orders"].Rows[Q]["CustomerID"] = "ALFKI";

ds.Tables["Orders"].Rows[1]["CustomerID"] = "ALFKI";

string strPathToDiffGram = "C:\\MyDiffGrarn.XML";

ds.WriteXml(strPathToDiffGram, XmlWriteMode.DiffGrani);

cmd = new SqlXmlCommand(strCcmn);

cmd.SchemaPath = strPathToSchema;

cmd.CofnmandType = SqlXmlCommandType.DiffGram;

cmd.CommandStreain = new FileStream(strPathToDiffGram, FileHode.Open, FtleAccess.Read);

cmd. ExecuteNonQueryO;

//Отмена изменений string strSQL = "UPDATE Orders SET CustomerlD = 'GROSR' " + "WHERE OrderlD = 10266 OR OrderlD = 10785";

cmd = new SqlXmlCommandCstrConn);

cmd.CommandText = strSQL;

cmd.ComnmndType = SqlXmlCommandType.Sql;

cmd. ExecuteNonQueryO;

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

Чтобы убедиться, что SqlXmlCommand передал хранящиеся в XML-доку менте формата diffgram изменения, определите точку останова перед выполнением последнего запроса, Логика обновления, используемая объектом SqlXmlCommand Прежде чем продолжить, немного поговорим о логике, генерируемой объектом SqlXmlCommand для передачи изменений в БД. Это поможет вам глубже понять преимущества и недостатки передачи обновлений с использованием поставщика SQL XML.NET Data Provider.

Когда фрагмент кода вызвал метод SqlXmlCommandExecuteNonQuery для пере дачи изменений из XML-документа формата diffgram, поставщик SQL XML.NET Data Provider сгенерировал и передал SQL Server следующий пакетный запрос:

SET XACT_ABORT ON BEGIN TRAN DECLARE @eip INT, @r Int, @e int SET @>eip = UPDATE Orders SET CustomerID=N'ALFKI' WHERE ( OrderID=10268 ) AND ( CustomerID=N'GROSR' ) AND ( OrderDate=N'1996-07-30 00:00:00' ) ;

SELECT ве_ = @@ERROR, @r = @ 1) RAISERROR ( N'SQLOLEDB Error Description: Ambiguous update, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty update, no updatable rows found Transaction aborted ', 16, 1) 482 Часть III Автономная работа с данными: объект DataSet модели ADO.NET UPDATE Orders SET CustomerID=N'ALFKI' WHERE ( OrderID=10785 ) AND ( CustomerID=N'GROSR' ) AND ( OrderDate=N'1997-12-18 00:00:00' ) ;

SELECT @e = @@ERROR, @r = @$ROWCOUNT IF (@e != 0 OR @r != 1) SET @eip = IF Or > 1) RAISERROR С N'SQLOLEDB Error Description: Ambiguous update, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty update, no updatable rows found Transaction aborted ', 16, 1) IF (0eip != 0) ROLLBACK ELSE COMMIT SET XACT.ABORT OFF Для начала он указывает SQL Server отменить текущую транзакцию, если та вызовет ошибку, начинает транзакцию и определяет ряд переменных для хране ния данных. Затем код выполняет первый запрос UPDATE и помещает данные в переменные, чтобы определить, есть ли ошибки и сколько записей затронул за прос. Если запрос затронул одну запись и не сгенерировал ошибку, код продол жает выполнять командные запросы и проверять, успешно ли проходит обновле ние. Когда все командные запросы выполнены, код при отсутствии ошибок под тверждает транзакцию и деактивирует параметр, указывавший SQL Server откатить транзакцию в случае ошибки.

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

Понимать отдельные запросы пакета вам не обязательно, но при передаче обновлений с использованием поставщика SQL XML.NET Data Adapter все же помните несколько правил.

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

Х При передаче изменений поставщик не выбирает из БД каких-либо данных.

По завершении передачи вы не увидите новых значений автоинкремента или значений типа timestamp.

Х Если изменения передаются средствами метода SqlXmtAdapter.Update, по успеш ном завершении передачи изменений объект SqlXmlAdapter вызывает метод DataSetAcceptChanges. SqlXmlAdapter не вызывает методы AcceptChanges только тех объектов DataTable, которые указаны в файле схемы.

Простой пример с использованием ADO.NET и XML До этого момента я показывал только изолированные примеры использования XML функций ADO.NET Настала пора объединить их в одном приложении, которое продемонстрирует совместную мощь языка XML и XML-функций ADO.NET.

ГЛАВА 12 Работа с XML-данными В приложении применяются параметризованные запросы, возвращающие спи сок заказов конкретного клиента. Но прежде, чем закатить глаза, заметьте: теперь мы будем получать больший объем информации Ч данные из четырех связанных таблиц Ч Customers. Orders, Order Details и Products. Таким образом, список зака зов станет четче Ч в нем появятся названия компаний, а также названия заказан ных товаров.

Приложение преобразует результаты запросов в XML-формат, с помощью XSLT трансформации преобразует полученные XML-данные в HTML-код и затем выво дит этот код в Internet Explorer (рис. 12-8).

icfosoft Interne! Еч)1г.г. 0Je Etft !j.iev* Fjvoritei Tods yelp ;

г> -Х "'Х L *^ Order Histoty Гот The Cracker Br,% 3 Orders Pro dart Quantity Unit Price Item Total $ Rossle Sauerkraut 10 Ed 56 Tliunnger Rostbiahvum $123 79 Cuia Malacca I1S Х I Produrt Quantity Item Total ". i i $ Laughing Lumberjack Lager Product Boston Crab Meat Fib Mix Рис. 12-8. Web-страница, генерируемая приложением-примером Наш пример Ч это консольное приложение, запускающее экземпляр Internet Explorer. Поскольку в качестве отправной точки используется именно консоль ное приложение, пример получился не слишком шикарный, но зато его легко использовать как ресурс для дальнейшей работы. Например, в двух случаях очень полезно преобразовать результаты запросов в HTML-код: при создании Web-при ложении и формировании отчетов. Полагаю, это самые яркие примеры исполь зования XML-функций ADO.NET.

Помните: в работе с HTML и XSLT я, в общем-то, новичок. Я создал очень про стую Web-страницу с помощью Microsoft FrontPage и затем сравнил ее структуру со структурой XML-документа, сгенерированного мной при помощи ADO.NET и содержавшего данные о моих заказах. Затем я по материалам книги, посвящен ной XSLT, попытался разобраться, как создать XSLT-трансформацию для преоб разования XML-кода в HTML-код.

Я открыто говорю о недостатке опыта:

Х потому что любой программист БД, не будучи профессионалом в XML, может в достаточной степени изучить XSLT для преобразования XML в HTML;

Х чтобы извиниться за то, что Web-страница получилась не очень привлекательной, Автономная работа с данными: объект DataSet модели ADO.NET 484 Часть III Два пути к одному конечному пункту Вообще-то я вас обманул, поскольку создал не один, а два примера. Оба они ис пользуют одинаковую XSLT-трансформацию и создают одинаковые HTML-файлы, но по-разному генерируют XML-документ с данными из БД Norhtwind.

Первый пример, DataSetToHTML, подключается к локальной БД Norhtwind.NET SDK MSDE при помощи поставщика OLE DB.NET Data Provider. Он использует стандартные запросы и помещает их результаты в объект DataSet. Чтобы обратиться к содержимому DataSet, как к XML-документу, и выполнить XSLT-трансформацию для создания HTML-страницы, первый пример создает объект XmlDataDocument, связанный с объектом DataSet.

Второй пример, SqlXmlToHTML, основан на поставщике SQL XML.NET Data Provider и XML-шаблоне запросов, который обращается к БД Northwind, но исполь зует синтаксис FOR XML. Свойству XslPath объекта SqlXmlCommand во втором примере задан путь к файлу XSLT-трансформации, ADO.NET и XML: счастливая пара ADO.NET предоставляет широкую поддержку языка XML. Благодаря XML-функци ям ADO.NET, разработчики без труда смогут переходить от традиционных объек тов доступа к данным к XML-объектам и обратно. Объект DataSet позволяет счи тывать и записывать данные и/или информацию схемы в XML-формате. Объект XmlDataSet позволяет легко обращаться к содержимому DataSet как к XML-доку менту. Поставщик SQL XML.NET Data Provider позволяет использовать XML-фун кции SQL Server 2000 и помещать результаты запросов в формате XML в файлы, XML-документы и объекты DataSet.

Вопросы, которые стоит задавать почаще Вопрос. Объекты DataSet, DataTable и DataColumn позволяют довольно гибко определять структуру XML-документа, создаваемого с помощью WriteXml, однако мне требуются еще более широкие возможности управления. Я хочу добавить в XML-файл инструкцию по обработке, ссылающуюся на таблицу стилей XSLT. Как это сделать?

Ответ. Воспользуйтесь объектом XmlDataDocument и обращайтесь к содержимому DataSet как к XML-документу. Задайте свойству EnforceConstraints объекта DataSet значение False и затем с помощью объекта XmlDataDocument определите нужную вам структуру XML-документа. В обсуждаемой ситуации годится метод CreateProces singlnstiiiction объекта XmlDataDocument. Затем вызовите метод XmlDataDocument.' Save и сохраните результаты в XML-файл.

Visual Basic.NET Dim ds As New DataSetC) ds.EnforceConstraints = False Dim xmlDoc As New XmlDataDocument(ds) Dim strPI as String = "type='text/xsl' href='HyTransform.XSLTT" Dim xmlPI as XmlProcessinglnstruction ГЛАВА 12 Работа с XML-данными xmlPI = xmlDoc.Create?rocessinglnstruction("xml-stylesheet", strPI) xmlDoc.InsertBeforefxmlPI, xmlDoc.DocumentElement) Dim strPathToXmlFile As String = "C:\MyData.XML" xmlDoc. Save(strPathToXmlFile) Visual C#.NET DataSet d's = new DataSetO;

ds.EnforceConstraints = false;

XmlDataDocument xmlDoc = new XmlDataDocument(ds);

string strPI = "type='text/xsl' href='MyTransfortn.XSLT'";

XmlProcessinglnstruction xmlPI;

xmlPI = xmlDoc.CreateProcessingInstruction("xml-stylesheet", strPI);

xmlDoc.InsertBeforeCxmlPI, xmlDoc.DocumentElement);

string strPathToXmlFile = "C:\\HyData.XML";

xmlDoc.Save(StrPathToXmlFile);

Вопрос. Я вызываю уже имеющиеся хранимые процедуры, и мне нужно получать результаты запросов в XML-формате, однако. Как это сделать?

Ответ. Укажите поставщику SQL XML.NET Data Provider преобразовать резуль таты запроса с XML-формат, задав свойству ClientSideXml объекта SqlXmlCommand значение True, как показано ниже:

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" StrSQL = "EXEC CustOrdersOrders 'ALFKI' FOR XML NESTED" Dim cmd As New SqlXmlCommand(strConn) cmd.CommandText = strSQL cmd.ClientSideXml = True cmd.RootTag = "ROOT" Dim xmlDoc As New XmlDocumentO Dim xmlRdr As XmlReader = cmd.ExecuteXmlReader xmlDoc.Load(xmlRdr) xmlRdr.Close Console.WriteLine(xmlDoc.InnerXral) Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "EXEC CustOrdersOrders 'ALFKI FOR XML NESTED";

SqlXmlCommand cmd = new SqlXmlCommand(strConn);

cmd.CommandText = strSQL;

cmd.ClientSideXml = true;

486 Часть III Автономная работа с данными: объект DataSet модели ADO.NET cmd.RootTag = "ROOT";

XmlDocument xmlDoc = new XmlDocumentf);

XmlReader xmlRdr = cmd. ExecuteXmlReaderO;

xmlDoc,Load(xmlRdr);

xmlRdr.Close();

Console.WriteLine(xmlDoc.InnerXml);

Подробнее о преобразовании в формат XML на стороне клиента с помощью поставщика SQL XML.NET Data Provider Ч в разделе Comparing Client-Side XML Formatting to Server-Side XML Formatting файла справки SQL XML 3 ЧАСТЬ СОЗДАНИЕ ЭФФЕКТИВНЫХ ПРИЛОЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ ADO.NET ГЛАВА Создание эффективных Windows-приложений х1так, вы уже умеете работать с различными объектами модели ADO.NET. Вам пред лагают создать объект DataSet и поместить результаты запроса в объект DataTable при помощи объекта DataAdapterl Нет проблем. Требуется добавить объект Data Relation для перемещения между дочерними и родительскими данными двух свя занных объектов DataTable' Это сможет даже ребенок. Необходимо создать логи ку для передачи изменений в БД? Легко.

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

В этой главе я расскажу о том, как создавать эффективные Windows-приложе ния, используя полученные вами знания. В первой части этой главы обсуждаются этапы создания приложения, аналогичного созданному нами в главе 2 с помощью мастера Data Form Wizard. Вы узнаете, как связывание с данными экономит вре мя при разработке пользовательского интерфейса приложения, а также о различ ных способах обновления и подключения. Заключительная часть главы посвяще на различным методам работы с данными больших двоичных объектов (binary large object, BLOB) в Windows-приложениях.

Быстрое создание пользовательского интерфейса при помощи связывания с данными Предположим, вам нужно создать интерфейс пользователя. Вы можете написать код для получения из БД данных и передачи в БД изменений, но вам также требу ется вывести эти данные на форме и предоставить пользователям возможность ГЛАВА 13 Создание эффективных Windows-приложений взаимодействовать с ними, добавляя, изменяя и удаляя записи данных. Помимо всего прочего, работу следует сделать как можно быстрее.

Понятно, что можно написать код, считывающий содержимое объекта DataRow и выводящий соответствующие данные в элементах управления TextBox формы.

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

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

Примечание В действительности связывание с данными позволяет работать не только с объектами DataSet. Элементы управления можно связывать с такими структурами ADO.NET, как объекты DataSet и DataTable, масси вы и любые другие объекты, реализующие интерфейс IList. Эта книга посвящена ADO.NET, и поэтому основное внимание я уделю связываншо с данными структур ADO.NET. Подробнее о связывании с данными дру гих структур Ч в соответствующих разделах документации.NET Frame work SDK.

Но достаточно вступительных слов. Давайте с помощью связывания с данны ми создадим простое приложение для приема заказов. Это приложение (рис. 13-1) позволяет просматривать и изменять заказы, размещенные клиентами. Процесс создания приложения разделен на несколько этапов, демонстрирующих отдель ные средства связывания с данными.

Order Details "roduetlD UnitPiice Quantity iiemTolal $45.Hf" 15 " $684. ЗЭ iG S12.CC Рис. 13-1- Приложение для приема заказов На прилагаемом к книге компакт-диске записан готовая версия данного при ложения, а также версии для каждого из рассматриваемых далее этапов. Кроме того, '7-595Е Создание эффективных приложений с использованием ADO.NET 490 Часть IV на диске вы найдете версии приложения, созданные как с использованием Microsoft Visual Basic.NET, так и с использованием Microsoft Visual С*.NET.

Примечание Приложение рассчитано на работу с БД Northwind. Инструкции по установке версии Microsoft Desktop Engine (MSDE) и баз данных, по ставляемых вместе c.NET Framework SDK, см. в главе 3 этой книги. В числе прочих устанавливается и БД Northwind.

Этап 1. Создание объектов DataAdapter и DataSet Мы создаем Windows-приложение, и поэтому начать следует с создания нового Windows-проекта на языке, который вы предпочитаете. Назовите приложение Chapter 13. Задайте форме по умолчанию JrmEditOrders заголовок Edit Orders. В при ложении я задал свойству MaximizeBox формы значение False, а свойству FormBorder Style Ч значение Fixed3D. Это гарантирует, что пользователь не сможет изменить размер формы. Задавать указанным свойствам такие значения не обязательно, но мне не нравится, когда пользователи изменяют размер формы, не рассчитанной на это.

Приложение предназначено для отображения сведений о заказах конкретно го клиента, и в связи с этим нам потребуется объект DataAdapter для выборки информации из БД Northwind. Выберите на вкладке Data панели инструментов элемент управления QleDbDataAdapter и перетащите его на форму. Запустится мастер Data Adapter Configuration Wizard (подробнее о нем Ч в главе 5).

В окне Connection мастера выберите существующее соединение с БД Northwind.

Если соединений нет. щелкните New Connection и создайте новое соединение. В окне Query Type оставьте переключатель в положении по умолчанию Ч Use SQL Statements. Затем в окне SQL Statement введите такой SQL-оператор:

SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders WHERE CustomerlD = ?

Я выбрал этот запрос по двум причинам. Во-первых, создается очень простое приложение, и поэтому следует ограничить число возвращаемых полей. Во-вто рых, вместо того чтобы получать из БД все заказы, требуется получить только заказы конкретного клиента. Помните;

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

Завершив работу с мастером, вы увидите в панели компонентов формы объекты QleDbDataAdapter и OleDbConnection. Измените их имена соответственно на daOr ders и cnNortbwind. Щелкните в панели компонентов правой кнопкой и выберите в контекстном меню команду Generate DataSet. В открывшемся диалоговом окне измените имя нового класса DataSet на xsdChapterl3 и щелкните ОК. В окне Solution Explorer появится новый элемент Ч xsdChapterl 3-xsd, а в панели компонентов Ч экземпляр класса DataSet. Задайте этому экземпляру имя dsChapterlB.

Столбец OrderlD таблицы Orders Ч это столбец с автоинкрементом. Как вы помните из главы 6, в объекте DataSet свойствам AutoIncrementSeed и Autolncrement Step столбцов с автоинкрементом рекомендуется задавать значение -1. Давайте зададим указанные свойства столбца OrderlD в только что созданном нами клас се DataSet со строгим контролем типов. В окне Solution Explorer дважды щелкни ГЛАВА 13 Создание эффективных Windows-приложений те файл схемы класса DataSet со строгим контролем типов (xsdChapter!3.xsd).

Выделите столбец OrderlD. Задайте свойствам AutoIncrementSeed и AutolncremetitStep значение -1. Закройте окно и сохраните изменения.

Этап 2. Добавление связанных с данными элементов управления TextBox На форме уже есть объекты DataAdapter и DataSet. Теперь давайте добавим эле менты управления lextBox, при помощи которых на форме будет отображаться информация о конкретном заказе. Чтобы сделать пользовательский интерфейс интуитивно-понятным, мы также добавим для каждого элемента управления мет ку с описанием отображаемых элементом данных, Сначала добавим метку и элемент управления TextBox для столбца OrderlD.

Перетащите с панели инструментов на форму элемент управления Label. Задайте его свойству Name значение IblOrderlD, а свойству Text Ч значение Order ID:. За тем перетащите с панели инструментов на форму элемент управления TextBox.

Задайте его свойству Name значение txtOrderlD, а свойству Text Ч пустое значение.

Сейчас мы по-прежнему имеем дело с простым элементом управления TextBox.

Чтобы связать его со столбцом OrderlD объекта DataSet, перейдите в окно Properi ies.

Найдите раздел (DataBindings). Если свойства упорядочены по категориям (кон фигурация по умолчанию), данный раздел отображается в категории Data. Я пред почитаю упорядочивать свойства по алфавиту;

при этом раздел (DataBindings) расположен в верхней части списка свойств. Нам нужно связать содержимое стол бца OrderlD со свойством Text элемента управления. Раскройте раздел (Data Bindings), выберите элемент Text и щелкните направленную вниз стрелку, чтобы просмотреть список доступных столбцов. В списке указан объект DataSet. Раскрыв его узел, вы увидите список объектов DataTable. В нашем случае список содержит только один объект DataTable Ч Orders. Раскрыв узел этого объекта, вы увидите список объектов DataColumn. Щелкните столбец OrderlD (рис. 13-2).

Рис. 13-2. Связывание свойства Text элемента управления TextBox с одним из объектов DataColumn из состава DataSet Следуя инструкциям, приведенным ранее, создайте элементы управления Label и TextBox для столбцов CustomerlD, EmployeelD и OrderDate. Используйте те же префиксы имен и тот же формат свойства Text элементов управления. Свяжите Часть IV Создание эффективных приложений с использованием ADO.NET элементы управления TextBox с соответствующими столбцами объекта DataSet.

Расположите элементы управления, как показано на рис. 13-3.

:

X frmEdtQrdcri.vh [Btsign] Т! Г1 J и I Solution 'Chapter 13' (1 protect) ДР Chapter '+ i Rrftrsreti ] 3 uiismbl/Wo.vb fflffl *j] Исимвох Рис. 13-3. Добавление на форму элементов управления Label и TextBox Как видно, связать элемент управления TextBox с объектом DataSet в период разработки очень легко. Кроме того, такое связывание можно осуществить про граммно:

Visual Basic.NET txtOrderlD.DataBindings.AddCText", dsChapter13, "Orders.OrderlD") Visual C#.NET txtOrderlD.DataBindings.AddCText", dsChapter13, "Orders.OrderlD");

Этот код связывает свойство Text элемента управления со столбцом OrderlD таблицы Orders из состава объекта DataSet dsChapter!3.

Примечание В приложении я задал свойству Readonly элемента управления TextBox, связанного со столбцом OrderlD, значение True, чтобы исклю чить редактирование содержимого этого столбца пользователями. По умолчанию цвет текста, доступного только для чтения, в элементе уп равления TextBox Ч серый, а не черный. Лично мне это не нравится, и я задал свойству ForeColor данного элемента управления значение Black.

Кроме того, я назначил свойству TextAlignment элементов управления TextBox, связанных со столбцами OrderlD и EmployeelD, значение Right, поскольку названные столбцы содержат численные данные.

ГЛАВА 13 Создание эффективных Windows-приложений Этап 3. Получение данных Теперь у вас есть объект DataSet и несколько элементов управления TextBox, свя занных со столбцами этого объекта, однако нет никаких данных для вывода на экран. Запустив проект, вы увидите форму с пустыми элементами управления Label и TextBox. Это совершенно верно Ч ведь в объекте DataSet пока нет записей о заказах.

Если вы имеете опыт работы с предыдущими версиями Visual Basic, то, возможно, помните, что при выводе связанного элемента управления приложение автома тически выполняло соответствующий запрос и получало его результаты. В.NET все по-другому.

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

Visual Basic.NET Dim strCustomerlD As String = "ALFKI" Visual C#.NET string strCustomerlD = "ALFKI";

Затем добавьте в процедуру обработки события Load такую строчку:

Visual Basic.NET daOrders.SelectCommand.Parameters(O).Value = strCustomerlD daOrders.Fill(dsChapter13.Orders) Visual C#.NET daOrders.SelectCommand.Parameters[0].Value = strCustomerlD;

daOrders.Fill(dsChapter13.Orders);

Первая строка необходима потому, что запрос, созданный нами для объекта DataAdapter Ч параметризованный. Добавив этот код и запустив проект, вы уви дите на форме сведения о заказе.

Кроме того, в правый нижний угол формы приложения я добавил кнопку Close.

В ее событии Click я вызываю метод Close формы, чтобы завершить работу при ложения. Чтобы сделать то же самое в своем приложении, перетащите кнопку с панели инструментов, задайте ее свойству Name значение btnClose и свойству Text Ч значение Close. Дважды щелкните кнопку, чтобы просмотреть код обработки ее события Click. Дополните его следующими строками:

Visual Basic.NET Me.CloseO Visual C#.NET this.CloseO;

Создание эффективных приложений с использованием ADO.NET 494 Часть IV Этап 4. Добавление кнопок для перемещения по содержимому объекта DataSet На текущий момент приложение лишь отображает сведения о заказе. Можно до бавить код, который проверяет значение свойства Count объекта DataTable Orders из состава нашего объекта DataSet и сообщает, сколько возвращено записей. При ложение не очень-то полезно, если способно отображать лишь один заказ. Давайте реализуем функциональность, обеспечивающую просмотр числа возвращенных записей и перемещение между ними.

На форме приложения, под элементами управления с информацией о заказе, я добавил четыре кнопки и метку (рис. 13-4). Кнопки позволяют перемещаться по записям о заказах, а метка показывает текущий номер и общее число заказов.

Jjxl Гбб Glided of Рис. 13-4. Добавление на форму элементов управления для перемещения по содержимому объекта DataSet Элементы управления предоставляют данную функциональность при помощи экземпляра класса CurrencyManager. И хотя из имени класса следует, что он вы ступает в роли финансового консультанта, на самом деле именно этот класс обес печивает функционирование связывания с данными. Windows-форма предостав ляет свойство BindingContext, позволяй идее обращаться к объектам CurrencyManager, которые контролируют связанные с данными элементы управления формы.

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

Вместо того чтобы объяснять назначение и место отдельных строк кода, я решил показать фрагмент кода приложения. Он включает переменную CurrencyManager уровня формы. Код процедуры, обрабатывающей событие Load формы, инициа лизирует эту переменную и добавляет обработчики событий ItemChanged и Position Cbanged объекта CurrencyManager. Процедуры обработки этих событий задают текст метки, отображающей номер текущего заказа. Кроме того, есть процедуры, обра батывающие событие Click различных кнопок и соответствующим образом зада ющие значение свойства Position объекта CurrencyManager.

Создание эффективных Windows-приложений ГЛАВА Visual Basic.NET 'Переменная уровня формы Dim cmOrders As CurrencyManager Private Sub frmEditOrders_Load...

cmdrders = CType(BindingContext(dsChapter13, "Orders"), CurrencyManager) AddHandler cmOrders.ItemChanged, AddressOf cmOrders_ItemChanged AddHandler cmOrders.PositionChanged, AddressOf cmOrders_PositionChanged DisplayOrdersPosition() End Sub Private Sub DisplayOrdersPositlonO IblOrdersPosition.Text = "Order " & cmOrders.Position + 1 & _ " of " & cmOrders.Count End Sub Private Sub cmOrders_ItemChange6(ByVal sender As Object, ByVal e As ItemChangedEventArgs) DisplayOrdersPositionO End Sub Private Sub cmOrders_PositionChanged(ByVal sender As Object, ByVal e As System.EventArgs) DtsplayOrdersPositionO End Sub Private Sub btnOrdersMoveFirst_Click...

cmOrders.Position = End Sub Private Sub btnOrdersMovePrevious_Click...

cmOrders.Position -= End Sub Private Sub btnOrdersMoveNext_Click...

cmOrders.Position += End Sub Private Sub btnOrdersMoveLast_Click...

cmOrders.Position = cmOrders.Count - End Sub Visual C#.NET //Переменная уровня формы CurrencyManager cmOrders;

private void frmEdltOrders_Load...

Создание эффективных приложений с использованием ADO. NET 496 Часть IV i cmOrders = (CurrencyManager) BindingContext[dsChapterl3, "Orders"];

cmOrders.ItemChanged += new ItemChangedEventHandler(cinOrders_ItemChanged);

cmOrders, PosittonChanged += new EventHandler(cmOrders_PositionChanged);

DisplayOrdersPositionf) ;

!

private void DisplayOrdersPositionQ !

IblOrdersPosition.Text = "Order " + (cmOrders. Position + 1} + " of " + cmOrders, Count;

I private void cmOrders_ItemChanged(object sender, ItemChangedEventArgs e) i DisplayOrdersPositionf ) ;

private void cmOrders_PositionChanged(object sender, EventArgs e) \ DisplayOrdersPositionf ) ;

private void btnQrdersMoveFirst_Click(object sender, System. EventArgs e) { cmOrders. Position = 0;

} private void btnOrdersMovePrevious_Click(object sender, System. EventArgs e) { cmOrders. Position-;

} private void btnOrdersHoveNext_Click(object sender, System. EventArgs e) < cmOrders. Position**;

private void btnOrdersMoveLast_Click(object sender, System. EventArgs e) { cmOrders. Position = cmOrders. Count - 1;

;

Этап 5. Добавление кнопок Add и Delete Теперь пользователь получил возможность просматривать все записи о заказах, возвращенные объектом DataAdapter, а также изменять состав заказа, редактируя содержимое связанных элементов управления TextBox.

Создание эффективных Windows-приложений ГЛАВА Запустите форму и измените значение поля EmployeelD в первом заказе. Вы помните оригинальное и новое значение данного поля? Отредатировав значение поля EmployeelD в первом заказе, перейдите к следующему и затем снова верни тесь к первому заказу. Вы увидите, что новое значение поля EmployeelD по-пре жнему на месте. Я изо всех борюсь с желанием чудесным водевильным голосом спросить: Это введенное вами значение поля EmployeelD? И хотя форма позволяет редактировать содержимое заказа, добавлять и уда лять заказы нельзя... пока нельзя. Объект Currency Man age r предоставляет методы, позволяющие добавлять и удалять элементы из структуры данных, с которой свя заны элементы управления. Метод AddNeiv добавляет новый элемент, а метод RemoveAt Ч удаляет существующий элемент. Элементы управления, связанные с объектом Сшгепсу Manager, соответствующим образом реагируют на вызов любого из этих методов. Если вы добавили элемент с помощью метода AddNew, элементы управления отобразят содержимое новой записи. При вызове метода RemoveAt элементы управления отобразят содержимое следующей доступной записи.

Visual Basic.NET Private Sub btnOrdersAdd_CHck...

cmOrders.AddNewO End Sub Private Sub btnOrdersDelete_Click...

If cmOrders. Count > 0 Then cmOrders.RemoveAt (cmOrders. Position) Else MessageBox. Show("No Order to Delete!", "Delete Order", HessageBoxButtons.OK, MessageBoxIcon. Error) End If End Sub Visual C#.NET private void btnOrdersAdd_Click(object sender, System. EventArgs e) < cmOrders.AddNewO;

SetQrdersEditMode(true);

private void btnOrdersDelete_Click(object sender, System. EventArgs e) Хi if (cmOrders. Count > 0) cmOrders.HemoveAt (cmOrders. Posit ion);

else MessageBox. Show( "No Order to Deletel", "Delete Order", MessageBoxButtons.OK, MessageBoxIcon. Error);

498 Часть IV Создание эффективных приложений с использованием ADO.NET Этап 6. Передача изменений в БД Теперь наше приложение позволяет редактировать, добавлять и удалять заказы. Но как вы, возможно, заметили, приложение не передает эти изменения в БД. Наде юсь, вы достаточно хорошо помните материал глав 5 и 10, чтобы понимать, по чему это так. Связанные элементы управления изменяют содержимое DafaSet, однако у нас нет кода, вызывающего метод DataAdapter Update.

Мы создали объект DataAdapter с помощью мастера Data Adapter Configuration Wizard, и поэтому определять логику обновления не требуется. За нас ее создал мастер. Все, что надо сделать нам. Ч добавить на форму кнопку Submit Changes и вызвать в процедуре обработки события Click этой кнопки метод DataAdapter.Update, чтобы передать изменения в БД. Код перехватывает возвращаемое значение мето да Update объекта DataAdapter, указывающее число переданных в БД изменений.

Я добавил пару строк кода, которые выводят число измененных записей о за казах, при ошибке обновления перехватывают генерируемое исключение и вы водят диалоговое окно, если объект DataSet не содержит отложенных изменений, Суть этого кода Ч все тот же вызов метода DataAdapter,Update-, Visual Basic.NET If dsChapter13.HasChanges Then Try Dim intOrdersModified As Integer intOrdersModified = daOrders.Update(dsChapter13.Orders) Dim strOutput As String strOutput = "Modified " & intOrdersModified & " order(s)" MessageBox.Show(strOutput, "Update succeeded!", MessageBoxButtons,OK, MessageBoxIcon.Information) Catch ex As Exception MessageBox.Show(ex.Message, "Update failed!", HessageBoxButtons.OK, MessageBoxIcon.Error) End Try Else MessageBox.Show("No changes to submit!", "SubmitChanges", MessageBoxButtons.OK, MessageBoxIcon.Information) End If Visual C#.NET if (dsChapter13.HasChanges()> i try { int intOrdersModified;

intOrdersModified = daOrders.Update{dsChapter13. Orders);

string strOutput;

strOutput = "Modified " + intOrdersModified + " order(s)";

MessageBox.ShowfstrOutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon.Information);

Создание эффективных Windows-приложений ГЛАВА catch (Exception ex) MessageBox.Show(ex. Message, "Update failed!", MessageBoxButtons. OK, HessageBoxIcon.Error) ;

else MessageBox.Stiow("No changes to submit!", "SubmitChanges", MessageBoxButtons. OK, MessageBoxIcon. Information);

Этап 7. Добавление кнопок Edit, Update и Cancel Созданное нами приложение Ч довольно простое: на форме лишь несколько эле ментов управления. Тем не менее работа с приложением не настолько интуитив но понятна, как может показаться.

Запустите приложение и измените значение поля EmployeelD первого заказа.

Щелкните кнопку Submit Changes. Откроется диалоговое окно с сообщением об отсутствии изменений для передачи в БД. Что же произошло?

Согласно нашему коду, объект DataSel не содержит каких-либо изменений. Так что же случилось с только что внесенными вами изменением? Объект Currency Manager по-прежнему хранит его и пока не записал в объект DataSet. Если перей ти к следующему заказу и щелкнуть кнопку Submit Changes, изменение будет ус пешно передано в БД.

Объект CurrencyManager не подтверждает отложенные изменения объекта DataSet, пока не реализуется переход к другой записи или пока не будет вызван метод CurrencyMariagerEndCurrentEdit. Такое поведение в чем-то аналогично фун кционированию метода DataRowBeginEdtt. Изменения действительно записываются в объект DataRow лишь после вызова метода EndEdit.

Для передачи изменений стоит прямо перед вызовом метода DataAdapter,Update добавить вызов метода CurrencyManager EndCurrentEdit, Это вполне уместно в кон кретной ситуации, Но я предпочитаю другой способ.

На этапе 7 создания приложения я добавил кнопки Edit, Cancei и Update. При запуске приложения все данные в элементах управления TextBox доступны толь ко для чтения. Чтобы изменить содержимое заказа, следует щелкнуть кнопку Edit.

Лишь после этого лам удастся редактировать содержимое элементов управления TextBox (кроме элемента, связанного со столбцом OrderlD). Кнопки перемещения, а также кнопки Add, Edit, Delete и Submit Changes на период редактирования от ключаются. Для продолжения работы следует щелкнуть кнопку Update или Cancel (рис. 13-5). При щелчке кнопки Update изменения текущего заказа подтвержда ются. а при щелчке кнопки Cancel Ч отменяются.

На добавление такой функциональности требуется лишь несколько минут, и они тратятся с умом. В результате работа приложения станет более интуитивно понятной. Исчезнут проблемы с передачей изменений, связанные с тем, что из менения не подтверждены. Кроме того, вам не придется напрягаться, чтобы по нять, как и когда объект CurrencyManager подтверждает изменения содержимого DataSet.

Создание эффективных приложений с использованием ADO. NET Часть IV CudeilD Custom tD EmotojeelD:

Orcfei Dale.

Update i Cance Рис. 13-5. Принудительное подтверждение или отмена изменений в составе текущего заказа Я добавил в код формы процедуру SetOrdersEcUtMode, которая принимает ло гическое значение, указывающее, может ли пользователь редактировать текущий заказ. Процедура в соответствии с принятым значением задает значения свойств Readonly элементов управления TextBox, а также значения свойств Enabled кно пок. Процедура вызывается в событии Click кнопок Add, Edit, Update и Cancel.

Visual Basic.NET Private Sub SetOrdersEditMode(ByVal blnEdit As Boolean) txtCustomerlO. Readonly = Not blnEdit txtEmployeelD. Readonly = Not blnEdit txtOrderDate. Readonly = Not blnEdit btnOrdersHoveFirst. Enabled = Not blnEdit btnOrdersMovePrevious. Enabled = Not blnEdit btnQrdersMoveNext. Enabled = Not blnEdit btndrdersMoveLast. Enabled = Not blnEdit btnOrdersCancel. Enabled = blnEdit btnOrdersUpdate. Enabled = blnEdit btndrdersEdit. Enabled = Not blnEdit btnOrdersAdd.Enaoled = Not blnEdit btnOrdersDelete. Enabled = Not blnEdit btnSubmitChanges, Enabled = Not blnEdit End.Sub Visual C#.NET private void SetOrdersEditMode(bool blnEdit) { txtCustoroerlD. Readonly = ! blnEdit;

txtEmployeelD. Readonly = IblnEdit;

txtOrderDate. Readonly = IblnEdit;

btnOrdersMoveFirst. Enabled = IblnEdit;

ЬОI Создание эффективных Windows-приложений ГЛАВА btnOrdersHovePrevious.Enabled = IblnEdit;

btnOrdersMoveNext.Enabled = IblnEdit;

btnOrdersMoveLast.Enabled = IblnEdit;

btnOrdersCancel.Enabled = blnEdit;

btnOrdersUpdate.Enabled = blnEdit;

btnOrdersEdit.Enabled = IblnEdit;

btnOrdersAdd.Enabled = IblnEdit;

btnOrdersDelete.Enabled = IblnEdit;

btnSubmitChanges.Enabled = IblnEdit;

Этап 8. Просмотр дочерних данных Теперь наше приложение позволяет просматривать и изменять данные таблицы Orders. Тем не менее такая функциональность не очень-то полезна, если не пре доставить возможность изменять состав заказов.

На рис. 13-6 показан пользовательский интерфейс следующей версии нашего приложения. Как видно, я добавил сетку, отображающую данные из таблицы Order Details. Когда вы переходите от одного заказа к другому, в сетке отображаются только связанные с этим заказом записи.

Рис, 13-6. Просмотр заказа и связанных с ним товаров Чтобы добавить данную функциональность в приложение:

1. добавьте объект DataAdapter, выбирающий все записи таблицы Order Deiails, соответствующие заказам клиента;

2. воссоздайте объект DataSet со строгим контролем типов, чтобы в будущем добавить в него объект DataTable с новыми данными, полученными на первом этапе. После этого добавьте в объект DataSet объект DataRelation, чтобы упро стить поиск связанных с заказом товаров;

3. добавьте на форму элемент управления DataGrid и свяжите с объектом DataSet таким образом, чтобы в нем отображались лишь товары из состава текущего заказа;

4. добавьте в процедуру, обрабатывающую событие Click кнопки Submit Changes, логику для передачи в БД изменений из обоих объектов DataTable.

502 Часть IV Создание эффективных приложений с использованием ADO.NET Получение только списка заказанных клиентом товаров Создать объект DataAdapter, получающий только заказы конкретного клиента, весьма просто, поскольку в таблице Orders определен столбец CustomerlD. Б таб лице Order Details такого столбца нет. Таким образом, запрос, получающий толь ко список заказанных клиентом товаров, должен одновременно ссылаться на таб лицы Order Details и Orders.

Есть несколько способов структурировать такой запрос. Бот три из них:

SELECT D.OrderlD, D.ProductlD, D.UnitPrice, 0.Quantity FROM Orders 0 INNER JOIN [Order Details] D ON O.OrderlD = D.OrderlD WHERE 0.CustomerlD = ?

SELECT OrderlD, ProductIO, UnitPrice, Quantity FROM [Order Details] WHERE OrderlD IN (SELECT OrderlD FROM Orders WHERE CustomerlD = ?) SELECT D.OrderlD, D.ProductlD, D.UnitPrice, D.Quantity FROM [Order Details] D, Orders WHERE D.OrderlD = 0,OrderlD AND 0.CustomerlD = ?

Согласно SQL Query Analyzer, выводящему примерный план выполнения, SQL Server создает для каждого из этих запросов одинаковый план, По двум причи нам я отдаю предпочтение синтаксису третьего запроса. Во-первых, он самый интуитивно понятный. Во-вторых, мастер Data Adapter Configuration Wizard не способен обработать такой синтаксис из-за наличия параметра в подзапросе.

Похоже, что мастер использует преимущественно синтаксис INNER JOIN, И хотя при создании приложения я использовал синтаксис третьего из приведенных выше запросов, мастер преобразовал мой SQL-оператор согласно синтаксису первого запроса. Каждому Ч свое.

Создав с помощью этого запроса новый объект OleDbDataAdapater, назовите его daDetails.

Добавление объекта DataTable Order Details в класс DataSet со строгим контролем типов Добавив объект DataAdapter для выборки записей из таблицы Order Details, мож но воспользоваться диалоговым окном Generate Dataset и добавить в класс DataSet со строгим контролем типов новый объект DataTable. Подробнее Ч на рис. 13-"?, Выберите из списка имеющихся объектов DataSet нужный. Убедитесь, что в спис ке таблиц в средней части диалогового окна помечена только одна таблица Ч та.

которая соответствует новому объекту DataAdapter.

Щелкните ОК, и Visual Studio.NET добавит новый объект DataTable в имеющийся класс DataSet со строгим контролем типов. Если оставить помеченными обе таб лицы, Visual Studio.NET перезапишет объект DataTable Orders, имеющийся в объекте DataSet со строгим контролем типов. Это означает, что значения свойств Autolncre rnentSeed \\AutoIncrementStep пропадут. Сбрасывать эти свойства нетрудно, но уто мительно.

Теперь, когда класс DataSet со строгим контролем типов содержит объекты DataTable, соответствующие таблицам Orders и Order Details, можно добавить объект ГЛАВА 13 Создание эффективных Windows-приложений DataRelation, определяющий между этими двумя объектами DataTable отношение на основе столбца OrderlD. В окне Solution Explorer дважды щелкните.xsd-файл класса, чтобы запустить конструктор XML Schema Designer. Перетащите столбец CustomerlD из объекта DataTable Orders в объект DataTable Order Details. Прими те предлагаемые конструктором значения параметров по умолчанию.

Genлate a dataset that inekjdes ?he specified tables, Choose a dataset:

* Enisling fchapteri 3. ifsdCbapterTs f" IJtew: I,' гл'--''.

Cfioose which tablets) to add tothedataset:

Orders (daOrders) '& fidd Ehhctataset to the designer.

Help Cancel Рис. 13-7. Добавление объекта DataTable Order Details в класс DataSet со строгим контролем типов Приложение также добавляет вычисляемое поле, отображающее общую сто имость заказанных единиц конкретного товара, Вы можете сделать то же самое, добавив в объект DataTable Order Details новый столбец. Задайте тип данных это го столбца как Decimal и задайте свойству Expression значение UnitPrice 'Quantity.

Закройте конструктор и сохраните изменения.

Добавление элемента управления DataGrid, отображающего дочерние данные Связать элемент управления DataGrid с данными очень просто. Достаточно задать значения двух свойств Ч DataSource и DataMember, Добавьте на форму элемент управления DataGrid и задайте ему имя gridDetails.

В окне Properties выберите свойство DataSource. Расткройте список справа, что бы просмотреть перечень доступных источников данных. Вы увидите объект DataSet и отдельные объекты DataTable. Выберите свойство DataMember. Для него также имеется список возможных значений (рис. 13-8). Раскрыв в списке узел объекта DataTable Orders, вы увидите созданный вами ранее объект DataRelation.

Если задать свойству DataMember значение DataRelation, элемент управления Data Grid отобразит только дочерние записи, используя объект DataRelation.

Чтобы связать элемент управления DataGrid с данными в период выполнения, воспользуйтесь таким кодом:

Часть IV Создание эффективных приложений с использованием ADO.NET Visual Basic.NET gridDetails.DataSource = dsChapter gridOetails.DataMemtier = "Orders. Order_x0020_Details' Visual C#.NET gridDetails.DataSource = dsChapter13;

gridDetails. DataHember = "Orders. Orcter_x0020_Details' Properties 9. x >yst em. Windows, Forms, DataGi f\ cdumnHeaderiVisibli True Х\ ContextMenu (none) _H0020_Petaib Order Details - |Tj Orders : GndLineCalor ^] Control GridLine5tyle 5oiid HeaderBackColar |_J Control S.Headeifont Microsoft Sans 5er(f, HeaderForeCobr ^ ControlText | ImeMode No Control Рис. 13-8. Задание значения свойства DataMember элемента управления DataGrid, отображающего дочерние записи В приложении, в конце процедуры, обрабатывающей событие Load формы, есть строка кода, вызывающая процедуру FormatDetailsGrid. Эта процедура добавляет в элемент управления DataGrid новый объект DataGrid'iableStyle, позволяющий управлять внешним видом элемента управления: выбирать отображаемые столбцы, а также задавать для каждого из них размер, формат и параметры выравнивания.

Передача в БД изменений из обеих таблиц Глава 11 посвящена проблемам передачи в БД иерархических изменений. По су ществу, новые записи требуется передавать, начиная с верхней части иерархии (сначала записи о заказах, потом записи о составе этих заказов), а удаленные за писи Ч начиная с нижней ее части (сначала записи о составе заказов, затем за писи о заказах). Таким образом, нельзя взять и целиком передать вызываемому методу DataAdapter.Update объект DataTable.

Для начала следует передать в БД информацию о новых и измененных зака зах. После этого Ч все изменения таблицы Order Details. Затем Ч удаленные за казы. Изучив код процедуры, обрабатывающей событие Click кнопки Submit Changes, вы увидите, что он использует такую же логику. Я привожу этот код ниже, опус тив для удобочитаемости блок Try/Catch.

Создание эффективных Windows-приложений ГЛАВА Visual Basic.NET Dim intOrdersModified, intDetallsModified As Integer Dim aRowsToUpdate As DataRow() Dim dvrs As DataViewRowState 'Передаем новые или измененные заказы dvrs = DataViewRowState.Added Or DataViewRowState.ModifiedCurrent aRowsToUpdate = dsChapter13.Orders.SelectC"", "", dvrs) intOrdersModified = daOrders.Update(aRowsToUpdate) 'Передаем все изменения объекта DataTable, 'соответствующего таблице Order Details intDetailsModified = daDetails.Update(dsChapter13.0rder_Details) 'Передаем удаленные заказы dvrs = DataViewRowState.Deleted aRowsToUpdate = dsChapter13.Orders.Select("", "", dvrs) intOrdersModified += daOrders.Update(aRowsToUpdate) Dim strOutput As String strOutput = "Modified " & intOrdersModified & " order(s)" & vbCrLf & _ "Modified " & intOetailsHodified & " detail(s)" MessageBox.Show(strOutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon,Information) Visual C#.NET int intOrdersHodified, intDetailsModified;

DataRow[] aRowsToUpdate;

DataViewRowState dvrs;

//Передаем новые или измененные заказы dvrs = DataViewHowState.Added ] DataViewRowState.ModifiedCurrent;

aRowsToUpdate = dsChapter13.Orders.SelectC'", "", dvrs);

intOrdersModified = daOrders.Update(aRowsToUpdate);

//Передаем все изменения объекта DataTable, //соответствующего таблице Order Details intDetailsModified = daDetails.Update(dsChapter13.0rder_Details);

//Передаем удаленные заказы dvrs = DataViewRowState.Deleted;

aHowsToUpdate = dsChapter13.Orders.SelectC'", "", dvrs);

intOrdersModified += daOrders.Update(aRowsToUpdate);

string strOutput;

strOutput = "Modified " + intOrdersModified + " order(s)\n\r" + "Modified " + IntDetailsHodified + " detail(s)";

MessageBox.Show(strQutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon.Information);

Этап 9. Связывание второй формы с тем же источником данных Связать несколько элементов управления на разных формах с одним источником данных сложно, но можно. Прежде чем подробно рассматривать процесс такого связывания, я отвлекусь и расскажу, как усовершенствовать интерфейс приложе ния, чтобы сделать редактирование данных еще более простым, Часть IV Создание эффективных приложений с использованием ADO.NET Элемент управления DataGrid Ч полезное и мощное средство. Я часто исполь зую такие элементы управления для вывода содержимого нескольких записей. Тем не менее мне не очень-то нравится ситуация, когда пользователю предоставлена возможность редактировать данные непосредственно в DataGrid. Вы, вероятно, знаете, что изменения в элементе управления DataGrid удается отменить, нажав Escape или Ctrl+Z, однако редко кто из начинающих пользователей понимает, что это вообще возможно.

Не надеясь, что пользователи обнаружат и запомнят этот способ, я предпочи таю сделать процесс редактирования более интуитивным, даже если он при этом и удлинится на пару этапов. Итак, на этапе 9 создания приложения я сделал объект DataGrid со сведениями о составе конкретного заказа доступным только для чте ния. Чтобы изменить информацию о заказанных товарах, пользователь должен щелкнуть кнопку Ч то же требование, что и к редактированию информации о заказе. Если пользователю понадобится изменить информацию о заказанном то варе, приложение откроет модальную форму (рис. 13-9), позволяющую изменить нужную запись.

Рис. 13-9. Редактирование информации о заказанном товаре на новой форме Здесь можно редактировать запись о товаре и точно так же, как при измене нии записи о заказе, для прекращения редактирования следует щелкнуть кнопку Update или Cancel.

Теперь вернемся к связыванию нескольких элементов управления, расположен ных на разных формах, с одним источником данных. Элементы управления Text Box новой формы связаны с той же записью данных, которая отображается в элемен те управления DataGrid основной формы. И хотя связать таким образом элемен ты управления в период разработки нельзя, это допустимо в период выполнения, как показано ниже.

Просмотрев код, выполняющийся по щелчку кнопки Edit, расположенной под элементом управления DataGrid, вы увидите, что он создает экземпляр формы с информацией о заказанных товарах и затем вызывает метод EditDetail этой фор мы. Данный метод принимает в качестве параметра объект Currency Manager.

Код метода EditDetail показан далее. Как видно, он использует объект Currency Manager, чтобы связать элементы управления TextBox на новой форме с той же записью данных. Свойство Current объекта CurrencyManager возвращает объект DataRowView. Элементы управления TextBox можно связать с объектом DataView, чтобы код на основе свойства DataView объекта DataRowView обращался к объек ту DataView. с которым связан возвращенный объект DataRowView, Создание эффективных Windows-приложений ГЛАВА Visual Basic.NET Dim drvDetail As DataRowView Dim vueDetail As DataView Public Sub EditDetail(ByVal cm As CurrencyManager) drvDetail = CType(cm.Current, DataRowView) vueDetail = drvDetail.DataView Me.BindingContext(vueDetail).Position = cm.Position txtOrderID,DataBindings.Add("Text", vueDetail, "OrderlD") txtProductID.DataBindings.Add("Text", vueDetail, "ProductID") txtUnitPrice.DataBindings.Add("Text", vueDetail, "UnitPrice") txtQuantlty.DataBindings.Add("Text", vueDetail, "Quantity") txtltemTotal.DataBindings.Add("Text", vueDetail, "ItemTotal") If He.ShowDialog = DialogHesult.OK Then cm.EndCurrentEditO Else cm.CancelCurrentEdit() End If End Sub Visual C#.NET DataRowView drvDetail;

DataView vueDetail;

public void EditDetail(CurrencyManager cm) { drvDetail = (DataRowView) cm.Current;

vueDetail = drvDetail.DataView;

this.BindingContext[vueDetail].Position = cm,Position;

txtOrderID.DataBindings.Add("Text", vueDetail, "OrderlD");

txtProductID.DataBindings.Add("Text", vueDetail, "ProductID");

txtUnitPrice.DataBindings.Add("Text", vueDetail, "UnitPrice");

txtQuantity.DataBindings.AddC'Text", vueOetail, "Quantity");

txtItemTotal.DataBindings.Add{"Text", vueDetail, "ItemTotal");

if (this.ShowDialogC) == DialogResult.OK) cm. EndCurrentEditO;

else cm. CancelCur rentEditO;

Часть IV Создание эффективных приложений с использованием ADO.NET Этап 10. Совершенствование пользовательского интерфейса Наше приложение позволяет просматривать и редактировать информацию о за казах клиента, однако стоит сделать кое-что еще, чтобы работать с ним стало еще удобнее.

Если есть выбор, большинство пользователей предпочитает при работе с фор мой иметь дело с подробной информацией, а не с шифрованными ключевыми сведениями. Например, дочерняя форма, позволяющая редактировать запись о товаре, требует, чтобы пользователь знал значение ключа, а не название товара.

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

Посмотрим, как представить данные в более понятном формате (рис. 13-Ю).

Рис. 13-Ю. Отображение данных в понятном формате Добавление функции поиска с использованием элемента управления ComboBox На рис. 13-10 я заменил элемент управления TextBox, отображавший значение поля EmployeelD текущей записи о заказе, элементом управления ComboBox, отобра жающим имя принявшего заказ сотрудника. Добавить такую функциональность на самом деле очень просто Ч достаточно задать значения четырех свойств эле мента управления ComboBox.

Прежде чем добавить на форму элемент управления ComboBox и проверить предлагаемую функциональность в действии, следует добавить объект DataAdapter, возвращающий информацию из таблицы Employees. Я создал этот объект посред ством следующего запроса:

SELECT EmployeelD, LastName + Х*Х FirstNarne AS EmployeeName FROM Employees Добавив объект DataAdapter для выборки информации о сотрудниках, нужно создать экземпляр объекта DataSet со строгим контролем типов и добавить в него объект DataTable Employees.

Нам нужно, чтобы элемент управления ComboBox отображал значения из стол бца EmployeeName объекта DataTable Employees. Когда пользователь выбирает из Создание эффективных Windows-приложений ГЛАВА списка имя сотрудника, Combo Box должен принять значение из столбца EmployeelD и поместить его в поле EmployeelD текущей записи о заказе. На самом деле мы свяжем элемент управления ComboBox одновременно с двумя разными источни ками данных Ч с объектом DataTable Employees и с текущей записью о заказе.

Примечание Приложение не будет изменять содержимое таблицы Employees, и поэтому создавать логику обновления для объекта DataAdapter, взаи модействующего с таблицей Employees, не требуется. Чтобы сообщить об отсутствии такой необходимости мастеру, достаточно щелкнуть в окне SQL Statement переключатель Advanced Options.

Сначала давайте посмотрим, как связать элемент управления ComboBox с объек том DataTable Employees. У ComboBox есть свойство DataSource, значение кото рого задается так же, как и значение одноименного свойства элемента управле ния DataGrid. Выберите из списка доступных источников данных объект DataSet, Свойство DisplayMember определяет, значениями какого столбца ComboBox запол няет список, и принимает имя соответствующего столбца. В нашем случае это имя столбца EmployeeName объекта DataTable Employees. Затем задайте свойству Value Member имя столбца EmployeelU объекта DataTable Employees.

Все, что осталось сделать, Ч сопоставить элемент управления ComboBox с полем EmployeelD текущей записи о заказе. Это осуществляется аналогично связыванию свойства Text элемента управления TextBox со столбцом объекта DataSet. Найди те в окне Properties раздел (DataBindings), раскройте его. найдите свойство Selected Value и свяжите это свойство со столбцом EmployeelD объекта DataTable Orders, В период выполнения осуществить все это позволяет следующий код:

Visual Basic.NET cboEmployee.DataSource = dsChapter cboEmployee,DisplayMember = "Employees.EmployeeName" cboEmployee.ValueMember = "Employees.EmployeelD" cboEmployee,DataBindings.AddC'SelectedValue", dsChapter13, "Orders.EmployeelD") Visual C#.NET cboEmployee.DataSource = dsChapter13;

cboEmployee.DisplayMember = "Employees.EmployeeName";

cboEmployee.ValueHember = "Employees.EmployeelD";

cboEmployee. DataBindings. AddC'SelectedValue", dsChapteMS, "Orders.EmployeelD");

Теперь при перемещении по записям о заказах будет отображаться значение поля EmployeeName, а не поля EmployeelD. Кроме того, можно отредактировать запись о заказе и изменить значение поля EmployeelD, изменив имя сотрудника, отображаемое в элементе управления ComboBox, Управление форматом связанных данных Тип данных столбца UnitPrice объекта DataSet Ч Decimal. Как следствие, элемент управления TextBox на форме с информацией о заказанных товарах, связанный Часть IV Создание эффективных приложений с использованием ADO,NET со столбцом UnitPrice, отображает содержимое этого столбца с использованием стандартного числового формата. Стоимость единицы товара, равная S4-50. будет отображаться в TextBox как 45 Можно написать код, чтобы вручную изменить формат отображения данных на более подходящий.

В одном из предыдущих разделов главы я приводил фрагмент кода, связываю щий свойство Text элемента управления TextBox со столбцом объекта DataView.

Visual Basic.NET txtOrderID.DataBindings.Add("Text", dsChapter13, "Orders.OrderlD") Visual C#.NET txtOrderID.DataBindings.Add("Text", dsChapter13, "Orders.OrderlD");

Метод Add возвращает объект Binding, который реагирует на события объекта CurrencyManager и перемещает данные между элементом управления TextBox и столбцом, с которым этот элемент связан, Объект Binding предоставляет два события Ч Format и Parse. Событие Format наступает, когда объект Binding загружает данные из источника в свойство, с ко торым связан. Событие Parse наступает, когда объект Binding считывает данные из связанного свойства и передает их в источник. С помощью этих двух событий удается управлять форматом отображения данных в связанных элементах управ ления TextBox.

Следующий фрагмент кода приложения выводит значение поля UnitPrice в элементе управления TextBox с использованием формата, соответствующего типу данных currency. Изменение формата представления данных осуществляется с помощью метода метод ToString класса Decimal Visual Basic.NET Public Sub EditDetail(ByVal cm As CurrencyManager) Dim b As Binding b = txtUnitPrice.DataBlndings.Add("Text", vueDetail, "UnitPrice") AddHandler b.Format, AddressOf DecimalToCurrencyString AddHandler b.Parse, AddressOf CurrencyStringToDecimal End Sub Private Sub DecimalToCurrencyString(ByVal sender As Object, ByVal cevent As ConvertEventArgs) If Not cevent.DesiredType Is GetType(String) Then Exit Sub End If If cevent.Value Is DBNull.Value Then cevent.Value = CDec(0).ToString("c") Else cevent.Value = CDec(cevent.Value).ToString("c") ГЛАВА 13 Создание эффективных Windows-приложений 51 End If End Sub Private Sub CurrencyStringToDecimal(ByVal sender As Object, ByVal cevent As ConvertEventArgs) If Not cevent. DesiredType Is GetType(Decimal) Then Exit Sub End If cevent. Value = Decimal. Parse(cevent. Value. ToString, Globalization. NumberStyles. Currency, Nothing) End Sub Visual C#.NET public void EditDetail(CurrencyManager cm) Binding b;

b = txtUnitPrice.DataBindings.Add( T Text", vueDetail, "UnitPrice");

b. Format += new ConvertEventHandler(DecimalToCurrencyString);

b. Parse += new ConvertEventHandler(CurrencyStringToDecimal);

:

private void DecimalToCurrencyString(object sender, ConvertEventArgs cevent) { if { I cevent. DesiredType. Equals(typeof (string))) return;

if (cevent. Value == DBNull. Value) cevent. Value = ((Decimal) 0).ToString("c");

else cevent. Value = ((Decimal) cevent. Value). ToStrlngC'c");

I private void CurrencyStringToDecimal(object sender, ConvertEventArgs cevent) ;

if (! cevent. DesiredType. Equals(typeof( Decimal))) return;

cevent. Value = Decimal. Parse(cevent. Value. ToStringO, System. Globalization. NumberStyles. Currency, null);

Создание эффективных приложений с использованием ADO.NET 512 Часть IV Этап 11. Если хочешь сделать что-то хорошо...

Давайте ненадолго прервемся и окинем взглядом созданное нами приложение.

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

При первоначальном связывании элементов управления у нас были весьма ограниченные возможности для контроля их взаимодействия с данными объекта DataSet. На этапе 10 мы добавили код, изменивший формат представления дан ных в элементах управления TextBox. В папке прилагаемого компакт-диска, соот ветствующей этапу 10 разработки приложения, также записан код, позволяющий форматировать и принимать значения Null. Можно добавить дополнительный код, расширяющий возможности контроля связанных элементов управления, но по мните: чем больше кода вы пишете, тем меньше выгоды от первоочередной став ки на связывание с данными.

Приведу пример. Я закончил этап 10 и начал работать с приложением- приме ром, попутно обдумывая, как бы его усовершенствовать. Выяснилось, что на фор ме Edit Detail при изменении содержимого элементов управления TextBox, соот ветствующих столбцам Quantity и Price, не обновлялось содержимое элемента управления TextBox ItemTotal. В связи с этим я решил поискать способ, который позволил бы автоматически обновлять содержимое элемента управления TextBox ItemTotal при изменении содержимого элементов управления TextBox, соответ ствующих столбцам Quantity и Price. Я пробовал задавать значение свойства Text элемента управления TextBox ItemTotal в событии Leave элементов управления TextBox, соответствующих столбцам Quantity и Price. Пытался вызывать метод CurrencyManagerRefresh. Старался задействовать методы CurrencyManager.Suspend Binding и CurrencyManagerResumeBinding. Чего я только не делал, но так и не смог найти нужной функциональности. Полагаю, эту задачу удастся решить при помо щи связывания с данными, но все же у связывания с данными другое предназна чение.

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

Если для вывода данных в элементах управления и передачи изменений в струк туру данных вы используете собственный код, вы полностью управляете и отве чаете за взаимодействие пользовательского интерфейса и структур данных. На эта пе 11 данные в элементах управления TextBox выводятся уже без помощи связы вания с данными. Просмотрев код формы Edit Orders, вы увидите процедуру Sbow CurrentOrder, а также код, определяющий, какой заказ и названия каких товаров отображать с использованием объектов DataView. Кроме того, формы включают код для проверки данных, вводимых в различные элементы управления TextBox.

ГЛАВА 13 Создание эффективных Windows-приложений Если пользователь указал недопустимое значение, например Четверг вместо цены товара, система выдаст сообщение соответствующего содержания.

Резюме: связывание с данными Как показывают различные этапы разработки приложения, средства связывания с данными, предоставляемые пакетом Windows Forms, позволяют создать мощный и надежный пользовательский интерфейс при минимуме кода. Тем не менее, пы таясь расширить возможности управления этим интерфейсом и добавляя в при ложение собственный код, вы в некоторых случаях обнаруживаете, что не допол няете средства связывания с данными, а сражаетесь с ними. Б этом случае лучше написать собственный код, реализующий взаимодействие данных и пользователь ского интерфейса.

Проблемы разработки приложений Создание полезного и интуитивно-понятного пользовательского интерфейса лишь один из аспектов разработки эффективного Windows-приложения. Давайте рассмотрим другие, не менее важные вопросы.

Выборка только необходимых данных При создании приложения важно учитывать потенциальное увеличение размеров БД. Б самом начале разработки запросы SELECT..FROM Таблица могут выполнять ся просто отлично, однако с ростом размера таблицы на выборку результатов запроса потребуется больше времени. Чем больше данных выбирается, чем боль ше времени потребуется.

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

Один из факторов, определяющий, какие данные выбирать, Ч среда приложе ния. В некоторых случаях пользователю требуется загрузить данные на ноутбук по модему на 28 кбит/с, поработать с ними в автономном режиме, изменить их и затем по все тому же модему подключиться к БД и передать отложенные измене ния. Обсуждаемый канал имеет весьма низкую пропускную способность, которую нет смысла расходовать попусту, однако в соответствии с требованиями среды приложения вам все равно придется загрузить на ноутбук из БД все необходимые данные.

Стратегии обновления Приложение-пример кэширует изменения и передает обновления с использова нием оптимистического управления блокировками. Сейчас я расскажу о других стратегиях обновления Создание эффективных приложений с использованием ADO.NET 514 Часть IV Мгновенные и котированные обновления Решение о том, следует ли передавать изменения в БД мгновенно или кэшировать их и передавать их позже, зависит от потребностей вашего приложения.

Когда пользователь изменяет запись о заказе, приложение-пример не переда ет это изменение в БД немедленно. Оно средствами ADO.NET кэширует это об новление и передает его лишь по щелчку кнопки Submit Change.

Приложение легко модифицировать, чтобы изменение записи о заказе пере давалось в БД по щелчку кнопки Update. Когда пользователь щелкнет кнопку Edit, приложение позволит изменить информацию о заказе и его составе. Если пользо ватель щелкнет кнопку Cancel, приложение отбросит сделанные изменения. Пос ле щелчка кнопки Update приложение, наоборот, сохранит изменения и затем с помощью объекта DataAdapter передаст их в БД.

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

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

Повторная выборка перед разрешением изменений Данные в отсоединенной структуре, например DataSet, могут потерять актуальность.

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

Рассмотрим наше приложение-пример. При запуске оно выбирает данные.

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

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

Для повторной выборки содержимого записи создайте объект DataAdapter, выполняющий параметризованный запрос следующего вида:

SELECT,. FROM Таблица WHERE ПолеКлюча = ?

ГЛАВА 13 Создание эффективных Windows-приложений Если значение свойства PrimaryKey объекта DataTable определено, объект Data Adapter обновит содержимое объекта DataRow, используя данные из БД. Помни те: этот запрос не сгенерирует исключение, если другой пользователь удалил в БД соответствующую запись. В этом случае он просто не вернет записей. Метод DataAdapterFill возвращает целое число, соответствующее количеству выбранных из БД записей. Если метод Fill вернул 0, записи в БД больше нет. Можно обрабо тать эту ситуацию и изящно известить пользователя об отсутствии нужной записи.

ADO.NET и пессимистическое управление блокировками Даже если изменения не кэшируются и сразу передаются в БД и, прежде чем пре доставить пользователю возможность редактирования записи, вы повторно вы бираете ее содержимое из БД, то и в этом случае при попытке обновления возмо жен отказ, поскольку на данные на сервере не наложена блокировка. Успех по пытки обновления гарантирует лишь пессимистическое управление блокировками.

Внимание! Пессимистическое управление блокировками Ч мощное и даже опасное средство. Бойтесь его. Сильно бойтесь.

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

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

Если при обновлении данных используется пессимистическое управление блокировками, перед редактированием содержимого на запись БД следует нало жить блокировку'. Это гарантирует, что при обновлении не возникнут сбои из-за того, что запись успел изменить другой пользователь. Объектная модель ADO.NET предназначена для передачи обновлений с применением оптимистического уп равления блокировками. Как вы помните из глав ] 0 и 11, объект DataAdapter по зволяет передавать в БД отложенные изменения из объекта DataSet. Когда вы из меняете содержимое объекта DataRow, DataSet не налагает блокировку на соот ветствующую запись БД. В ADO.NET, по крайней мере в первой версии этой мо дели, у объектов нет свойств, позволяющих организовать пессимистическое уп равление блокировкой.

И все же задействовать такой тип управления блокировкой можно, воспользо вавшись объектами Transaction. Тем не менее в транзакциях обычно выполняют нечто более сложное, чем простой запрос SELECT. Налагаются ли блокировки на данные при выполнении запроса SELECT в транзакции, зависит от БД, типа запроса и уровня изоляции транзакции.

Уровень изоляции транзакции определяет, как работа, выполненная в одной транзакции, повлияет на работу, выполняемую в других транзакциях. Уроиень изоляции транзакций в SQL Server по умолчанию Ч Read Committed (чтение при подтвержденной транзакции). При этом на запись, измененную в транзакции, сразу же налагается блокировка. При простой выборке содержимого записи с помощью 516 Часть IV Создание эффективных приложений с использованием ADO.NET запроса SELECT блокировка не налагается. Но если используется уровень изоля ции Repeatable Read (повторяемое чтение) или Scrializable (серийный), то блоки ровка налагается и после выборки содержимого записи средствами запроса SELECT.

Некоторые БД позволяют использовать в запросах указания по блокировке. Б SQL Server следующий запрос налагает на запись блокировку независимо от уровня изоляции транзакции:

SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WITH (UPDLOCK) WHERE CustomerlD = 'ALFKI' Подробнее о поддерживаемых уровнях изоляции транзакций и указаниях по блокировке Ч в документации вашей СУБД.

Следующий фрагмент кода при помощи объекта OleDbTransaction и указаний по блокировке, включенных в запрос SELECT, пессимистично налагает блокиров ку на запись БД MSDE. Когда код вернет результаты запроса, на сервере на запись налагается блокировка. Можно определить точку останова после вызова метода DataAdapterJFill и убедиться в наличии блокировки. На этом этапе вы сможете просмотреть содержимое записи при помощи средства выполнения произволь ных запросов, например утилиты SQL Server Query Analyzer, но изменить это со держимое не удастся.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Oata Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT CustomerID, CompanyName FROM Customers " & "WITH (UPDLOCK) WHERE CustomerlO = 'ALFKI'" ' Dim en As New OleDbConnection(strConn) en.Open() Dim txn As OleDbTransaction = cn.BeginTransaction Dim cmd As New 01eDbCommand(strSQL, en, txn) Dim da As New OleDbDataAdapter(cmd) Dim cb As New OleDbCommandBuilder(da) Dim tbl As New DataTableO da.Fill(tbl) Dim row As DataRow = tbl.Rows(O) row("CompanyName") = "Modified" da.Update(tbl) txn.RollbackQ cn.CloseO Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

StrSQL = "SELECT CustomerlD, CompanyName FROM Customers " + "WITH (UPDLOCK) WHERE CustomerlD = 'ALFKI'";

OleDbConnection en = new QleDbConnection(strConn);

cn.0pen();

ГЛАВА 13 Создание эффективных Windows-приложений OleDbTransaction txn = cri.BeginTransactionO;

OleDbCommand cmd = new OleDbCommandCstrSQL, en, txn);

OleDbDataAdapter da = new OleDbDataAdapter(cmd);

OleDbCommandBuilder cb = new OleDbCotnmandBuilder(da);

DataTable tbl = new DataTableO;

da.Flll(tbl);

DataRow row = tbl.Rows[0];

row["CompanyName"] = "Modified";

da.Update(tbl);

txn.Rollback();

cn.Close{);

Примечание В данном фрагменте кода объект CommandButtder используется исключительно для краткости. Подробнее о преимуществах создания собственной логики обновления Ч в главе 11.

Стратегии подключения Стратегий подключения к БД две, ваш выбор зависит от параметров приложения.

Подсоединение и отсоединение Простейший способ подключения к БД Ч позволить объектам DataAdapter неяв но открывать соединение. В созданном нами приложении используется именно он. При вызовах методов DataAdapter fill и DataAdapter.Update объект DataAdapter неявно открывает соединение, а по завершении яызовов Ч закрывает его, Хотя данный способ прост, он не всегда является самым лучшим.

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

Оптимальное решение Ч гибрид двух этих методов. При необходимости при ложение явно открывает соединение с БД. Это напоминает способ, при котором объекты DataAdapter управляют состоянием соединения. Приложение при запус ке выполняет такой код:

Visual Basic.NET daProducts.Fill(dsChapter13.Products) daEmployees.Fill(dsChapter13.Employees) daOrders.SelectCommand.Parameters(O).Value = strCustomerlD daOrders.Fill(dsChapteM3. Orders) daDetails.SelectCommand.Parameters(O). Value = str Customer-ID daDetails.Fill(dsCnapter13.0rder_Details) Создание эффективных приложений с использованием ADO.NET 518 Часть IV Visual C#.NET daProducts.Fill(dsChapter13.Products);

daEmployees.Fill(dsChapter13.Employees);

daOrders.SelectCommand.Parameters[0].Value = strCustomerlD;

daOrders.Fill(dsChapter13.Orders);

daDetails.SelectCommand.Parameters[Q].Value = strCustomerlD;

daDetails.Fill(dsChapter13.0rder_Details) При каждом вызове метода DataAdapterfill неявно открывается и закрывается связанный с объектом DataAdapter объект Connection. Это означает, что код че тырежды открывает и закрывает объект Connection. Вызвав метод СоппесПоп.Ореп перед вызовом методов DataAdapterfill, вы чуть-чуть повысите производительность приложения. Кроме того, явно открыв объект Connection до вызова методов объек тов DataAdapter, вы сможете объединить изменения, ожидающие передачи в БД, в одну транзакцию.

Если бы мне пришлось рекомендовать единый, универсальный способ управ ления состоянием соединения, я порекомендовал бы способ, описанный выше.

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

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

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

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

Пул соединений предназначен преимущественно для многоуровневых прило жений, но он годится для повышения производительности и простых двухуров невых приложений. Когда наше приложение-пример явно или неявно закрывает объект Connection, реальное соединение с БД помещается в пул. Если приложе ние, до того как истечет время хранения соединения в пуле, снова откроет объект Connection, соединение будет использовано повторно.

Чтобы при работе с поставщиком OLE DB.NET Data Provider не применять в своем приложении пул соединений, добавьте в строк)' подключения такой атрибут:

OLE OB Services=-4;

Если требуется исключить обращение к пулу соединений при работе с постав щиком SQL Client.NET Data Provider, добавьте следующий атрибут:

Pooling=False;

ГЛАВА 13 Создание эффективных Windows-приложений Работа с данными больших двоичных объектов Для повышения производительности можно хранить данные больших двоичных объектов (BLOB-данные) в файлах на сервере, а пути к этим файлам Ч в БД. Опе рационные системы лучше применять для работы с файлами. Хранить те же дан ные в БД менее эффективно. Например. SQL Server 2000 делит BLOB-данные объе мом свыше 8 кбайт на несколько страниц БД. Таким образом, файл размером кбайт делится на пять составных частей.

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

Если вы решите хранить BLOB-данные в БД, в следующих разделах приводит ся несколько советов по работе с такими данными в ADO.NET.

Отложенная выборка BLOB-данных Предположим, ваш запрос возвращает сотню записей, включающих поля с BLOB данными. Действительно ли следует возвращать все эти данные в наборе резуль татов запроса? В SQL Server поля с BLOB-данными могут содержать до 2 Гбайт ин формации. Представляете ли вы, сколько BLOB-данных вернет ваш запрос?

Один из способов повысить производительность приложения Ч отложить выборку BLOB-данных из БД до момента, когда они понадобятся. Выбирайте обыч ные данные заблаговременно, а затем по мере необходимости получайте BLOB данные. Этот способ особенно полезен, если пользователь обращается только к BLOB-данным отображаемой в настоящий момент записи, Обработка BLOB-данных с помощью объектов DataSet На самом деле обращаться и редактировать содержимое столбцов с BLOB-данны ми при помощи объектов DataSet очень просто. ADO.NET хранит текстовые BLOB данные в виде строк, а двоичные Ч в виде байтовых массивов. В отличие от пре дыдущих моделей доступа к данным объект DataRow не предоставляет методов Get-Chunk \\nnAppendChunk. Вам потребуется получать и редактировать все содер жимое поля.

Работают с содержимым поля с текстовыми BLOB-данными так же, как и с содержимым других полей с текстовыми типами данных.

Visual Basic.NET Dim row As DataRow Dim strBlob As String 'Обращение к содержимому поля с текстовыми BLQB-данными strBlob = CStr(row("TextBlob")) 'Редактирование содержимого поля с текстовыми BLOB-данными rowC'TextBlob") = strBlob 520 Часть IV Создание эффективных приложений с использованием ADO.NET Visual C#.NET DataRow row;

string strBlob;

//Обращение к содержимому поля с текстовыми BLOB-данными strBlob = (string) row["TextBlob"];

//Редактирование содержимого поля с текстовыми BLOB-данными row["TextBlob"] = strBlob;

К полям с двоичными BLOB-данными обращаются точно также, как и к полям, содержащим двоичные данные меньшего объема.

Visual Basic.NET Dim row As DataRow Dim aBinaryBlob As ByteO 'Обращение к содержимому поля с двоичными BLOB-данными aBinaryBlob = CType(row("BinaryBlob"), ByteO) 'Редактирование содержимого поля с двоичными BLOB-данными row("BinaryBlob") - aBinaryBlob Visual C#.NET DataRow row;

Byte[] aBinaryBlob;

//Обращение к содержимому поля с двоичными BLOB-данными aBinaryBlob = (Byte[]) row["BinaryBlob"];

//Редактирование содержимого поля с двоичными BLQB-данными row["BinaryBlob"] = aBinaryBlob;

Обработка BLOB-данных с помощью объектов DataReader Объект DataReader предоставляет альтернативу: обращаться сразу ко всему содер жимому поля с ВЮВ-данными или выбирать это содержимое по фрагментам.

Следующий фрагмент кода получает содержимое поля с BLOB-данными, один раз обратившись к объекту DataReader.

Visual Basic.NET Dim end As OleDbCommand Dim rdr As OleDbDataReader Dim intTextBlobColumnNo, intBinaryBlobColumnNo As Integer Dim strTextBlob As String Dim aBinaryBlob As ByteO rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess) Do While rdr.Read StrTextBlob = rdr.GetStringfintTextBlobColuinnNo} aBinaryBlob = CType(rdr(intBinaryBlobColumnNo), ByteO) Loop rdr,Close ГЛАВА 13 Создание эффективных Windows-приложений Visual C#.NET OleDbCommand cmd;

OleDbDataReader rdr;

int IntTextBlobColumnNo, intBinaryBlobColumnNo;

string strTextBlob;

Byte[] aBinaryBlob;

rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

while (rdr.Readf)) { strTextBlob = rdr.GetString(intTextBlobColumnNo);

aBinaryBlob = (Byte[]) rdr[intBinaryBlobColumnNo];

I rdr.CloseO Примечание Этот фрагмент кода получает содержимое поля с текстовыми ВЮВ-данными, используя метод GetString со строгим контролем типов.

Дня получения же двоичных ВЮВ-данных код неявно обращается к свой ству Пет без контроля типов и затем преобразует возвращенное значе ние в байтовый массив. У объекта DataReader есть метод GetBytes, одна ко он возвращает данные фрагментами, а не единым куском.

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

Объект DataReader предоставляет два метода, GetBytes и GetChars, позволяю щих выбирать двоичные данные фрагментами. Следующий фрагмент кода с по мощью метода GetBytes выбирает из объекта DataReader двоичные ВЮВ-данные фрагментами по 8 кбайт и записывает их в файл. Используя эту же логику, вы по лучите текстовые ВЮВ-данные с помощью метода GetCbars.

Visual Basic.NET 'Добавьте в начало модуля кода следующую строку Imports System. Dim cmd As OleDbCommand Dim rdr As OleDbDataReader Dim intBlobColumnNo As Integer = Dim intChunkSize As Integer = Dim intOffset As Integer = Dim intBytesReturned As Integer Dim aBinaryBlob(intCnunkSize) As Byte Dim strPathToFile As String = "C:\GetBytes.jP9" Dim filOutput As New FileStreamfstrPathToFile, FileMode.Create) rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess) Часть IV Создание эффективных приложений с использованием ADO.NET rdr.Read() Do intBytesReturned = CInt(rdr.GetBytes(intBlobColumnNo, intOffset, _ aBinaryBlob, 0, intChunkSize)) If (intBytesReturned > 0) Then filOutput.Write(aBinaryBlob, 0, intBytesReturned) End If intOffset += intBytesReturned Loop Until intBytesfleturned о intChunkSize filOutput.Close() rdr.CloseO Visual C#.NET //Добавьте в начало модули кода следующую строку using System.10;

QleubCommand cmd;

OleDbDataReader rdr;

int intBinaryBlobCol = 1;

int intChunkSize = 8192;

int intOffset = 0;

int intBytesReturned;

Byte[] aBinaryBlob = new Byte[intChunkSize];

string strPathToFile = "C:\XGetBytes.jpg";

FileStream filOutput = new FileStream(strPathToFile, FlleMode.Create);

rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

rdr.ReadC);

do { intBytesReturned = (int) rdr.GetBytes(intBinaryBlobCol, intOffset, aBinaryBlob, 0, intChunkSize);

if (intBytesReturned > 0) filOutput.Write(aBinaryBlob, 0, intBytesReturned);

intOffset += intBytesReturned;

1 while (intBytesReturned == intChunkSize);

filOutput. CloseO;

rdr.CloseO;

Двоичные BLOB-данные в БД Northwind Вы, вероятно, заметили, что БД Northwind включает столбцы с BLOB-данными. Так, в таблице Employees есть столбец Photo, содержащий фотографию сотрудника.

К сожалению, столбец Photo также содержит некоторые дополнительные дан ные, представляющие собой OLE-заголовок Access. Этот заголовок позволяет Access определить, данные какого типа содержит ВШВ-поле Ч.jpg-файл, документ Word, электронную таблицу Excel и т.д. Как следствие, если вы попытаетесь выбрать содержимое столбца Photo средствами ADO.NET (или ADO, RDO и др.), то не смо жете загрузить эти данные в элемент управления PictureBox или просмотреть содержимое файла в программе для работы с изображениями, например Paint.

Создание эффективных Windows-приложений ГЛАВА 13 Как же отбросить OLE-заголовок Access и оставить только нужные вам данные.

Если вкратце, никак Формат OLE-заголовка Access Ч оригинальная разработка Microsoft, к которой нет документации.

Тем не менее на прилагаемом к книге компакт-диске записано приложение LoadEmployeePhotos, позволяющее заменить содержимое по умолчанию столбца Photo таблицы Employee рисунками в формате.jpg. В папке приложения вы най дете.jpg-файлы с изображениями сотрудников. Приложение загружает 1 эти фай лы в БД Northwind, выполняя ряд параметризованных запросов.

Кроме того, данное приложение можно рассматривать как пример загрузки содержимого файлов в БД средствами параметризованных запросов, Пример приложения для работы с BLOB-данными Теперь, когда в таблице Employees есть реальные изображения, мы вкратце рас смотрим пример приложения, возвращающего и выводящего эти изображении на Windows-форме. Приложение ShowEmpfoyeesPhotos (рис. 13-11), записанное на прилагаемом к книге компакт-диске, возвращает сведения о сотруднике из БД Northwind в объект DataSet.

Рис. 13-11. Вывод двоичных BLOB-данных в элементе управления PictureBox В целях быстрого создания приложения я воспользовался объектом Currency Manager и связанными элементами управления для контроля текущего номера и общего числа записей, а также для упрощения вывода сведений о сотрудниках.

Используемый в приложении объект DataSet включает два отдельных объекта DataTable: один для BLOB-данных и один Ч для прочих данных. Дочерний объект DataTable также включает поле первичного ключа (EmployeelD), упрощающее переход от записи родительского объекта DataTable к соответствующей записи дочернего объекта DataTable. Кроме того, я добавил в родительский объект Data Table столбец FetchedPhoto, позволяющий определить, выбрана ли из БД фотогра фия конкретного сотрудника.

При запуске приложение получает из БД стандартные сведения о сотрудниках (значения полей EmployeelD, LastName, FirstName и т.д.). Затем, когда пользова Часть IV Создание эффективных приложений с использованием ADO.NET тель впервые обратится к записи о конкретном сотруднике, приложение считы вает двоичные ВЮВ-данные Ч содержимое поля Photo, (фотографии имеют не большой размер Ч всего 22 кбайт Ч и поэтому загрузка изображений при запус ке приложения не оказала бы резко отрицательного влияния на производитель ность. Это особенно верно, если размер таблицы, из которой выбираются данные, невелик). Такой способ значительно повышает производительность приложения, выводящего на экран лишь часть возвращаемых записей.

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

Вопросы, которые стоит задавать почаще Вопрос. Следует ли использовать связывание с данными приложении, которое я собираюсь распространять?

Ответ. Я применяю связывание с данными при создании пользовательского ин терфейса приложения, чтобы сделать это быстро и с минимальным объемом кода.

Как только структура пользовательского интерфейса и схема данных, требуемых приложению, меня устраивает, я решаю, потребуется ли мне связывание с данны ми в дальнейшем. Если мне нужны более широкие возможности управления, чем те, которые предоставляет связывание, или я решил, что связывание не поможет сэкономить время на разработку приложения, я заменяю стандартные функции связывания с данными собственным кодом, Вернувшись к описанию этапа 11 разработки приложения-примера, вы уви дите, что содержимое заказа выводится в связанном элементе управления DataGrid.

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

Вопрос. Можно ли связать элементы управления с объектом DataSet без конт роля типов?

Ответ. Безусловно. Элементы управления в период разработки разрешается свя зывать с объектами DataSet без контроля типов, схемы которых также определя ются в период разработки при помощи окон свойств. Кроме того, используя при водившийся в одном из предыдущих разделов код, элементы управления можно в период выполнения связывать как с объектами DataSet без контроля типов, так и с объектами DataSet со строгим контролем типов.

Создание эффективных Windows-приложений ГЛАВА Visual Basic.NET 'Связывание элемента управления TextBox с объектом DataColumn TextBox.DataBindings.Add{"Text", DataSet, "TableName.ColumnName") 'Связывание элемента управления DataGrid с объектом DataTable DataGrid.DataSource = DataSet DataGrid.DataMember = "TableName" Visual C#.NET //Связывание элемента управления TextBox с объектом DataColumn TextBox.DataBindings.Add("Text", DataSet, "TableName.ColumnName"};

//Связывание элемента управления DataGrid с объектом DataTable DataGrid.DataSource = DataSet;

DataGrid.DataMember = "TableName";

Вопрос. При запуске приложения мне нужно загружать данные. Ваши рекомен дации?

Ответ. Убедитесь, что эти данные действительно вам необходимы. Очевидно, что на возврат меньшего числа записей и/или столбцов потребуется меньше време ни. Еще один вариант Ч- воспользоваться имеющейся в.NET Framework поддерж кой многопоточности и загрузить данные при запуске приложения в другом по токе. Подробнее Ч в разделе документации.NET Framework SDK. посвященном пространству имен System-Threading..NET Framework упрощает работу с потока ми, особенно пользователям Visual Basic, однако тема управления потоками ле жит вне круга вопросов, освещаемых в данной книге.

Вопрос. Почему в коде на этапе 11 разработки приложения-примера положение текущего заказа определяется с помощью объекта DalaView, а не DataTahle't Ответ. Если пометить объект DataKow как удаленный, он по-прежнему будет находиться в наборе Rows объекта DataTable. Приложение позволяет помечать заказы на удаление, и если бы в нем использовался только объект DataTable, по требовалось бы на порядок больше кода, чтобы при перемещении по оставшим ся заказам пропускать заказы, помеченные как удаленные.

Вместо этого приложение применяет объект DataView. Если не изменять зна чение свойства Roii'StateFilter объекта DataView по умолчанию, через этот объект окажутся недоступными записи, помеченные как удаленные, что упрощает про цесс перемещения по оставшимся заказам. Объект CurrencyManager функциони рует аналогичным образом.

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

Создание эффективных приложений с использованием ADO.NET 526 Часть IV Ответ. Я уже говорил, что пессимистическое управление блокировками на самом деле требуется лишь ограниченному кругу приложений. Определенно, пессимис тическое управление блокировками в многоуровневом приложении, промежуточ ный уровень которого не поддерживает сведений о состоянии Ч трудная задача.

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

Давайте вкратце остановимся на архитектуре. Поскольку приложение обраща ется к БД через промежуточный уровень без хранения сведений о состоянии, оно должно поддерживать блокировки, заданные пользователями, даже при отсутствии живых соединений. Кстати, я не знаю ни одной СУБД, которая удерживала бы заданную пользователем блокировку, пока пользователь не подключится снова, если он отсоединился.

Тем не менее для реализации этой функциональности можно разработать соб ственную схему блокировки. Если честно, мне не приходилось развертывать мно гоуровневые приложения, основанные на такой архитектуре. Но если бы мое су ществование зависело от разработки подобных приложений, обращающихся к СУБД SQL Server, я бы поступил следующим образом.

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

2. Добавил в таблицу два столбца: один с уникальным ключом блокировки, а дру гой Ч с датой и временем успешного наложения блокировки пользователем, 3. Создал хранимую процедуру, позволяющую налагать блокировку на ряд дан ных. Хранимая процедура принимает в качестве параметров ввода первичный ключ ряда, а также GUID. Если на ряде нет блокировки, процедура помечает его как заблокированный. Вот пример такой процедуры:

CREATE PROCEDURE spPessimisticLockAcquirelock (@ID int, @LockID uniqueidentifier) AS UPDATE tblPessimisticLock SET LockAcquired = GetDateO, LockID = зLockID WHERE ID = ID AND LockAcquired IS NULL 4. Создал хранимую процедуру, позволяющую редактировать содержимое ряда.

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

Успешно обновив ряд, процедура снимает с него блокировку.

CREATE PROCEDURE spPessimisticLockUpdateRow (@ID int, @DescCol varchar(32), @LockiD uniqueidentifier) AS UPDATE tblPessimisticLock SET DescCol = $DescCol WHERE 10 = OID AND LockID = @LockID IF @@ROWCOUNT = BEGIN Создание эффективных Windows-приложений ГЛАВА SET NOCOUNT ON UPDATE tblPessimisticLock SET LockAcquired = NULL, LockID = NULL WHERE ID = SID AND LockID = LockID END 5. Создал хранимую процедуру, позволяющую снять с ряда заданную блокировку.

CREATE PROCEDURE spPessimistlcLockReleaseLock (@ID int, @LockID uniqueidentifier) AS UPDATE tblPessimisticLock SET LockAcquired = NULL, LockID = NULL WHERE ID = 810 AND LockID = @LockID 6. Создал задание, снимающее все неснятые вовремя блокировки. Следующий запрос ищет ряды с блокировкой, удерживаемой более 5 минут, и снимает ее:

UPDATE tblPessimist!сLock SET LockAcquired = NULL, LockID = NULL WHERE DateAdcKmi, 5, LockAcquired) <= GetDate{) ГЛАВА Создание эффективных Web-приложений 11осле того как мы обсудили разработку Windows-приложений для доступа к дан ным, пришла пора рассмотреть их Интернет-аналог Ч Web-приложения.

Краткий обзор Web-приложений Эта глава познакомит вас с Web-приложениями. О технологии ASP.NET написано множество книг, в том числе и издания, посвященные написанию кода для досту па к данным, например Building Web Solutions with ASP.NET and ADO.NET (Microsoft Press, 2002.). Изучив материал этой главы, вы получите базовые навыки создания Web-приложений, взаимодействующих с БД при помощи ADO.NET. Вы также уз наете о связывании с данными, преимуществах и недостатках различных функ ций кэширования ASP.NET, постраничной разбивке информации и передаче об новлений. В большинстве приводимых примеров используется наиболее мощный и гибкий элемент управления ASP.NET, связанный с данными, Ч DataGrid.

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

ASP.NET значительно упрощает процесс создания Web-приложений. ASP.NET код можно писать на языке по вашему выбору Ч Visual Basic.NET или Visual C*.NET.

Создание эффективных Web-приложений ГЛАВА Достаточно задать свойства Web-элементов управления ASP.NET. как в случае с обычными Windows-элементами управления, и элементы автоматически преоб разуют эти параметры в HTML-код. Если поместить на Web-форму элемент управ ления Button ASP.NET и добавить код в событие Click этого элемента, ASP.NET ав томатически добавит в соответствующую Web-страницу HTML-код, который по зволит Web-серверу по щелчку кнопки выполнять код ее события Click. Благодаря метаданным, которые ASP.NET добавляет в страницу, код может обращаться к па раметрам элементов управления и собирать отправленные пользователем данные.

Многие разработчики, создававшие приложения на ASP.NET, не знают о воз можностях ASP.NET, упрощающих разработку Web-приложений. А между тем это Ч один из китов мощи ASP.NET. Так, некоторые разработчики не вполне понимают, что код на ASP.NET и пользовательский HTML-интерфейс выполняются на разных машинах. Еще больше программистов даже представления не имеют о том, что промежуточный уровень приложений ASP.NET не поддерживает сведения о состо янии, а часть разработчиков даже не знает, что такое промежуточный уровень без поддержки сведений о состоянии (stateless).

Преимущества и недостатки способа без хранения сведений о состоянии Когда пользователь открывает в браузере ASP.NET-страницу (Vaspx), IIS передает запрос среде ASP.NET, и та загружает для этой страницы скомпилированную биб лиотеку. Обработав запрос, ASP.NET освобождает ресурсы страницы. Когда пользо ватель передает на страницу информацию, ASP.NET создает страницу заново и реагирует на событие передачи данных странице. По умолчанию ASP.NET не хра нит между запросами каких-либо сведений о состоянии пользовательского сеанса.

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

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

Скажем, вы написали на ASP.NET код, который при помощи объекта DataAdapter выбирает из БД сведения о товаре в объект DataTable. Кроме того, вы связали с объектом DataTable Web-элемент управления DataGrid, чтобы вывести сведения о товаре на Web-странице. Предположим также, что вы реализовали на Web-стра нице кнопку или ссылку, позволяющую добавить товар в корзину.

Среда ASP.NET помогла вам преобразовать результаты запроса в HTML-код, который был выведен в Web-браузере. Однако сразу после ответа на запрос стра ницы ASP.NET освободила ресурсы этой страницы, включая объект DataTable с информацией о товаре. Web-браузер выводит только визуальное представление результатов и в действительности не получал объекта DataTable. Фактически, даже 530 Часть IV Создание эффективных приложений с использованием ADO.NET если бы он его и получил, то не знал бы, что с этим объектом делать;

можно ска зать, что в браузере нет поддержки.NET.

Подключение к БД В целом, подключение к БД из Web-приложения аналогично подключению к БД из Windows-приложения. Управление соединением также осуществляется средства ми объекта Connection, Тем не менее есть несколько отличий, о которых нужно помнить.

Использование доверенных соединений В предыдущих главах мы применяли доверенные соединения для подключения к локальному экземпляру.NET Framework SDK MSDE:

Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

Initial Catalog=Northwind;

Trusted_Cormection=Yes;

Эту же строку подключения можно вставить в Web-приложение и в период разработки, но тогда возникнут проблемы в период выполнения. Скажем, вы созда ли новое приложение и добавили в событие Load Web-формы такой код:

Visual Basic.NET Dim strConn As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" Dim en As New OleObConnection(strConn) cn.OpenO Visual C#.NET string strConn;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

OleDbConnection en = new DleDbConnection{strConn);

cn.OpenO;

В зависимости от версии ОС Windows может при запуске такого Web-прило жения сгенерировать исключение и выдать следующее сообщение:

Login failed for user 'ИмяКомпьютера\А8РМЕТ' Используемая строка подключения отлично работала в Windows- и в консоль ных приложениях. Но что же не так в случае с Web-приложением? Помните: если используются доверенные соединения, SQL Server определяет наличие у вас прав на подключение к БД на основе ваших реквизитов для входа в сеть. Сообщение, сопровождающее исключение, указывает, почему именно невозможно подключить ся к БД. При подключении применяются реквизиты учетной записи ASP.XET, а не ваши реквизиты, поскольку код выполняется в процессе ASP.NET. Результат попытки подключения зависит от того, имеет ли учетная запись ASP.NET соответствующие права в БД SQL Server или нет.

Создание эффективных Web-приложений ГЛАВА SQL Server Enterprise Manager упрощает назначение учетным записям прав доступа к БД. Раскройте узел Security нужного сервера, щелкните узел Logins пра вой кнопкой и выберите New Login. Если вы работаете с БД MSDE и у вас нет до ступа к Enterprise Manager, для предоставления учетной записи доступа к БД SQL Server можно выполнить такой запрос:

exec sp.grantlogin 'ИмяДоменаИлиКомпьютера\ИмяУчетнойЭаписи' Подмена пользователей Что, если вы хотите подключаться к БД, применив реальные реквизиты пользова теля?

ASP.NET сильно упрощает подмену пользователей. Создав в Visual Studio.NET новое Web-приложение, в окне Solution Explorer вы увидите файл \Veb.config. Это XML-документ с параметрами вашего приложения. В данном документе есть раз дел авторизации:

Такие параметры позволяют всем пользователям подключаться к приложению.

Если их изменить, как показано ниже, приложение станет подменять текущего пользователя и исключит анонимные подсоединения:

После этого при подключении к БД по доверенным соединениям будут при меняться реальные реквизиты пользователя для входа в сеть. Тем не менее по ряду причин мне не нравится такой способ работы с доверенными соединениями в Web приложениях, Данная архитектура совершенно не использует преимущества пула соедине ний. Если к Web-приложению, в котором задействованы подмена и доверенные соединения, подключатся три разных пользователя, каждое из соединений будет иметь разный контекст безопасности. Как следствие, ADO.NET не удастся помес тить соединение Пользователя А в пул и затем задействовать это соединение для Пользователя Б. Фактически пул соединений может оказать негативное влияние на производительность приложения, применяющего доверенные соединения.

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

Мне не нравится реализовывать безопасность в приложении средствами СУБД, Если вы знакомы с доверенными соединениями с БД, заманчиво использовать их, а не изучать различные варианты проверки подлинности, предоставляемые ASP.NET.

Вам не надо лишний раз обращаться по сети к БД для проверки подлинности текущего пользователя. ASP.NET предлагает множество способов реализации бе зопасности в приложении: проверку подлинности средствами Windows, провер Создание эффективных приложений с использованием ADO.NET 532 Часть IV ку подлинности средствами службы Microsoft Passport, а также проверку подлин ности средствами Forms.

Работа с БД Access Многие разработчики имеют дело с БД Access и планируют использовать их в своих ASP.NET-приложениях. Я это не одобряю. БД Access не рассчитаны на множество параллельно работающих пользователей. Их возможности масштабирования да леки от БД SQL Server и Oracle. Тем не менее привлекательность БД Access вполне понятна. Создавать их и управлять ими очень просто.

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

Когда вы открываете БД Access, ядро БД Jet отслеживает различные блокиров ки (как чтения, так и записи), налагаемые пользователями на ряды и страницы, при помощи сопоставленного с БД файла блокировок. Следовательно, у пользо вателя, обращающегося к БД, должны быть разрешения чтение и запись на доступ к папке, в которой эта БД находится. Предположим, система генерирует исключения типа;

The Microsoft Jet database engine cannot open the file 'C:\Path\To\MyDatabase.mdb'. It is already opened exclusively by another user, or you need permission to view its data.

или Operation must use an updateable query.

В этом случае проблема скорее всего в том, что у учетной записи ASP.NET нет права доступа на запись к файлу блокировок. Убедитесь, что учетная запись ASP.NET обладает необходимыми разрешениями на доступ к папке, в которой находится БД Access.

Вывод данных на Web-странице Конечно, подключение к БД Ч это только первый этап. После подключения обычно выполняют запрос, получают его результаты и выводят полученные данные на Web странице.

Как и в случае с Windows Forms, написать ASP.NET-код для задания свойств элементов управления можно вручную:

Visual Basic.NET TextBoxl.Text = MyDataSet.Tables("MyTable").Rows(Q)("MyColumn") Visual C#.NET TextBoxl.Text = (string) MyDataSet.Tables["MyTable"].Rows[0]["HyColumn"];

ГЛАВА 14 Создание эффективных Web-приложений Чтобы упростить данный процесс, воспользуйтесь функциями связывания с данными, которые предоставляют элементы управления ASP.NET. Скажем, вы до бавили в Web-приложение код, выполняющий запрос и помещающий его резуль таты в объект DataSet. Теперь вам нужно вывести содержимое DataSet на Web-стра нице в виде HTML-таблицы. Можно генерировать HTML-код таблицы, написав для этого большой объем кода на ASP.NET, можно воспользоваться XML и XSLT, но вы избавите себя от массы проблем, если задействуете средства связывания с данными, предоставляемые ASP.NET.

Связанные элементы управления ASP.NET преобразуют данные в HTML-код для Web-страницы. Связывание с данными в ASP.NET Ч это улица с односторонним движением. Если связать текстовое поле с полем объекта DataTable, отобразить страницу и изменить содержимое текстового поля, это никак не отобразится на оригинальном содержимом поля в объекте DataTable. У элемента управления DataGrid есть возможности, позволяющие реализовывать в Web-приложениях функции обновления, однако большинство связанных элементов управления ASP.NET применяются для вывода данных, доступных только для чтения.

Как и при связывании с данными в Windows, с источниками данных разреша ется связывать однозначные, например текстовые поля, и многозначные элемен ты управления, например сетки. Для связывания многозначных элементов управ ления применяются стандартные свойства DataSource и DataMernber. Связать с данными одновременно однозначные и многозначные элементы управления уда ется при помощи статичного объекта DataBinder Web-страницы, Использование метода DataBinder.Eval Начнем изучение возможностей связывания с данными, предоставляемых ASP.NET, с того, что свяжем с данными элемент управления TextBox, воспользовавшись методом Eval объекта DataBinder. DataBinder получает данные из объектов при помощи механизма Reflection. Так, следующий код возвращает значение поля Company-Name первой записи объекта DataTable:

DataBindeг.Eval(DataTable, "Rows[0],[CompanyName]") Этот метод Eval возвращает данные с использованием универсального типа Object, который затем преобразуется в любой нужный тип данных.

Показанный ранее метод Eval перегружен. Есть еще один метод Eval, прини мающий строку формата. Скажем, вы получаете информацию о конкретном то варе и вам нужно вывести Б текстовом поле цену этого товара. Следующий вызов метода Eval форматирует содержимое поля UnitPrice как значение с типом дан ных currency:

DataBinder. EvaKDataTable, "Rows[0]. [UnitPrice]", "{0:c}") Этот метод Eval возвращает данные в виде строки с использованием указан ного вами формата. Подробнее о выражениях для форматирования строк Ч в документации метода StringFormat.

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

Создание эффективных приложений с использованием ADO.NET 534 Часть IV DataBinder.Eval(TextBox1, "Visible") Данный код примерно эквивалентен прямому обращению к свойству Visible элемента управления TextBox за исключением того, что в коде на основе DataBinder используется позднее связывание. Если вы случайно сделаете опечатку и напишете такой код:

DataBinder.Eval(TextBox1, "Visibile") то узнаете о своей ошибке только в период выполнения, а не в период компи ляции, Тот факт, что'объект DataBinder использует позднее связывание для возврата данных, делает этот объект более гибким, но одновременно приводит и к неболь шому снижению производительности, поскольку системе в период выполнения приходится выполнять больший объем работы.

Связывание элемента управления TextBox с объектом DataSet Получив представление об объекте DataBinder, давайте воспользуемся им и свя жем элемент управления TextBox с полем данных, которое возвращает объект DataAdapter, Следующий фрагмент кода выбирает данные из таблицы Customers в объект DataSet и затем при помощи объекта DataBinder задает свойству Text элемента управления TextBox значение поля CompanyName первой записи, воз вращенной запросом:

Visual Basic.NET Dim strConn, strSQL, strExpression As String strConn = "Provider=SQI_OLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT CustomerlD, CompanyName FROM Customers " & "WHERE CustomerlD = 'WOLZA'" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim ds As New DataSet() da.Fill(ds, "Customers") strExpression = "Tables[Customers].Rows[0].[CompanyName]" TextBoxl.Text = CStrfDataBinder.Eval(ds, strExpression)) Visual C#.NET string strConn, strSQL, strExpression;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "SELECT CustomerlD, CompanyName FROM Customers " + "WHERE CustomerlD = 'WOLZA ';

OleDbDataAdapter da = new 01eDbDataAdapter(strSOL, strConn);

DataSet ds = new DataSetO;

da.Fill(ds, "Customers");

strExpression = "Tables[Customers].Rows[0].[CompanyName]";

TextBoxl.Text = (string) DataBinder.Eval(ds, strExpression);

ГЛАВА 14 Создание эффективных Web-приложений Связывание элемента управления TextBox с объектом DataReader С помощью объектов DataBinder удается также получать данные из объектов Data Reader. Следующий фрагмент кода получает результаты запроса при помощи объек та DataReader. Поскольку нам нужна только первая запись из этих результатов, в коде используется константа SingleRow из перечисления CommandBehavior-.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLED8;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trijsted_Connection=Yes;

" Dim en As New OleDbConnection(strConn) cn.0pen() strSQL = "SELECT CustomerlD, CompanyName FROM Customers " & "WHERE CustomerlD = 'WOLZA'" Dim cmd As New 01eDbCommand(strSQL, en) Dim rdr As OleDbDataReader rdr = cmd.ExecuteReader(CommandBehavior.SingleRow) rdr.ReadC) TextBoxl.Text = CStr(DataBinder.Eval(rdr, "[CompanyName]")) rdr.Close() cn.CloseO Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

OleDbConnection en = new OleDbConnection(strConn);

cn.0pen();

strSQL = "SELECT CustomerlD, CompanyName FROM Customers " + "WHERE CustomerlD = 'WOLZA'";

OleDbCommand cmd = new 01eDbCommand(strSQL, en);

OleDbDataReader rdr;

rdr = cmd.ExecuteReader(CommandBehavior.SingleRow);

rdr.fleadO;

TextBoxl.Text = (string) DataBinder.Evalfrdr, "[CompanyName]");

rdr.CloseO;

cn.CloseO;

Связывание элементов управления DataGrid с результатами запросов AS P. NET включает встроенный элемент управления DataGrid, позволяющий пре образовывать результаты запроса в HTML-таблицу. Данный элемент предоставля ет ряд мощных функций, например постраничное представление данных, сорти ровку и обновление. Сейчас же я расскажу о том, как при помощи элемента уп равления DataGrid отобразить результаты запроса.

536 Часть IV Создание эффективных приложений с использованием ADO.NET Связывание элемента управления DataGrid с объектом DataSet Следующий фрагмент кода создает объекты OleDbDatoAdapter и DataSet, выбира ет результаты запроса в объект DataSet и связывает элемент управления DataGrid Web-формы с нужным объектом DataTable из состава DataSet.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provlder=SQLOLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT CustomerlD, CompanyName, ContactName, Phone " & "FROM Customers" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim ds As New DataSetO da.Fill(ds, "Customers") gridCustomers.DataSource = ds gridCustomers.DataMember = "Customers" g ridCustomers. DataBlndO Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "SELECT CustomerlD, CompanyNafoe, ContactName, Phone " + "FROM Customers";

OleDbDataAdapter da = new OleDbDataAdapterfstrSQL, strConn);

DataSet ds = new DataSetO;

da.Fill(ds, "Customers");

gridCustomers.DataSource = ds;

gridCustomers.DataMember = "Customers";

gridCustomers.DataBind();

Данный код идентичен коду для связывания элемента управления DataGrid Windows-формы с объектом DataSet за одним небольшим, но важным исключе нием. Задав значения свойств DataSource и DataMember, код вызывает метод Data Bind элемента управления DataGrid. В случае с Windows-формой DataGrid связы вается с источником данных сразу после задания значений указанных свойств. В случае с Web-формой дело обстоит иначе. DataGrid отобразит данные из источ ника только после вызова метода DataBmd этого элемента управления. Кроме того, можно вызвать метод DataBind страницы;

при этом неявно вызываются одноимен ные методы элементов управления этой страницы.

Связывание элемента управления DataGrid с объектом DataReader Как уже говорилось, данные на Web-странице доступны только для чтения. Объект DataSet предоставляет значительно более широкую функциональность (кэширо вание обновлений, простое перемещение между таблицами и тд.), которая может потребоваться вам при выполнении запроса и выводе его результатов на Web странице. Для повышения производительность элементы управления ASP.NET также поддерживают связывание с объектами DataReader.

ГЛАВА 14 Создание эффективных Web-приложений Следующий фрагмент кода выполняет все тот же запрос, но получает его ре зультаты при помощи объекта OleDbDataReader и связывает с этим объектом эле мент управления DataGrid Web-формы.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT Customer-ID, CompanyName, ContactName, Phone " & "FROM Customers" Dim en As New OleDbConnection(strConn) cn.0pen() Dim cmd As New 01eDbCommand(strSQL, en) Dim rdr As OleDbDataReader = cmd.ExecuteReader() gridCustomers.DataSource = rdr g ridCustomers.DataBindC) rdr.Close() cn.Close() Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "SELECT CustomerlO, CompanyName, ContactName, Phone " + "FROH Customers";

OleDbConnection en = new OleDbConnection(strConn);

cn.OpenO OleDbCommand cmd = new 01eDbCommand{strSQL, strConn);

OleDbDataReader rdr = cmd.ExecuteReader();

gridCustomers,DataSouroe = rdr;

gridCustomers. DataBindO;

rdr.CloseC);

cn.CloseO;

Кэширование данных между обращениями к БД Итак, вы умеете выводить данные на Web-странице, Вы знаете, как выполнить запрос и преобразовать его результаты в HTML-код при помощи элементов управления, связанных с данными. Что, если вы не хотите выполнять одинаковые запросы при каждом обращении к серверу? Например, можно кэшировать результаты запроса, интенсивно использующего ресурсы системы. Или же сохранять содержимое кор зины клиента.

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

Создание эффективных приложений с использованием ADO.NET 538 Часть IV Чтобы понять, какие функции кэширования требуются вашему приложению и нужны ли они вообще, вам необходимо четко представлять себе архитектуру создаваемой программы, а также принципы работы и преимущества и недостат ки каждой из этих функций. Следующий раздел не содержит необоснованных утверждений типа Кэширование данных в объекте Application Ч паршивая идея*, Прочитав его, вы уясните основы функций кэширования ASP.NET.

Способ без хранения сведений о состоянии Нет закона программирования, гласящего, что необходимо кэшировать данные.

ASP.NET-код вашего Web-приложения может полностью исключать хранение све дений о состоянии.

При этом выполнение запросов и преобразование их результатов в HTML осу ществляется средствами связанных элементов управления. Если у вас есть прило жение дая размещения заказов, в котором пользователь хранит список товаров в корзине, допустимо записывать содержимое корзины текущего пользователя в БД, выполняя поиск связанных с этим пользователем записей БД по идентификатору сеанса.

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

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

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

Файлы cookie Многие Web-узлы хранят пользовательские данные на стороне клиента при по мощи файлов cookie. Объекты Request и Response ASP.NET предоставляют набор cookies, позволяющий записывать и получать информацию между обращениями к БД. Так, следующий код с помощью файла cookie отслеживает время и дату пос леднего посещения узла пользователем:

Visual Basic.NET If Request.Cookies("LastVisit") Is Nothing Then IblLastVisit.Text = "This is your first visit! Welcome!" Response.AppendCookie(New HttpCookie("LastVisit", Now.ToString)) Создание эффективных Web-приложений ГЛАВА Else IblLastVisit.Text = "Welcome back. Your last visit was: " & _ Request.Cookies("LastVisit").Value Response.Cookiesf'LastVisit").Value = Now.ToString End If Visual C#.NET if (Request.Cookies["LastVisit"] == null) { IblLastVisit.Text = "This is your first visit! Welcome!";

Response.AppendCookie(new HttpCookieC'LastVisit", DateTime.Now.ToStringO));

} else { IblLastVisit.Text = "Welcome back. Your last visit was: " + Request.Cookies["LastVisit"].Value;

Response.Cookies["LastVisit"].Value = DateTime.Now.ToStringO;

!

Преимущества ASP.NET упрощает работу с файлами cookie. Хранение данных на стороне клиен та позволяет ASP.NET-коду не поддерживать сведения о состоянии, благодаря чему повышается масштабируемость. Кроме того, файлы cookie разрешается настраи вать. Для управления сроком хранения файлов cookie применяют свойства Expires объекта HttpCookie.

Недостатки Файлы cookie рассчитаны на незначительный объем информации. Большинство браузеров не позволяют хранить в файле cookie больше пары килобайт данных, и поэтому для кэширования результатов запроса, возвращающего больше пары строк, такие файлы не особенно удобны.

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

Уровень безопасности файлов cookie достаточно низок. Пользователь имеет право изменить содержимое такого файла.

Скрытые поля Информацию допустимо хранить в скрытом поле Web-страницы. Данный процесс чем-то аналогичен использованию скрытого элемента управления Windows-формы.

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

Часть IV Создание эффективных приложений с использованием ADO.NET Свойство ViewState Класс Page из пространства имен System.Web.fJI предоставляет свойство ViewState, содержащее объект StateBag. По сути, данный объект аналогичен набору пар лимя Ч значение и, так же как и набор Cookies объектов Request и Response, применяется для хранения информации, Записав данные в свойство ViewState страницы, вы получите их при следую щем событии передачи данных серверу, связанном с этой страницей. Работать со свойством ViewState так же просто, как и с файлами cookie:

Pages:     | 1 |   ...   | 7 | 8 | 9 | 10 | 11 |    Книги, научные публикации