69067

Основи технології ADO.NET. Автономні дані

Лекция

Информатика, кибернетика и программирование

Коли застосовується цей підхід, дані виключають будь-які операції з ними у сховище в момент їх вилучення. Код застосування повністю відстежує всі дії користувача, зберігання інформації та визначення того, коли нова команда повинна бути сгенерирована і виконана.

Украинкский

2014-09-29

269.5 KB

2 чел.

Лекція №11

Основи технології ADO.NET. Автономні дані

Слайд №43. Схема доступу до даних:

 Об'єкт Connection встановлює з'єднання між застосуванням і БД

 Об'єкт Command дозволяє виконувати команди безпосередньо над БД

 Якщо виконана команда повертає кілька значень, Command відкриває доступ до них через об'єкт DataReader

 Результати також можна отримувати в DataSet, використовуючи для цього об'єкт DataAdapter

 Оновлювати БД можна за допомогою Command або DataAdapter

Автономні дані (disconnected). У всіх розглянутих на попередній лекції прикладах використовувалися засоби ADO.NET, засновані на з'єднаннях. Коли застосовується цей підхід, дані виключають будь-які операції з ними у сховище в момент їх вилучення. Код застосування повністю відстежує всі дії користувача, зберігання інформації та визначення того, коли нова команда повинна бути сгенерирована і виконана.

ADO.NET пропонує зовсім іншу філософію із застосуванням об'єкта DataSet. При підключенні до бази даних DataSet наповнюється копією інформації, прочитаної з бази. Якщо інформація в DataSet змінюється, відповідна інформація у таблиці бази не змінюється. Це означає, що можна легко обробляти і маніпулювати даними, ні про що не турбуючись, тому що при цьому не використовується цінне з'єднання з базою. При необхідності можна повторно підключитися до вихідного джерела даних і застосувати всі, виконані у DataSet зміни за одну пакетну операцію.

Звичайно, ця угода не позбавлена недоліків, таких як наслідки паралелізму. В залежності від того, як спроектовано застосування, загальний пакет змін може бути підтверджений негайно. Але навіть одна помилка (наприклад, спроба оновити запис, який одночасно оновлюється іншим користувачем) може зруйнувати весь процес оновлення. При добре продуманому кодуванні можна захистити свіє застосування від цих проблем, але для цього потрібні додаткові зусилля.

З іншого боку, іноді може знадобитися використовувати автономну (disconnected) модель доступу ADO.NET та DataSet. Наведемо деякі сценарії, в яких DataSet використовувати легше, ніж DataReader:

• Коли потрібний зручний пакет для відправки даних іншому компоненту (наприклад, якщо ви поділяєте інформацію з іншими компонентами або поставляєте їх клієнтам через Web-службу).

• Коли потрібний зручний формат файлу для сериалізації даних на диск (DataSet включає вбудовану функціональність, що дозволяє зберігати його у файлі XML).

• Коли потрібно організувати навігацію у двох напрямках по великому об'єму даних. Наприклад, можна використовувати DataSet для підтримки посторінкового елемента керування – списка, який показує інформацію по частинах. На противагу цьому DataReader забезпечує переміщення тільки в одному напрямку – вперед.

• Коли потрібно виконувати навігацію по декількох різних таблицях. DataSet може зберегти в собі всі ці таблиці та інформацію про відносини між ними, таким чином, дозволяючи створювати сторінки типу "головна-підлегла" без необхідності запиту до бази даних більше одного разу.

• Коли потрібно використовувати прив'язку даних до елементів керування користувацького інтерфейсу. Для прив'язки даних можна застосовувати і DataReader, але оскільки DataReader використовує лише односпрямований курсор, неможливо прив'язати дані до множини елементів керування. Крім того, на відміну від DataSet, він не має можливості застосовувати користувацькі критерії сортування та фільтрації.

• Коли необхідно маніпулювати даними як XML.

• Коли необхідно виконувати пакетні оновлення через веб-службу. Наприклад, можна створити Web-службу, що дозволяє клієнтові завантажувати DataTable, заповнений рядками, виконувати в ньому множинні зміни і пізніше підтверджувати їх для джерела даних. У такий момент веб-служба може проводити всі зміни в одній операції (якщо припустити, що ніяких конфліктів не виявлено).

Далі розглядається, як отримувати дані для DataSet. Крім того, показано, як отримувати дані з безлічі таблиць, як створювати відносини між цими таблицями, що знаходяться у пам'яті, як сортувати і фільтрувати дані, а також як шукати певні записи. Однак задача застосування DataSet для виконання оновлень не розглядається. Це тому, що модель ASP.NET більш орієнтована на прямі команди, як буде показано у наступних лекціях.

Web-застосування та DataSet. Загальнию помилкою є думка про те, що DataSet необхідний для забезпечення масштабованості Web-застосувань. Тепер коли вже розглянуто архітектуру обробки запитів ASP.NET, можливо не важк побачити, що це не зовсім так. Web-застосування виконуються протягом якихось секунд, а це означає, що навіть якщо веб-застосування використовує прямий доступ на базі курсорів, час життя з'єднання настільки малий, що не може сильно вплинути на масштабованість, окрім як для дуже сильно навантажених веб-застосувань.

Фактично, використання DataSet набагато більш виправдано в розподілених застосуваннях з багатофункціональними Windows-клієнтами. У такому сценарії клієнти можуть витягувати DataSet з сервера (можливо, використовуючи для цього Web-службу), працювати з цими об'єктами DataSet протягом тривалого часу, і повторно підключатись до системи тільки в тому випадку, якщо вони потребують оновлення джерела даних проведеними змінами. Це дозволяє системі обслуговувати набагато більшу кількість конкуруючих користувачів, ніж у тому випадку, коли кожен клієнт підтримує пряме, довгостроково існуюче з'єднання. Це також дозволяє ефективно розділяти ресурси за рахунок кешування даних на сервері і використовувати пули з'єднань між клієнтськими запитами.

DataSet також виступає у ролі чіткого пакету інформації для застосувань типу "товстого" клієнта, які лише час від часу підключаються до системи. Наприклад, розглянемо туристичну агенцію, якій потрібно вводити інформацію про замовлення або переглядати інформацію про продажі путівок на портативному комп'ютері. Використовуючи DataSet, застосування на портативному комп'ютері користувача може зберігати автономні дані локально і серіалізувати їх в XML-файл. Це дозволить торговому агенту складати нові замовлення, використовуючи кешовані дані, навіть коли недоступне підключення до Інтернету. Нові дані можуть бути підтверджені пізніше, коли користувач зможе підключитися до системи.

Отже, що ж залишається Web-застосуванням ASP.NET? По суті справи, у є два варіанти вибору. Можна використовувати DataSet або можна використовувати прямі команди, що виключають необхідність в DataSet. Загалом, можна обійтися без DataSet, коли необхідно додавати, вставляти або оновлювати записи. Однак не зможна повністю уникнути застосування DataSet. Фактично, при отриманні запису існує ймовірність того, що виникне необхідніть використовувати DataSet, тому що він підтримує ряд абсолютно необхідних засобів. Зокрема, DataSet дозволяє легко передати блок даних від компонента бази даних на Web-сторінку. DataSet також підтримує зв'язування даних, що дозволяє відображати інформацію в розвинених елементах керування даними, таких як GridView. З цієї причини більшість Web-застосувань витягують дані в DataSet, але поновлення здійснюють через прямі команди.

Примітка. Веб-служби представляють єдиний реальний сценарій використання Web-застосуваннь, в якому може виникнути необхідність у виконанні пакетного оновлення через DataSet. У цьому випадку застосування типу "товстого клієнта" завантажує дані у вигляді DataSet, редагує їх і пізніше підтверджує внесені зміни, відправляючи дані джерелу.

Інтеграція з XML. DataSet також представляє "рідну" XML-cepіалізацію. Вам навіть не потрібно знати про це, щоб скористатися її перевагами накшталдт можливості простої серілізаціі DataSet у файл або передачі DataSet іншому застосуванню через Web-службу. А найкраще те, що цей засіб дозволяє розділити дані з клієнтами, написаними на інших мовах програмування і працюючими на інших операційних системах.

Інтеграція DataSet з XML дозволяє в будь-який момент отримати доступ до інформації в DataSet у вигляді XML-документа. Можна навіть модифікувати значення, видаляти рядки і додавати нові записи за допомогою модифікації XML, не втрачаючи при цьому ніякої інформації. Така глибока інтеграція XML не потрібна у типових самодостатніх Web-застосуваннях. Фактично, при модифікації реляційних даних через модель XML можна зіткнутися з декількома типами проблем, які відсутні при безпосередній взаємодії з DataSet, а саме – проблемами перетворення типів і помилками, пов'язаними з дублюванням записів або порушенням відношень. Переваги підтримки формату XML у DataSet дійсно проявляються у всій красі у таких ситуаціях, коли потрібно здійснити обмін інформацією DataSet з іншими застосуваннями і бізнес-процесами.

Класи DataSet

DataSet – серце автономного доступу до даних. DataSet містить дві важливі складові: колекцію з нуля або більше таблиць (доступних через властивість Tables) і колекцію з нуля або більше відношень, які можна застосовувати для зв'язування таблиць між собою (представлених властивістю Relationships). На рис. 8.3 показана базова структура DataSet.

Примітка. Іноді розробники-новачки в ADO.NET помилково вважають, що DataSet повинен містити всю інформацію певної таблиці джерела даних. Це не так. З міркувань продуктивності DataSet зазвичай використовуться для роботи з невеликою підмножиною від загального обсягу інформації джерела даних. До того ж таблиці в DataSet не зобов'язані відображатися безпосередньо на таблиці джерела даних. Одна таблиця може містити результати запиту по одній таблиці або ж результати запиту злиттям (JOIN), який комбінує дані більш ніж з однієї зв'язаної таблиці.

Рис. 8.3. Анатомія DataSet

Як видно на рис. 8.3, кожен запис DataSet представлений, як об'єкт DataRow. Щоб керувати автономними змінами, DataSet відстежує інформацію про версії по кожному DataRow. При редагуванні значення рядка попереднє значення зберігається у пам'яті і рядок позначається як змінений. При додаванні або видаленні рядка він позначається як доданий або віддалений.

Слід завжди пам'ятати, що інформація в джерелі даних не зачіпається взагалі при роботі з об'єктами DataSet. Всі зміни виконуються локально в DataSet, що знаходиться в пам'яті. DataSet ніколи не підтримує з'єднання з джерелом даних. Якщо необхідно отримати записи з бази і використовувати їх для наповнення таблиці в DataSet, то для цього слід використати інший об'єкт ADO.NET – DataAdapter. Об'єкт DataAdapter також дозволяє оновлювати джерело даних у відповідності зі змінами, виконаними в DataSet (хоча більш кращий підхід для поновлення в ASP.NET полягає в застосуванні команд).

DataSet також має методи, які можуть писати і читати дані і схеми XML, а також методи для швидкого очищення і дублювання даних. У табл. 8,1 коротко перераховані ці методи. Докладніше про XML поговоримо пізніше.

Таблица 8.1. Методи DataSet для роботи з XML та ряд інших

Метод

Опис

GetXml() та GetXmlSchema()

Повертають рядок даних (у розмітці XML) або інформацію схеми для DataSet. Інформація схеми  це структурована інформація на зразок кількості таблиць, їх імен, стовпців, типів даних і встановлених відношень.

WriteXml() та WriteXmlSchema()

Зберігають дані та схеми, представлені DataSet у файлі або потоці формату XML.

ReadXml() та ReadXmlSchema()

Створюють таблиці в DataSet на основі існуючого XML-документа або документа схеми XML. Джерелом ХМL може бути файл або будь-який інший потік.

Clear()

Очищає всі дані таблиць. Однак цей метод залишає недоторканою інформацію про схему та відношення.

Сорy()

Повертає точний дублікат DataSet з тим же набором таблиць, відношень і даних.

Clone()

Повертає DataSet з тією ж структурою (таблицями та відношеннями), але без даних.

Merge()

Приймає інший DataSet, як введення та об'єднує його з поточним DataSet, додаючи нові таблиці і об'єднуючи дані в існуючих.

Клас DataTable

Як видно з рис. 8.3, кожен елемент в колекції DataSet.Tables є екземпляром DataTable. DataTable містить свої власні колекції  колекцію стовпців об'єктів DataColumn (яка описує ім'я і тип даних кожного поля) та колекцію рядків об'єктів DataRow (містять дійсні дані кожного запису).

Порада. ASP.NET додає новий метод CreateDataReader() у класи DataSet та DataTable. Можна викликати його, щоб отримати об'єкт у стилі DataReader, який дозволяє виконувати ітерації по автономним даними. Це засіб зокрема є зручним, якщо у є існуючий код, який очікує DataReader. Метод CreateDataReader() повертає об'єкт DataTableReader, який успадковується від DbDataReader і реалізує інтерфейс IDataReader, як і всі об'єкти DataReader.

Клас DataRow

Кожен об'єкт DataRow представляє один запис у таблиці, який витягується з джерела даних. DataRow  це контейнер для дійсних значень полів. Можна звертатися до них за іменами полів, наприклад, myRow["FieldNameHere"].

Класс DataAdapter

DataAdapter служить мостом між одним DataTable в DataSet та джерелом даних. Він включає всі доступні команди для виконання запитів і оновлення джерела даних. Ключові методи:

Таблиця 8.2. Методи DataAdapter

Метод

Опис

Fill()

Додає DataTable до DataSet за рахунок виконання запиту в SelectCommand. Якщо запит повертає множинні результуючі набори, цей метод додасть множину об'єктів DataTable за раз. Також можна використовувати цей метод для додавання даних до існуючого DataTable.

FillSchema() 

Додає DataTable до DataSet за рахунок виконання запиту в SelectCommand і витягання тільки інформації про схему. Цей метод не додає ніяких даних до DataTable. Замість цього він просто попередньо конфігурує DataTable детальною інформацією про імена стовпців, типи даних, первинних ключах та обмеженнях унікальності.

Update()

Перевіряє всі зміни в окремій DataTable і застосовує пакет цих змін до джерела даних за допомогою виконання відповідних операцій InsertCommand, UpdateCommand та DeleteCommand.

Чтобы позволить DataAdapter редактировать, удалять и добавлять строки, вы должны специфицировать объекты Command для свойств UpdateCommand, DeleteCommand и InsertCommand объекта DataAdapter. Чтобы использовать DataAdapter для наполнения DataSet, потребуется установить SelectCommand.

На рис. 8.4 показано, как DataAdapter и его объекты Command работают вместе с источником данных и DataSet.

Рис 8.4. Взаимодействие DataAdapter с источником данных

Слайд №52. Модифікація та оновлення

Объект DataSet можно редактировать на клиентской машине: редактировать записи, добавлять или удалять DataRow. Однако эти изменения не будут отражены в БД, пока она не будет обновлена с помощью DataAdapter. DataAdapter предоставляет для целей обновления метод Update.

DataSet поддерживает две версии DataRow – исходную (полученную заполнением DataSet) и текущую. В любой момент можно откатиться на исходную

Есть возможность модифицировать данные, используя методы DataRow AcceptChanges и RejectChanges 

Объекты DataTable и DataSet также предоставляют методы AcceptChanges и RejectChanges, которые позволяют принять или отвергнуть все изменения, внесенные в них

Определить состояние DataRow можно с помощью свойства RowState:

  •  Unchanged (строка не менялась вообще или после последнего вызова AcceptChanges)
    •  Modified (строка менялась) 
    •  Added (строка была добавлена, но AcceptChanges не был вызван)
    •  Deleted (строка была удалена) 
    •  Detached (строка создана, но не добавлена ни в один DataRowCollection)

Наполнение DataSet

В следующем примере будет показано, как извлекать данные из таблицы SQL Server и использовать их для наполнения объекта DataTable, принадлежащего DataSet. Вы также увидите, как отображать данные, используя элемент управления Repeater или организуя программный цикл по записям с отображением их одной за другой. Вся логика находится в обработчике события Page.Load.

Сначала код создает соединение и определяет текст запроса SQL:

string connectionString =

WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

SqlConnection con = new SqlConnection(connectionString);

string sql = "SELECT * FROM Employees";

Следующий шаг заключается в создании нового экземпляра класса SqlDataAdapter, который извлечет список сотрудников. Хотя каждый объект DataAdapter поддерживает объекты Command, только один из них (а именно — SelectCommand) необходим для наполнения DataSet. Чтобы еще более облегчить жизнь, вы можете неявно создать необходимый объект Command и присвоить его свойству DataAdapter.SelectCommand за один прием. Для этого вам нужно только передать строку запроса и объект Connection в конструкторе DataAdapter, как показано ниже:

SqlDataAdapter da = new SqlDataAdapter(sql, con);

Теперь потребуется создать новый пустой DataSet и воспользоваться методом DataAdapter.Fill() для выполнения запроса и помещения результата в новый DataTable этого DataSet. В этот момент вы можете также указать имя таблицы. Если этого не сделать, автоматически будет использовано имя по умолчанию "Table". В следующем примере имя таблицы соответствует имени исходной таблицы базы данных, хотя это и не обязательно:

DataSet ds = new DataSet();

da.Fill(ds, "Employees");

Заметьте, что этот код не открывает соединения вызовом Connection.Open(). Вместо этого DataAdapter открывает и закрывает связанное с ним соединение автоматически, когда вызывается метод Fill(). В результате единственная строка кода, которую нужно рассмотреть для помещения в блок с обработкой исключений — вызов DataAdapter.Fill(). Альтернативно вы также можете открывать и закрывать соединение вручную. Если соединение открыто на момент вызова Fill(), то DataAdapter использует это соединение и не станет закрывать его автоматически. Такой подход удобен, если вы хотите быстро выполнить множество последовательных операций с источником данных и не хотите каждый раз нести дополнительные расходы, связанные с повторным открытием-закрытием соединения.

Последний шаг — отображение содержимого DataSet. Быстрее всего это можно сделать, применяя ту же технику, что была продемонстрирована в предыдущей главе, и построить строку HTML, опрашивая каждую запись. Следующий код выполняет цикл по всем объектам DataRow в DataTable и отображает в списке значения полей каждой записи:

StringBuilder htmlStr = new StringBuilder("");

foreach (DataRow dr in ds.Tables["Employees"].Rows)

{

htmlStr.Append("<li>");

htmlStr.Append(dr["TitleOfCourtesy"].ToString());

htmlStr.Append(" <b>");

htmlStr.Append(dr["LastName"].ToString());

htmlStr.Append("</b>, ");

htmlStr.Append (dr["FirstName"] .ToString());

htmlStr.Append("</li>");

}

HtmlContent.Text = htmlStr.ToString();

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

На заметку! Когда вы связываете DataSet с элементом управления, никакие объекты данных не сохраняются в видимом состоянии. Элемент управления данными сохраняет достаточно информации, чтобы показать только те данные, которые нужно отобразить в данный момент. Если вам нужно организовать взаимодействие с DataSet через множественные обратные отсылки, то следует сохранить его в коллекции ViewState вручную (что значительно увеличит размер страницы) или же применить объекты Session либо Cache.

Работа с множественными таблицами и отношениями

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

Первый шаг — инициализация объектов ADO.NET и объявление двух SQL-запросов (для извлечения категорий и продуктов), как показано низке:

string connectionString =

WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

SqlConnection con = new SqlConnection(connectionString);

string sqlCat = "SELECT CategoryID, CategoryName FROM Categories";

string sqlProd = "SELECT ProductName, CategoryID FROM Products";

SqlDataAdapter da = new SqlDataAdapter(sqlCat, con);

DataSet ds = new DataSet();

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

try

{  con.Open();

// Наповнити DataSet даними з таблиці Categories.

da.Fill(ds, "Categories");

// Змінити текст команди та отримати дані таблиці Products.

// Вы могли бы также использовать другой объект DataAdapter

// для выполнения этой задачи.

da.SelectCommand.CommandText = sqlProd;

da.Fill(ds, "Products");

}

finally

{  con.Close();

}

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

Здесь мы имеем DataSet с двумя таблицами. Эти две таблицы связаны в базе Northwind отношением по полю CategoryID. Это поле является первичным ключом таблицы Categories и внешним ключом таблицы Products. К сожалению, ADO.NET не предусматривает никакого способа прочесть информацию об отношениях между таблицами в источнике данных, чтобы применить ее к вашему DataSet автоматически. Поэтому приходится вручную создавать DataRelation, чтобы представить это отношение.

Отношение создается путем определения объекта DataRelation и добавления его к коллекции DataSet.Relations. Когда вы создаете DataRelation, то специфицируете три аргумента конструктора: имя отношения, DataColumn первичного ключа родительской таблицы и DataColumn внешнего ключа дочерней таблицы. Для этого понадобится следующий код:

// Визначити відношення між Categories та Products.

DataRelation relat = new DataRelation("CatProds",

ds.Tables["Categories"].Columns["CategoryID"],

ds.Tables["Products"].Columns["CategoryID"]);

// Додати відношення до DataSet.

ds.Relations.Add(relat);

После того, как вы извлечете все данные, можно пройти в цикле по записям в таблице Categories и добавить имя каждой категории к строке HTML:

StringBuilder htmlStr = new StringBuilder("");

// Пройти у цикліпо всіх записах категоріях та побудувати строку HTML.

foreach (DataRow row in ds.Tables["Categories"].Rows)

{ htmlStr.Append("<b>");

htmlStr.Append(row["CategoryName"].ToString());

htmlStr.Append("</b><ul>");

Здесь присутствует интересная часть. Внутри блока можно обратиться к связанным записям о продуктах для текущей категории, вызвав метод DataRow.GetChildRows(). Получив массив записей о продуктах, можно пройтись по ним с помощью вложенного цикла foreach. Это намного проще, чем код, который понадобился бы для поиска этой информации в отдельном объекте или для выполнения множества запросов с традиционным доступом через соединение.

Следующий фрагмент кода демонстрирует этот подход, извлекая дочерние записи и завершая внешний цикл foreach:

// Получить дочерние (products) записи для родителя (category).

DataRow[] childRows = row.GetChildRows(relat);

// Пройти по всем продуктам данной категории.

foreach (DataRow childRow in childRows)

{ htmlStr.Append("<li>");

htmlStr.Арреnd(childRow["ProductName"].ToString());

htmlStr.Append("</1i>");

}

htmlStr.Append("</ul>");

}

Последний шаг — отображение строки HTML на странице:

HtmlContent.Text = htmlStr.ToString();

На этом код примера завершен. Если вы запустите эту страницу, то увидите вывод, показанный на рис. 8.5.

Совет. Вопрос, который часто задают программисты-новички в ADO.NET: когда нужно использовать запросы слиянием (JOIN), а когда — объекты DataRelation? Наиболее важное условие для правильного ответа — собираетесь ли вы обновлять извлеченные данные? Если да, то применение отдельных таблиц и объекта DataRelation всегда обеспечивает большую гибкость. Если нет, то можно применять любой подход, хотя запрос слиянием и может оказаться более эффективным, потому что включает лишь одно обращение к базе по сети, в то время, как вариант с DataRelation требует два, чтобы наполнить отдельные таблицы.

Ссылочная целостность. Когда вы добавляете отношение таблиц в DataSet, то при этом ограничены правилами ссылочной целостности. Например, вы не можете удалить родительскую запись, если существуют связанные с ней дочерние строки, и не можете создать дочернюю запись, ссылающуюся на несуществующую родительскую. Это может стать причиной проблем, если ваш DataSet содержит лишь частичные данные. Например, если у вас есть полный список клиентских заказов, но только часть списка клиентов, то может случиться, что заказ будет ссылаться на заказчика, который не существует, потому что запись о нем отсутствует в вашем DataSet. Один из способов обойти эту проблему — создать DataRelation без соответствующих ограничений. Чтобы сделать это, используйте конструктор DataRelation, который принимает булевский параметр createConstraints, и установите его значение равным false, как показано ниже:

DataRelation relat = new DataRelation("CatProds",

ds.Tables["Categories"].Columns["CategoryID"],

ds.Tables["Products"].Columns["CategoryID"], false);

Другой подход состоит в отключении всякого рода проверок ограничений (включая проверку уникальных значений), присвоив false свойству DataSet.EnableConstraints перед добавлением отношения.

Пошук визначених рядків

Клас DataTable пропонує зручний метод Select(), що дозволяє витягувати масив об'єктів DataRow на основі SQL-виразу. Вираз, який використовується з методом Select(), відіграє ту ж роль, що і конструкція WHERE оператора SELECT.

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

// Отримати записи з таблиці Products.

DataRow[] matchRows = DataSet.Tables["Products"].Select("Discontinued = 0");

// Пройти по всім продуктам, що мають знижки та сгенерувати перелік

htmlStr.Append("<ul>");

foreach (DataRow row in matchRows)

{

htmlStr.Append("<li>");

htmlStr.Append(row["ProductName"].ToString());

htmlStr.Append("</li>");

}

htmlStr.Append("</ul>");

В этом примере оператор Select() использует достаточно простую строку фильтрации. Однако можно применять более сложные операции и комбинации различных критериев. Более подробную информацию можно найти в руководстве MSDN по библиотеке классов, в описании свойства DataColumn.Expression или же в табл. 8.3 и в разделе "Фильтрация с помощью DataView".

На заметку! Метод Select() имеет одно потенциальное ограничение — он не принимает параметризованных условий. В результате он открыт для атак внедрением SQL. Ясно, что такие атаки, которые может предпринять злонамеренный пользователь в этой ситуации, достаточно ограничены, потому что нет способа получить доступ к реальному источнику данных или выполнить дополнительные команды. Однако тщательно написанные значения могут заставить ваше приложение вернуть дополнительную информацию из таблицы. Если вы создадите фильтрующее выражение с указываемым пользователем значением, то можете пожелать выполнить итерацию по DataTable вручную, чтобы найти нужные вам строки, вместо того, чтобы использовать метод Select().

Прив'язка даних. Хоча ніщо не може перешкодити програмісту генерувати HTML вручну, проходячи циклом по набору автономних даних, у більшості випадків засіб прив'язки даних ASP.NET може трохи полегшити життя. Пізніше прив'язка даних обговорюватиметься у всіх подробицях, але перш ніж перейти до прикладу з DataView в цій лекції, необхідно познайомитися з основами.

Ключова ідея, що лежить в основі прив'язки даних, полягає у асоціюванні зв'язку між об'єктом даних та елементом керування, а інфраструктура прив'язки даних ASP.NET піклується про побудову відповідного виводу. Один з найзручніших у використанні елементів керування, що прив'язується до даних, – це елемент GridView. Gridview володіє вбудованою можливістю створювати таблиці HTML з одним рядком на запис і одним стовпцем на поле. Щоб прив'язати дані до такого елементу керування, як GridView, перш за все необхідно встановити властивість DataSource. Ця властивість вказує на об'єкт, який містить інформацію, яку потрібно відобразити. В даному випадку це DataSet:

GridView1.DataSource = ds;

Оскільки елемент керування, що прив'язується до даних, може бути пов'язаний тільки з однією таблицею (а не з усім DataSet), також слід явно специфікувати таблицю, яку необхідно використовувати. Це можна зробити присвоєнням властивості DataMember імені відповідної таблиці, як показано нижче:

GridView1.DataMember = "Employees";

І, нарешті, як тільки визначено, де знаходяться дані, тут же потрібно викликати метод елемента керування DataBind(), щоб скопіювати інформацію з DataSet в елемент керування. Якщо забути виконати цей крок, елемент керування залишиться порожнім і інформація на сторінці не з'явиться.

GridView1.DataBind();

Також, можна викликати метод DataBind() поточної сторінки, і він пройде по усіх елементах керування, що підтримують прив'язку даних, і для кожного викличе метод DataBind().

Примітка. У наступних прикладах використовується прив'язка даних для демонстрації засобів фільтрації і сортування GridView. Детальніше прив'язка даних та елемент управління GridView вивчаються у наступних лекціях.

Клас DataView

DataView визначає зовнішнє представлення об'єкта DataTable – іншими словами, подання даних у DataTable, яке може включати користувацькі настройки фільтрації та сортування. Щоб дозволити конфігурувати ці налаштування, в DataView передбачені такі властивості, як Sort та RowFilter. Ці властивості дозволяють вибирати дані, які повинні бути видимі у поданні. Однак вони не впливають реальні дані DataTable. Наприклад, якщо відфільтрувати таблицю так, щоб приховати певні рядки, то ці рядки залишаться в DataTable, але не будуть доступні через DataView.

DataView зручний, зокрема, в сценаріях прив'язки даних. Він дозволяє показати тільки підмножину загального набору даних таблиці, без необхідності обробляти або змінювати дані, якщо вони необхідні для інших завдань.

Кожен об'єкт DataTable має асоційований з ним за промовчанням DataView, хоча допускається створення множини об'єктів DataView для представлення різних видів однієї й тієї ж таблиці. DataView за промовчанням представлений властивістю DataTable.DefaultView.

У наступних прикладах буде показано, як створювати деякі екранні таблиці, що відображають записи з сортуванням по різних полях і відфільтровані у відповідності до заданих виразів.

Сортування за допомогою DataView. У наступному прикладі використовується сторінка з трьома елементами керування GridView. Під час завантаженні цієї сторінки всі елементи прив'язуються до однієї DataTable. Однак вона використовує три різні подання, кожне з яких сортує результат за різними полями.

Код починається з отримання списку співробітників у DataSet:

// Створити Connection, DataAdapter та DataSet

string connectionString =

WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

SqlConnection con = new SqlConnection(connectionString);

String sql =

"SELECT TOP 5 EmployeeID, TitleOfCourtesy, LastName, FirsName FROM Employees";

SqlDataAdapter da = new SqlDataAdapter(sql, con);

DataSet ds = new DataSet();

// Наповнити DataSet

da.Fill(ds, "Employees");

Наступний крок – наповнення елемента керування GridView за допомогою прив'язки даних. Щоб прив'язати перший з них, можна безпосередньо застосувати DataTable, що змусить його використовувати DataView за промовчанням і відобразити дані. Для двох інших необхідно створити новий об'єкт DataView, а потім явно використовувати його властивість Sort:

// Привязати оригінальні дані до элемента №1.

grid1.DataSource = ds.Tables["Employees"];

// Сортувати за прізвищем та привязати до елемента №2.

DataView view2 = new DataView(ds.Tables["Employees"]);

view2.Sort = "LastName";

grid2.DataSource = view2;

// Сортувати за іменем та привязати до елемента №3.

DataView view3 = new DataView(ds.Tables["Employees"]);

view3.Sort = "FirstName";

grid3.DataSource = view3;

Сортування відображуваних даних полягає у присвоєнні властивості DataView.Sort коректного виразу сортування. У даному прикладі сортування виконується в кожному поданні за значеннями єдиного поля, але також можна сортувати за декількома полями, вказуючи список полів, розділених комами, наприклад:

view2.Sort = "LastName, FirstName";

Примітка. Сортування виконується відповідно до типу даних стовпця. Числові стовпці та стовпці дат упорядковуються від менших значень до більших. Строкові стовпці сортуються в алфавітно-цифровому порядку, незалежно від регістра, якщо властивість DataTable.CaseSensitive встановлено в False (за промовчанням). Стовпці, що містять двійкові дані, не можуть бути відсортовані. Можна також використовувати атрибути ASC або DESC для сортування у зростаючому або спадаючому порядку. Прив'язавши екранні таблиці, необхідно ініціювати процес прив'язки даних, що копіює значення DataTable в елемент керування. Це можна зробити для кожного елемента окремо або для всієї сторінки в цілому – викликом Page.DataBind(), як у наступному прикладі:

Page.DataBind();

На рис. 8.6 показаний результат роботи сторінки.

Фільтрація за допомогою DataView

Можна також використовувати DataView для застосування користувацької фільтрації, щоб відображати на екрані тільки певні рядки. Для забезпечення цієї можливості служить властивість RowFilter. Властивість RowFilter діє подібно конструкції WHERE в SQL-запиті. Використовуючи її, можна обмежити результат за допомогою логічних операцій (таких як <,> та =) і широкого діапазону критеріїв. У табл. 8,3 перераховані найчастіше використовувані операції фільтрування.

Таблиця 8.3. Операції фільтрування

Операція

Опис

<, >, <= и >=

Виконують порівняння більш ніж одного значення. Ці порівняння можуть бути числовими (з даними числових типів) або алфавітними порівняннями за словником (з рядковими даними).

<> и =

Виконують перевірку на еквівалентність.

NOT

Звертає на протилежний логічний вираз. Може використовуватися у поєднанні з будь-якої іншої конструкцією.

BETWEEN

Вказує діапазон включно. Наприклад, Units BETWEEN 5 AND 15 вибирає рядки, у яких значення стовпця Units знаходиться в межах від 5 до 15.

IS NULL

Перевіряє стовпець на null-значення.

IN(a, b, с)

Коротка форма операції OR з одним і тим же полем. Перевіряє еквівалентність значення стовпця будь-якого з перерахованих значень (a, b та c).

LIKE

Виконує перевірку відповідності рядкового значення шаблону.

+

Складає два числа або "склеює" два рядка.

-

Віднімає одне числове значення з іншого.

*

Перемножує два числових значення.

/

Ділить одне числове значення на інше

%

Обчислює модуль (залишок від ділення одного числа на інше).

AND

Комбінує більше однієї логічної конструкції. Для відображення запис повинен відповідати всім критеріям, об'єднаним за допомогою AND.

OR

Комбінує більше однієї логічної конструкції. Для відображення запис повинен відповідати хоча б одному критерію з об'єднаних за допомогою OR.

Следующий пример страницы включает три элемента управления GridView. Каждый из них привязан к одной и той же DataTable, но с разными установками фильтрации:

string connectionString =

WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

SqlConnection con = new SqlConnection(connectionString);

string sql = "SELECT ProductID, ProductName, UnitsInStock, UnitsOnOrder, " +

"Discontinued FROM Products";

SqlDataAdapter da = new SqlDataAdapter(sql, con);

DataSet ds = new DataSet();

da.Fill(ds, "Products");

// Фільтрувати продукт Chocolade.

DataView view1 = new DataView(ds.Tables["Products"]);

view1.RowFilter = "ProductName = 'Chocolade'";

GridViewl.DataSource = view1;

// Фільтрувати продукты, яких немає у замовленнях та на складі.

DataView view2 = new DataView(ds.Tables["Products"]);

view2.RowFilter = "UnitsInStock = 0 AND UnitsOnOrder = 0";

GridView2.DataSource = view2;

// Фільтрувати продукти, чия назва починаєтся з букви Р.

DataView view3 = new DataView(ds.Tables["Products"]);

view3.RowFilter = "ProductName LIKE 'P%'";

GridView3.DataSource = view3;

this.DataBind();

Запуск этой страницы заполнит три экранные таблицы, как показано на рис. 8.7.

Совет. DataView также включает свойство RowStateFilter, которое вы можете использовать для фильтрации DataTable так, чтобы он отображал только строки в определенном состоянии (вставленные, удаленные, модифицированные или неизмененные). По умолчанию это свойство установлено в CurrentRows (для показа всех строк за исключением тех, что помечены как удалённые).

Розширене фільтрування з відношеннями

DataView дозволяє застосовувати деякі складні вирази фільтрації. Одним з маловідомих засобів є можливість фільтрувати рядки на основі відношень. Наприклад, можна відобразити категорії, які містять більше 20 продуктів, або відобразити замовників, які зробили певну кількість покупок. В обох випадках потрібно фільтрувати одну таблицю на базі інформації з іншої зв'язаної таблиці.

Щоб створити такий рядок фільтруючого виразу, необхідно скомбінувати дві складові:

 Відношення, що пов'язує дві таблиці.

 Агрегатну функцію, таку як AVG(), МАХ(), MIN() або COUNT(). Ця функція застосовується до даних у пов'язаних записах.

Наприклад, припустимо, що DataSet заповнили таблицями Categories і Products та визначили між ними наступне відношення:

// Определение отношения между Categories и Products.

DataRelation relat = new DataRelation ("CatProds",

ds.Tables["Categories"].Columns["CategoryID"],

ds.Tables["Products"].Columns["CategoryID"]);

// Добавить отношение к DataSet.

ds.Relations.Add(relat);

Тепер можна фільтрувати дані таблиці Categories, використовуючи выраз фільтрації, заснований на таблиці Products. Наприклад, нехай необхідно показати тільки ті записи про категорії, для яких існує хоча б один продукт вартістю більше $50. Щоб добитися цього, використовується функцію MAX() поряд з ім'ям відношення таблиць (CatProds). Так буде виглядати необхідний рядок фільтрації:

МАХ(Child(CatProdB).UnitPrice) > 50

Застосуємо цей рядок до DataView:

DataView view1 = new DataView(ds.Tables["Categories"));

viewl.RowFilter = "MAX(Child(CatProds).UnitPrice) > 50";

GridView1.DataSource = view1;

В результаті GridView покаже лише ті категорії, в яких є продукти, дорожчі $50.

Вычисляемые столбцы

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

В качестве примере рассмотрим столбец, использующий строковую конкатенацию для комбинации имени и фамилии в одном поле:

Обчислювані стовпці. Крім полів, витягнутих з джерела даних, можна застосовувати обчислювані стовпці. Обчислювані стовпці ігноруються при витяганні і оновленні даних. Замість цього вони є значеннями, які обчислюються на основі комбінації існуючих значень. Щоб створити обчислюваний стовпець, необхідно створити новий об'єкт DataColumn (спеціфікуючи його ім'я та тип) і встановити його властивість Expression. І, нарешті, додати цей об'єкт DataColumn у колекцію стовпців об'єкта DataTable за допомогою методу Add().

В якості прикладу розглянемо стовпець, який є конкатенацію імені та прізвища в одному полі:

DataColumn fullName = new DataColumn(

"FullName", typeof(string),

"TitleOfCourtesy + ' ' + LastName + ', ' + FirstName");

ds.Tables["Employees"].Columns.Add(fullName);

Порада. Зрозуміло, що також можна виконати запит, який створює обчислювані поля. Однак такий підхід створює більше труднощів при подальшому оновленні джерела даних і додає йому роботи. З цієї причини часто краще створювати обчислювані стовпці в DataSet.

Можна створювати обчислювані стовпці, які включають інформацію з пов'язаних рядків. Наприклад, можна додати в таблицю стовпець категорії, що показує кількість пов'язаних рядків таблиці Products. У цьому випадку необхідно спочатку визначити видношення у об'єкті DataRelation. Також потрібно застосувати агрегатну функцію SQL, таку як AVG(), МАХ(), MIN() або COUNT().

У наступному прикладі створені три обчислювані стовпці, кожен з яких використовує агрегатну функцію і відношення таблиць:

string connectionString =

WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

SqlConnection con = new SqlConnection(connectionString);

string sqlCat = "SELECT CategoryID, CategoryName FROM Categories";

string sqlProd = "SELECT ProductName, CategoryID, UnitPrice FROM Products";

SqlDataAdapter da = new SqlDataAdapter(sqlCat, con);

DataSet ds = new DataSet();

try

{  con.Open();

da.Fill(ds, "Categories");

da.SelectCommand.CommandText = sqlProd;

da.Fill(ds, "Products");

}

finally

{ con.Close();

}

// Визначення відношення між Categories та Products.

DataRelation relat = new DataRelation("CatProds",

ds.Tables["Categories"].Columns["CategoryID"],

ds.Tables["Products"].Columns["CategoryID"]);

// Додати відношення до DataSet.

ds.Relations.Add(relat);

// Створити обчислювані стовпці

DataColumn count = new DataColumn(

"Products (#)", typeof(int), "COUNT(Child(CatProds).CategoryID)");

DataColunm max = new DataColumn(

"Most Expensive Product", typeof(decimal), "MAX(Child(CatProds).UnitPrice)");

DataColumn min = new DataColumn(

"Least Expensive Product", typeof(decimal), "MIN(Child(CatProds).UnitPrice)");

// Додати стовбці

ds.Tables["Categories"].Columns.Add(count);

ds.Tables["Categories"].Columns.Add(max);

ds.Tables["Categories"].Columns.Add(min);

// Показати дані

GridView1.DataSource = ds.Tables["Categories"];

GridView1.DataBind();

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

Резюме. В этой главе вы научились создавать основные компоненты баз данных и ближе познакомились с классами DataSet и DataView. В следующей главе вы продолжите работать с теми же компонентами базы данных и DataSet, хотя и на новом уровне. Вы узнаете, как элементы управления источника данных "обертывают" мир ADO.NET с помощью более высокого уровня абстракции, позволяя строить богатые выразительные страницы для отображения данных при минимальном объеме кода.

Построение компонента доступа к данным

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

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

  •  Открывайте и закрывайте соединения быстро. Открывайте соединение с базой данных при каждом вызове метода и закрывайте перед его завершением. Соединения никогда не должны удерживаться открытыми между клиентскими запросами, и клиент не должен управлять получением и освобождением соединений. Если клиенту предоставить такую возможность, то возникнет вероятность того, что соединение может не быть закрыто максимально быстро, или может оставаться неадекватно открытым, что наносит ущерб масштабируемости.
  •  Реализуйте обработку ошибок. Используйте обработку ошибок, чтобы гарантировать закрытие соединения, даже если команда SQL сгенерирует исключение. Помните, что соединения — ограниченный ресурс, и использование их всего на несколько секунд дольше необходимого может пагубно отразиться на общей производительности.
  •  Следуйте практике дизайна без состояний. Передавайте всю необходимую методу информацию в его параметрах, и возвращайте все извлекаемые им данные через возвращаемое значение. Если вы создадите класс, который поддерживает состояние, его трудно будет реализовать в виде Web-службы или использовать в сценарии балансировки нагрузки. К тому же если компонент базы данных будет размещен вне процесса, то каждый вызов метода потребует заметных накладных расходов, а применение множественных вызовов для установки свойств займет гораздо больше времени, чем единственный вызов метода с передачей всей информации в параметрах.
  •  Не позволяйте клиенту указывать информацию строки соединения. Это представляет собой риск нарушения безопасности, вероятность сбоя устаревших клиентов и ослабляет эффективность пулов соединений, которые требуют точного совпадения строк соединений.
  •  Не подключайтесь под клиентским идентификатором пользователя. Как упоминалось в предыдущей главе, возможность вариаций параметров в строке соединения мешает работе пула соединений. Вместо этого полагайтесь на безопасность на основе ролей или на систему на базе билетов (ticket-based system), посредством которой выполняется аутентификация пользователей для выполнения ограниченных операций. Эта модель также работает быстрее, чем попытки выполнения запросов от имени некорректной учетной записи с ожиданием ошибки.
  •  Не позволяйте клиентам использовать широкие открытые запросы. Каждый запрос должен благоразумно выбирать лишь те строки, которые ему действительно нужны. К тому же, где только возможно, ограничивайте результаты с помощью конструкции WHERE. Например, извлекая записи о заказах, вы можете установить минимальный диапазон дат (или применить SQL-конструкцию вроде ТОР 1000). Без таких предосторожностей ваше приложение может работать хорошо в начале, но замедляться по мере роста базы данных и размера клиентских запросов, которые могут нагружать как базу данных, так и сеть.

Хороший, прямолинейный дизайн компонентов баз данных использует отдельный класс для каждой таблицы базы (или логически связанной группы таблиц). Общие методы доступа к данным, такие как вставка, удаление и модификация записей, должны быть помещены в отдельные, не поддерживающие состояние, методы. И, наконец, каждое обращение к базе данных должно использовать выделенную хранимую процедуру. На рис. 8.1 показан тщательно спроектированный многослойный дизайн.

Побудова компонента доступу до даних

У професійних застосуваннях код бази даних не вбудовується безпосередньо на клієнті, а інкапсулюється у спеціальний виділений клас. Щоб виконати операцію бази даних, клієнт створює екземпляр цього класу і викликає відповідний метод.

При створенні класу даних потрібно слідувати основним рекомендаціям, які будуть наведені нижче. Це гарантує, що буде створено добре інкапсульований, оптимізований компонент бази даних, який при необхідності можна виконувати в окремому процесі, і навіть застосовувати в багатосерверній конфігурації для балансування навантаження.

Відкривайте і закривайте з'єднання швидко. Відкривайте з'єднання з БД при кожному виклику методу і закривайте перед його завершенням. З'єднання ніколи не повинні утримуватися відкритими між клієнтськими запитами, і клієнт не повинен керувати отриманням та звільненням з'єднань. Якщо клієнту надати таку можливість, то виникне ймовірність того, що з'єднання може не бути закрите максимально швидко, або може залишатися відкритим неадекватно довго, що завдає шкоди масштабованості.

Реалізуйте обробку помилок. Використовуйте обробку помилок, щоб гарантувати закриття з'єднання, навіть якщо команда SQL згенерує виключення. Пам'ятайте, що з'єднання – обмежений ресурс, і використання їх всього на декілька секунд довше необхідного може згубно позначитися на загальній продуктивності.

Слідуйте практиці дизайну без станів. Передавайте всю необхідну методу інформацію в його параметрах, і повертайте всі витягувані ним дані через значення, що повертається. Якщо створити клас, який підтримує стан, його важко буде реалізувати у вигляді Web-служби або використовувати у сценарії балансування навантаження. До того ж якщо компонент бази даних буде розміщений поза процесом, то кожен виклик методу зажадає помітних накладних витрат, а застосування множинних викликів для установки властивостей займе набагато більше часу, ніж єдиний виклик методу з передачею всієї інформації в параметрах.

Не дозволяйте клієнтові вказувати інформацію рядка з'єднання. Це збільшує ризик порушення безпеки, ймовірність збою застарілих клієнтів і послаблює ефективність пулів з'єднань, які вимагають точного збігу рядків з'єднань.

Не підключайтеся під клієнтським ідентифікатором користувача. Як згадувалося в попередньому розділі, можливість варіацій параметрів у рядку з'єднання заважає роботі пулу з'єднань. Замість цього покладайтеся на безпеку на основі ролей або на систему на базі квитків (ticket-based system), за допомогою якої виконується аутентифікація користувачів для виконання обмежених операцій. Ця модель також працює швидше, ніж спроби виконання запитів від імені некоректного облікового запису з очікуванням помилки.

Не дозволяйте клієнтам використовувати широкі відкриті запити. Кожен запит повинен розсудливо вибирати лише ті рядки, які йому дійсно потрібні. До того ж, де тільки можливо, обмежуйте результати за допомогою конструкції WHERE. Наприклад, витягуючи запис про замовлення, можна встановити мінімальний діапазон дат (або застосувати SQL-конструкцію на зразок ТОР 1000). Без таких пересторог застосування може працювати добре на початку, але сповільнюватися зі зростанням бази даних і розміру клієнтських запитів, які можуть навантажувати як базу даних, так і мережу.

Хороший, прямолінійний дизайн компонентів баз даних використовує окремий клас для кожної таблиці бази (або логічно пов'язаної групи таблиць). Загальні методи доступу до даних, такі як вставка, видалення і модифікація записів, повинні бути поміщені в окремі методи, що не підтримують стан. І, нарешті, кожне звернення до бази даних повинне використовувати виділену збережену процедуру. На рис. 8,1 показаний ретельно спроектований багатошаровий дизайн.

Рис. 8.1. Багатошаровий дизайн класу бази даних

Следующий пример демонстрирует простой компонент базы данных. Вместо помещения кода работы с базой на Web-страницу, он следует более совершенной практике дизайна, связанной с выделением кода в отдельный класс, который может быть использован на многих страницах. Этот класс при желании может быть затем скомпилирован как часть отдельного компонента. Вдобавок строка соединения извлекается из раздела <connectionStrings> файла web.config, а не кодируется жестко.

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

Стратегии параллелизма

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

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

  •  Обновление "последний выигрывает". Это наименее ограничивающая форма управления параллелизмом, в которой обновление всегда подтверждается (если только исходная строка не была удалена). Всякий раз, когда фиксируется обновление, применяются все значения полей. Обновление "последний выигрывает" имеет смысл, если коллизии данных относительно редки. Например, вы можете успешно использовать этот подход, если только одно лицо ответственно за обновление данной группы записей. Обычно такая стратегия реализуется написанием конструкции WHERE, идентифицирующим обновляемую строку на основе поля первичного ключа. Метод UpdateEmployee() в предыдущем примере использует именно такой подход.

UPDATE Employees SET ... WHERE EmployeeID=@ID

  •  Обновление при полном соответствии. Чтобы реализовать эту стратегию, вы добавляете конструкцию WHERE, которая пытается сравнить текущие значения всех полей в операторе UPDATE. Таким образом, даже если единственное поле было модифицировано в параллельном сеансе, запись не будет соответствовать старому состоянию, а потому обновление не удастся. Например, если два пользователя пытаются модифицировать разные части одной и той же записи, то запрос обновления от второго пользователя будет отклонен, даже если он не приводит к конфликту. Другая, более существенная проблема стратегии полного соответствия состоит в том, что она порождает громоздкие, неэффективные операторы SQL. Вы можете реализовать ее более эффективно с помощью временных меток (см. следующий абзац).

UPDATE Employees SET ...

WHERE EmployeeID=@ID AND FirstName=@FirstName

AND LastName=@LastName ... 

  •  Обновление на базе временных меток. Большинство систем баз данных поддерживают столбцы временных меток, которые источник данных обновляет автоматически при каждом обновления записи. Нет необходимости модифицировать их вручную. Однако вы можете проверять их изменение, и таким образом определять, что другой пользователь недавно применил свое обновление. Если вы напишете оператор UPDATE с конструкцией WHERE, которая включает первичный ключ и текущую временную метку, то вы гарантировано обновите запись только в том случае, если она не была модифицирована — как и в случае обновления при полном соответствии.

UPDATE Employees SET ...

WHERE EmployeeID=@ID

AND TimeStamp=@TimeStamp

  •  Обновление измененных значений. Этот подход пытается применить только измененные значения в команде UPDATE, позволяя двум пользователям вносить одновременные изменения, если они касаются разных полей. Проблема этого подхода — в сложности, поскольку необходимо отслеживать, какие именно значения изменены пользователем (чтобы включить их в конструкцию WHERE).

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

Если у вас большая, сложная запись, и вы хотите поддерживать различные типы редактирования, то простейший путь решения подобной проблемы состоит в создании более узконаправленных методов. Вместо создания обобщенного метода UpdateEmployee() используйте более узконаправленные методы вроде UpdateEmployeeAddress() или ChangeEmployeeStatus(). Эти методы могут выполнять более ограниченные операторы UPDATE без риска повторного применения старых значений.


 

А также другие работы, которые могут Вас заинтересовать

43406. Динамика в фотографии 1.19 MB
  Передача характера движения. Направление движения в кадре. Сохранить динамику действия движения внутреннего состояния человека показать развитие события во времени и пространстве в единичном снимке фиксирующем всего лишь кратчайший миг происходящего момент длиною в 1 30 1 100 1 500 долю секунды довольно непросто. И дело конечно не только в передаче на снимке движения как такового как перемещения объекта съемки в пространстве.
43408. Моделирование технологии получения отливки «ОТЛИВКА» из сплава марки СПЛАВ методом литья в МЕТОД ЛИТЬЯ в системе компьютерного моделирования литейных процессов СКМ 578 KB
  Произвести компьютерное моделирование заполнения и затвердевания отливки по заданному технологическому процессу. Провести анализ полученных результатов и дать рекомендации по улучшению предложенного технологического решения. Обозначить вероятные проблемы и возможные дефекты литья, выявленные в ходе анализа. Применить для анализа СКМ ЛП LVMFlow.
43409. Проект мероприятия по озеленению и благоустройству территории сквера “Победа” 263 KB
  Летнее повышение температуры вызывается тропическим воздухом, проникающим из Средиземноморья. Юго-восточные ветры приносят из пустынь Средней Азии засуху. Воздушные потоки с Атлантики приносят пасмурную погоду, снегопады, а летом – облачность и дожди.
43410. Разработка и исследование математическую модель функционирования бытового электрического водонагревателя 621.5 KB
  Интегрированная среда разработки Trce ModeОбщие сведения TRCE MODE состоит из инструментальной системы интегрированной среды разработки и из набора исполнительных модулей. С помощью исполнительных модулей TRCE MODE проект АСУ запускается на исполнение в реальном времени. TRCE MODE позволяет создавать проект сразу для нескольких исполнительных модулей узлов проекта.
43411. Усилитель мощности звуковой частоты 296.5 KB
  Очень широкое применение в современной технике имеют усилители у которых как управляющая так и управляемая энергия представляет собой электрическую энергию. Такие усилители называют усилителями электрических сигналов. Усилители электрических сигналов далее просто усилители применяются во многих областях современной науки и техники. Особенно широкое применение усилители имеют в радиосвязи и радиовещании радиолокации радионавигации радиопеленгации телевидении звуковом кино дальней проводной связи технике радиоизмерений где они...
43412. УСИЛИТЕЛЬ МОЩНОСТИ СИГНАЛОВ ЗВУКОВОЙ ЧАСТОТЫ 551 KB
  Выходная группа каскадов –двухтактный эмиттерный повторитель на составных квазикомплементарных транзисторах работающих в режиме АВ. наметились два направления в конструировании любительских УМСЗЧ – проектирование ультролинейных усилителей имеющих коэффициент гармоник порядка тысячных долей процента но весьма сложных в регулировке и налаживании и создание сравнительно простых усилительных устройств обычно на одном – двух операционных усилителях и двух – четырёх транзисторах легко повторяемых но не позволяющих получить требуемое для...
43413. Расчет и конструирование плиты и главной балки монолитного ребристого перекрытия 923.5 KB
  Здания по крайним осям 253x23 м причем расстояние между продольными осями здания l1=84м – что является пролетом главной балки lГБ=84м а между поперечными l2=46м – что является пролетом второстепенной балки lВБ=46м. Арматура рабочая продольная: для плиты класса ВрI арматурная проволока 35 мм и АIII для главной балки класса АIII в Арматура рабочая поперечная для главной балки класса АIII г Арматура монтажная: для плиты класса ВрI; для главной балки АIII. Компоновка перекрытия Вдоль поперечных осей здания...