Щелкните, чтобы оценить и отправить отзыв
 Службы данных. Разработка надежных ...
Related Articles

We demonstrate creating a peer-to-peer processing platform where multiple players function together for a common purpose: getting your work done.

Matt Neely

MSDN Magazine June 2009

...

Read more!

This month we demonstrate how easy it is to use IronPython to test .NET-based libraries.

James McCaffrey

MSDN Magazine June 2009

...

Read more!

Use Test-Driven Development with mock objects to design object oriented code in terms of roles and responsibilities, not categorization of objects into class hierarchies.

Isaiah Perumalla

MSDN Magazine June 2009

...

Read more!

Microsoft Velocity exposes a unified, distributed memory cache for client application consumption. We show you how to add Velocity to your data-driven apps.

Aaron Dunnington

MSDN Magazine June 2009

...

Read more!

Jeremy Miller continues his discussion of persistence patterns by reviewing the Unit of Work design pattern and examining the issues around persistence ignorance.

Jeremy Miller

MSDN Magazine June 2009

...

Read more!

Popular Articles

Jason Clark

MSDN Magazine July 2003

...

Read more!

C# allows developers to embed XML comments into their source files-a useful facility, especially when more than one programmer is working on the same code. The C# parser can expand these XML tags to provide additional information and export them to an external document for further processing. This article shows how to use XML comments and explains the relevant tags. The author demonstrates how to set up your project to export your XML comments into convenient documentation for the benefit of other developers. He also shows how to use comments ...

Read more!

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Paul DiLascia

MSDN Magazine August 2002

...

Read more!

This article introduces 10 development tools that can increase your productivity, give you a better understanding of .NET, and maybe even change the way that you develop applications. The tools covered include NUnit to write unit tests, Reflector to examine assemblies, FxCop to police your code, Regulator to build regular expressions, NDoc to create code documentation and five more.

James Avery

MSDN Magazine July 2004

...

Read more!

Службы данных
Разработка надежных и масштабируемых приложений с помощью служб данных SQL Server
Дэвид Робинсон (David Robinson)

В этой статье рассматриваются следующие вопросы/
  • Модель данных SSDS
  • Управление сущностями, контейнерами и центрами
  • Создание примера веб-приложения
  • Сериализация и десериализация класса
В данной статье использованы следующие технологии.
SQL Server
Эта статья основана на предварительной версии служб данных SQL Server. Любые содержащиеся в ней сведения могут быть изменены.
Платформа данных Майкрософт состоит из богатого набора продуктов и технологий для всех типов данных – от неструктурированных двоичных данных на одном краю спектра до высокоструктурированных кубов интерактивной аналитической обработки (online analytical processing – OLAP) на другом. Хотя эти технологии подходят для любых возможных вариантов применений, обычно они требуют предварительных расходов времени и ресурсов оборудования до начала программирования решения.
Оценить требования к объему хранения и вычислительным мощностям, которые предложение предъявит за свой жизненный цикл, часто бывает сложно. Эта проблема может быть особенно острой для начинающей компании с ограниченным бюджетом и недостатком данных для планирования расходов на оборудование. Для решение проблем, связанных с этими предварительными расходами, корпорация Майкрософт недавно добавила к своей и без того основательной платформе данных новую технологию: Службы данных SQL Server® (SQL Server Data Services – SSDS).
Не являясь продуктом в терминах традиционного «коробочного» программного обеспечения, SSDS – это надежная, не нуждающаяся в масштабировании служба данных, использующая внутри себя проверенную технологию SQL Server и предоставляющая свои функции через стандартные для отрасли интерфейсы веб-служб. Службы SSDS предоставляют простую в использовании, гибкую модель данных, доступ к которой возможен по открытым протоколам, соответствующим отраслевым стандартам.
Вне зависимости от того, ведется ли разработка с помощью Microsoft® .NET Framework, Java или иных технологий, SSDS можно использовать как хранилище данных. Вдобавок, службы SSDS разработаны, чтобы позволить разработчикам быстро предоставлять учетные записи и немедленно начинать разработку для них.
Службы SSDS предоставляют разработчикам сервер данных, который не скован наличием отсеков для дисков или помещения для своего размещения. Использование SSDS как платформы данных позволяет приложению потреблять столько данных, сколько нужно. Вне зависимости от того, хранит ли пользователь гигабайт или петабайт данных, он платит только за потребляемые им ресурсы.
Нагрузка на многие веб-узлы и приложения изменяется во времени циклически, что требует достаточной емкости для пиковых нагрузок, даже если эти нагрузки возникают лишь время от времени. Использование SSDS как сервера данных позволяет уделить основное внимание приложению, а не на планированию емкости платформы данных.
В этой статье я представлю основы разработки решения данных на основе SSDS. Я начну с планирования модели данных, применяемой SSDS. Затем я углублюсь в технические детали разработки на примере создания простой сетевой системы тематических объявлений, показанной на рис. 1, с использованием SSDS.
Рис. 1 Пример приложения тематических объявлений, размещенного в SSDS (щелкните изображение, чтобы увеличить его)

Модель данных SSDS
SSDS предоставляет гибкую модель данных на основе сущностей. Эта модель имеет три основных элемента – сущность, контейнер и центр, – именуемых концепциями ACE (authority, container, entity) (см. рис. 2 ). Центр SSDS может быть соотнесен с базой данных в реляционной терминологии. При предоставлении центра службы SSDS создадут доменное имя для доступа к этому центру. Например, при предоставлении центра, именуемого ssdsdemo, доступ к нему возможен по следующему идентификатору URI:
ssdsdemo.data.beta.mssds.com
Рис. 2. Базовые компоненты SSDS
Центр также является единицей географического расположения, а это значит, что созданное доменное имя будет соответствовать конкретному центру данных Майкрософт, где размещены данные. В случае создания двух центров, например americasdemo.data.beta.mssds.com и europedemo.data.beta.mssds.com, каждый из них будет связан со своим центром данных, расположенным ближе всего к пользователям.
Центр содержит коллекцию контейнеров – контейнер подобен таблице в реляционной базе данных. Ключевое различие состоит в том, что к таблице базы данных прикрепляется схема, чтобы сделать все строки в таблице однородными. Контейнеру в SSDS не требуется схема, что позволяет сохранять разнородные сущности в одной удобной точке. Это просто коллекция сущностей. В текущей версии SSDS областью действия всех запросов является один контейнер.
Другим важным моментом, который следует отметить здесь, является то, что все контейнеры размещаются на различных узлах в кластере SSDS. Для дополнительного повышения производительности в SQL Server различные таблицы помещаются на отдельные физические диски; это позволяет максимально использовать возможности чтения и записи. Аналогично обстоит дело и в SSDS, за исключением того, что каждый контейнер будет находиться на отдельном компьютере. Производительность можно дополнительно повысить, разделив данные на несколько контейнеров и сделав запросы многопоточными, поскольку тогда чтение и запись не будут ограничены единственным компьютером.
Наконец, насчет контейнеров следует отметить, что хотя каждый контейнер размещается на отдельном узле в кластере SSDS, данные также реплицируются на нескольких других узлах в целях аварийного восстановления. Если машина, на которой находится контейнер, отказывает, на ее место автоматически подставляется одна из резервных реплик, чтобы гарантировать отсутствие каких-либо потерь данных или производительности.
Сущность можно сравнить со строкой в реляционной базе данных. Сущность является просто набором свойств пар имени/значения. Эти пары имени/значения сгруппированы в две категории: различенные свойства системы и гибкие свойства.
Различенные свойства системы являются общими для всех сущностей; в их число входят идентификатор, вид и версия. Идентификатор дает уникальное определение сущности. Идентификаторы должны быть уникальны внутри контейнеров, где они существуют, но различные контейнеры могут иметь сущности с одинаковым идентификатором. Вид используется для сведения похожих сущностей вместе. К сущностям не прикрепляется схема, так что наличие сущностей одного вида не гарантирует идентичность структуры, и для определения текущей версии сущности используется свойство версии. Это значение обновляется при каждой операции.
В гибких свойствах разработчик сохраняет данные приложения. Гибкие свойства поддерживают простые типы: string, decimal, bool, datetime и binary. Каждое гибкое свойство индексируется вплоть до первых 256 байтов.

Создание системы тематических объявлений
Чтобы продемонстрировать компоненты SSDS и дать обзор того, с чем может столкнуться разработчик, я разберу реализацию простой системы тематических объявлений в сети, Contoso Classifieds. Этот пример состоит из двух отдельных приложений: приложения Windows® Forms для администрирования системы и приложения ASP.NET – основного узла Contoso Classifieds, который будет посещаться пользователями. Приложение получает доступ к службам SSDS через интерфейс SOAP в приложении Windows Forms и через интерфейс REST (Representational State Transfer) в приложении ASP.NET.
При подписке на учетную запись SSDS будут получены имя пользователя и пароль. Отсюда разработчик может начать создание своих центров, контейнеров и сущностей. Для Contoso Classifieds я установил центр contosoclassifieds, а конечной точкой идентификатора URI службы в бета-выпуске SSDS является data.data.beta.mssds.com.
В центр contosoclassifieds входят два контейнера: Categories («Категории») и Cities («Города»). Categories будет содержать сущности, относящиеся к категориям перечня. Cities будет содержать сущность для каждого города, определенного в системе. Эти сущности будут просто указывать на центры конкретных городов. У каждого центра конкретного города будет иметься контейнер на каждую категорию перечня (см. рис. 3).
Рис. 3. Элементы Contoso Classifieds (щелкните изображение, чтобы увеличить его)
Я воспользовался такой схемой по нескольким причинам. Я решил применить один центр на город, чтобы обеспечить предоставление центра в центре данных, являющемся географически близким к пользователям, которых он будет обслуживать. Я выбрал один контейнер на категорию перечня, чтобы обеспечить простой шаблон запроса и возможность распределить нагрузку между несколькими компьютерами в кластере (помните, что контейнер масштабируется под конкретный узел в серверном кластере). На Рис. 4 показан интерфейс пользователя для клиента администрирования Contoso Classifieds.
Рис. 4 Административный клиент приложения тематических объявлений (щелкните изображение, чтобы увеличить его)
Как упоминалось ранее, создание любых центров, контейнеров и сущностей, которые будут использоваться приложением, является задачей разработчика. В примере приложения вся эта работа содержится внутри события нажатия для кнопки выполнения первоначальной установки. (Полный код, включая обработку ошибок, входит в исходный код, прилагаемый к данной статье.)
Предоставить центр SSDS очень просто. В этом примере кода я используют SitkaSoapServiceClient, для которого я заранее добавил ссылку на службу к интерфейсу SOAP SSDS:
using (SSDSClient.SitkaSoapServiceClient ssdsProxy = 
  new SSDSClient.SitkaSoapServiceClient())
Центр мы уже увидели. Что же такое область действия SSDS? Объект области действия используется внутри SSDS для предоставления способа адресации объектов в службе SOAP, подобного способу, которым идентификаторы URI используются в службе REST.
В первую очередь для службы следует установить имя пользователя и пароль:
ssdsProxy.ClientCredentials.UserName.UserName = 
  txtUserName.Text;
ssdsProxy.ClientCredentials.UserName.Password = 
  txtPassword.Text;
Необходимо создать центр, являющийся высшим уровнем модели ACE, так что сперва создается пустая область действия:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
Далее создается центр Contoso Classifieds для хранения общих сведений о настройке в системе
SSDSClient.Authority contosoAuth = 
  new SSDSClient.Authority();
contosoAuth.Id = "contosoclassifieds";
и передачи созданных объектов в SSDS:
ssdsProxy.Create(serviceScope, contosoAuth);
Теперь, когда основной центр Contoso Classifieds создан, используйте его в качестве области действия и создайте контейнер, содержащий города, которым будут представляться тематические объявления, после чего передайте созданный контейнер в SSDS:
serviceScope.AuthorityId = contosoAuth.Id;

SSDSClient.Container citiesContainer = new SSDSClient.Container();
citiesContainer.Id = "Cities";

ssdsProxy.Create(serviceScope, citiesContainer);
Процесс создания контейнера подобен процессу создания центра. Единственное различие состоит в том, что вместо объекта центра создается объект контейнера, а вместо пустой области – обновленная область, указывающая на центр, в котором будет создан контейнер.
Создайте контейнер, который будет содержать все категории заголовка и перечня, которые будут поддерживаться внутри приложения:
SSDSClient.Container categoriesContainer = 
  new SSDSClient.Container();
categoriesContainer.Id = "Categories";

ssdsProxy.Create(serviceScope, categoriesContainer);
После создания центра его можно открыть в веб-обозревателе, используя либо HTTP, либо HTTPS, и просмотреть содержимое контейнера при помощи интерфейса REST. Просто используйте новое доменное имя, созданное службами SSDS при установке центра. В этом случае URL-адрес будет таков:
http://contosoclassifieds.data.data.beta.mssds.com/v1
После ввода этого URL-адреса в обозреватель будет выдан запрос на проверку подлинности. После успешной проверки подлинности в обозревателе можно будет увидеть нечто подобное:
<s:Authority xmlns:s="http://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Id>contosoclassifieds</s:Id>
  <s:Version>11</s:Id>
</s:Authority>
Имя центра приведено в нижнем регистре, поскольку раз уж службы SSDS создают запись DNS для центра, то необходимо следовать правилам именования DNS.
Если после создания контейнера обновить обозреватель и добавить пустой запрос к URL-адресу в форме ?q="", можно будет увидеть единственный объект контейнера, возвращаемый в EntitySet. EntitySet – это просто коллекция сущностей, которые возвращаются в качестве ответа на запрос. По завершении первоначальной установки пример приложения создаст один центр (contosoclassifieds) и два контейнера (Categories («Категории») и Cities («Города»)):
<s:EntitySet xmlns:s="http://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Container>
    <s:Id>Categories</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
  <s:Container>
    <s:Id>Cities</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
</s:EntitySet>
Теперь, когда первоначальная установка закончена, можно пойти дальше и добавить функции обслуживания городов.

Добавление города
Как упоминалось выше, Contoso Classifieds помещает перечни каждого города в свой собственный центр, что позволяет выбрать центр данных, на котором он размещен. (Хотя эта функция не поддерживается в бета-версии SSDS на данный момент, она будет поддерживаться к моменту выхода продукта.)
Код для добавления города подобен коду для первоначальной установки. Используя прокси SOAP, установите учетные данные, создайте пустую область, установите идентификатор центра и вызовите метод создания:
SSDSClient.Authority cityAuth = 
  new SSDSClient.Authority();
cityAuth.Id = cityAuthorityName;

ssdsProxy.Create(serviceScope, cityAuth);
Теперь, когда новый центр для города создан, добавьте сущность указателя к контейнеру городов в основном центре contosoclassifieds:
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Cities";

//Create the City Entity, and set its properties appropriately
SSDSClient.Entity cityEntity = new SSDSClient.Entity();
cityEntity.Id = cityAuthorityName;
cityEntity.Kind = "CityServed";
cityEntity.Properties = new Dictionary<string, object>();
cityEntity.Properties["AuthorityUri"] = 
  string.Format("{0}.data.data.beta.mssds.com/v1/", cityAuthorityName);
cityEntity.Properties["Name"] = txtCity.Text;

//Issue the create to SSDS
ssdsProxy.Create(serviceScope, cityEntity);
Обобщим сделанное. Я создал основной центр contosoclassifieds, содержащий все данные, затрагивающие систему в целом. В этом центре имеется контейнер городов, содержащий сущность для каждого города, который будет обслуживаться приложением. Эта сущность содержит отображаемое имя города вместе с указателем на центр, содержащий все перечни.

Добавление категорий
Вплоть до этого момента мы просто добавляли центры, контейнеры и сущности. Следующим этапом является реализация компонента перечня категорий. Для этого придется использовать функции запроса, обновления и удаления служб SSDS.
Contoso Classifieds будет поддерживать заголовок категории с подкатегориями ниже для размещения собственно сообщений от пользователей. При использовании реляционной модели для этих данных обычно будет использоваться шаблон заголовок/линия. Но гибкая модель данных сущностей, используемая SSDS, позволяет данным принимать любую желаемую форму. В случае приложения Contoso Classifieds я решил использовать по сущности для представления каждого из заголовков категорий и всех принадлежащих к ней подкатегорий (см. рис. 5).
Рис. 5 Модель данных сущности, используемая приложением (щелкните изображение, чтобы увеличить его)
Обратите внимание, что я использую этот шаблон просто для демонстрации гибкой природы сущности и иллюстрации того обстоятельства, что хотя несколько сущностей могут и принадлежать к одному виду, но это не заставляет их быть однородными. Сущность может принимать любую желаемую форму.
Лучшим подходом к этому случаю будет создание двух различных видов сущности – категории и категории перечня, а также придания гибкого свойства CategoryID всем сущностям. Это позволит выдавать такие запросы, как:
from e in entities where e["CategoryID"] ==­    "For Sale" select e
Отметьте, что я говорю о гибком свойстве. Как упоминалось выше, сущности в контейнере должны иметь уникальные идентификаторы, что не дает использовать идентификатор различенного свойства системы. Также обратите внимание на синтаксис выданного мною запроса. В SSDS использует синтаксис запросов, подобный LINQ, который должен выглядеть знакомо для большинства разработчиков, использующих .NET Framework.
Если взглянуть на административное приложение, показанное на рис. 4, то можно заметить представление дерева, представляющее категории перечней и два текстовых окна для добавления заголовков категорий и категорий перечней. Рассмотрим реализацию добавления заголовка категории подробнее.
Как и в случае с предыдущим кодом, здесь используется SitkaSOAPServiceClient, устанавливаются учетные данные и устанавливается область действия – на центр contosoclassifieds и контейнер категорий. Остается только создать сущность:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();

serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";

SSDSClient.Entity categoryHeaderEntity = new SSDSClient.Entity();
categoryHeaderEntity.Id = CategoryID;
categoryHeaderEntity.Kind = "Category";
Можно также добавить одно гибкое свойство, которое будет содержать имя категории. Отметьте, что в результате к коллекции гибких свойств будет добавлено лишь одно свойство:
categoryHeaderEntity.Properties = 
  new Dictionary<string, object>();
categoryHeaderEntity.Properties["CategoryName"] = CategoryName;
Следующим этапом является добавление категорий перечней. Поскольку в Contoso Classifieds используется одна сущность для представления всей коллекции категорий перечней внутри заголовка категории, необходимо извлечь сущность категории, добавить дополнительные категории перечней (которые будут добавлены как дополнительные гибкие свойства) и передать обновление службам SSDS.
Для получения сущности категории установите областью действия непосредственно ее и вызовите метод Get («Получить»), который напрямую извлечет сущность, не требуя отправки запроса.
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = CategoryID;
//Retrieve the Category Entity
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
Теперь просто определите индекс нового свойства, добавьте его как дополнительное гибкое свойство и вызовите метод обновления (см. рис. 6).
//Determine whether this is the first listing
//category being added to this header
if (categoryEntity.Properties.Count > 2) {
  propCount = ((categoryEntity.Properties.Count - 1) / 2);
  propCount++;
}
else {
  propCount = 1;
}

//Create the FlexProperties for the new listing category
string listingIdPropName = string.Format  ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format  ("ListingCategoryName{0}", propCount);

categoryEntity.Properties[listingIdPropName] = ListingCategoryID;
categoryEntity.Properties[listingNamePropName] = ListingCategoryName;

//Issue the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);

Обновление и удаление сущностей
Для обновления категорий перечней не требует ничего сложнее, чем добавление еще одного гибкого свойства. Для каждого обновления требуется получить сущность, внести любые обновления локально и отправить изменение, передавая обновленную сущность вместе с областью действия методу Update («Обновление»).
Начните с установки основного центра contosoclassifieds в качестве области действия службы. Кроме того, он должен указывать на контейнер категорий и сущность для обновляемой категории:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = currentHdrNode.Name;
Получите сущность, которую нужно обновить:
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
Теперь можно переинициализировать коллекцию гибких свойств, заново добавляя все гибкие свойства для категорий перечней. Как вариант, можно перебрать все гибкие свойства и внести обновления, но в силу простоты структуры этой сущности ее проще создать заново, как показано на рис. 7.
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = currentHdrNode.Text;

int propCount = 1;

if (e.Node.Parent != null) {
  //Loop through each node and add its category ID and
  //category name flex property.
  foreach (TreeNode node in e.Node.Parent.Nodes) {
    string listingIdPropName = string.Format      ("ListingCategoryID{0}", propCount);
    string listingNamePropName = string.Format      ("ListingCategoryName{0}", propCount);

    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    //if we are re-adding the updated category, the tree view 
    //will still have its old value, so set the flex property
    //to the updated value
    if (node.Name == e.Node.Name) {
      categoryEntity.Properties[listingIdPropName] =         m_OldSelectNode.Name;
      categoryEntity.Properties[listingNamePropName] = e.Label;
    }

    propCount++;
  }
}

//Submit the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
Для удаления сущности или контейнера ограничьте ServiceScope нужным элементом и вызовите метод удаления, принадлежащий SitkaSoapServiceClient. Если элемент – заголовок, удалите сущность целиком:
if (m_OldSelectNode.Parent == null) {
  ssdsProxy.Delete(serviceScope);
}
Для других сущностей, как в случае с обновлением, воссоздайте коллекцию гибких свойств и постройте все заново, выполняя добавление для всех узлов, кроме удаленного:
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = CategoryName;

int propCount = 1;

foreach (TreeNode node in entityNode.Nodes) {
  string listingIdPropName = string.Format    ("ListingCategoryID{0}", propCount);
  string listingNamePropName = string.Format    ("ListingCategoryName{0}", propCount);

  if (node.Text != m_OldSelectNode.Text) {
    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    propCount++;
  }
}
Наконец, можно отправить обновление в SSDS и удалить узел из представления дерева:
ssdsProxy.Update(serviceScope, categoryEntity);

tvCategories.Nodes.Remove(m_OldSelectNode);

Удаление и добавление схем перечня
Текущая бета-версия SSDS не поддерживает схемы. Планы добавления поддержки схем в будущем существуют, но и самостоятельное добавление схем внутри SSDS является простой задачей. Единственная загвоздка состоит в необходимости управлять схемой изнутри приложения, поскольку SSDS пока не обеспечивает ее применения.
Для Contoso Classifieds казалось хорошей идеей возложить на администратора определение специальной схемы для определенных категорий перечней. Схему можно определить внутри административного клиента и использовать ее позднее при реализации веб-узла.
Вплоть до этого момента разнородные сущности не содержались в одном контейнере. Поскольку каждая сущность имеет свойство вида, это обстоятельство можно использовать для хранения различных типов сущностей в одном контейнере, а также для запросов сущностей определенного типа, или, в данном случае, вида. Хотя с видом не связано контрактов схемы, этот механизм легко использовать для группировки похожих данных. Теперь я добавлю новую сущность вида ListingSchema к контейнеру категорий.
Диалоговое окно для добавления схемы перечня является довольно простым, как показано на рис. 8. Оно позволяет администратору определять дополнительные поля, связанные с перечнем, и опознавать определенные поля по мере необходимости. Я расскажу об этом подробнее чуть ниже, когда буду показывать реализацию собственно веб-узла.
Рис. 8 Настройка схемы для перечня (щелкните изображение, чтобы увеличить его)
Процесс, используемый для добавления сущности ListingSchema, идентичен уже показанному, но на это раз в нем используется другая сущность (вид). Полный код для добавления ListingSchema показан на рис. 9. После добавления схемы перечня для категории For Sale Cars («Продам автомобиль») обратите внимание, что в содержимое контейнера категорий входят два разных типа сущностей с совершенно различными данными в одном контейнере.
listingSchemaEntity.Kind = "ListingSchema";
listingSchemaEntity.Properties = new Dictionary<string, object>();
listingSchemaEntity.Properties["ListingID"] = m_ListingID;
listingSchemaEntity.Properties["ListingName"] = m_ListingName;

int propCount = 0;
bool required = false;

foreach (DataGridViewRow row in dataGridView1.Rows) {
  if (row.IsNewRow) continue;
  propCount++;

  listingSchemaEntity.Properties[string.Format    ("Property{0}Name",propCount)] = 
    row.Cells["colFieldName"].Value.ToString();
  listingSchemaEntity.Properties[string.Format    ("Property{0}DataType", propCount)] = 
    row.Cells["colDataType"].Value.ToString();

  if (row.Cells["colRequired"].Value == null) {
    required = false;
  }
  else {
    required = Convert.ToBoolean(row.Cells      ["colRequired"].Value.ToString());
  }

  listingSchemaEntity.Properties[string.Format    ("Property{0}Required", propCount)] = required;
}

if (propCount > 0) {
  using (SSDSClient.SitkaSoapServiceClient ssdsProxy =     new SSDSClient.SitkaSoapServiceClient()) {
    //Set username and password for the service
    ...

    //Set service scope to the main contoso classifieds
    //authority. Point it to the categories container
    ...

    //If this entity doesn't exist, create it
    if (listingSchemaID == "" || listingSchemaID == null) {
      ssdsProxy.Create(serviceScope, listingSchemaEntity);
    }
    //Otherwise update it
    else {
      serviceScope.EntityId = listingSchemaID;
      ssdsProxy.Update(serviceScope, listingSchemaEntity);
    }
  }
  MessageBox.Show("Custom Schema Saved", "Custom Schema Saved",
    MessageBoxButtons.OK, MessageBoxIcon.Information);
  this.Hide();
}
Последней частью администрирования клиента является удаление специальной схемы перечня при удалении категории перечня. Ранее я говорил об удалении сущностей и контейнеров, но тогда мне были известны идентификаторы сущностей или контейнеров, которые я хотел удалить. Удаление схемы означает сперва выдачу запроса LINQ на извлечение всех схем перечней, прикрепленных к категориям перечней, и удаление их. Запрос выглядит следующим образом:
string.Format(@"from e in entities 
  where e.Id == ""{0}"" select e", CategoryID);
Этот запрос возвратит список сущностей ListingSchema, и всё, что нужно сделать на этом этапе, – удалить их. Полный метод DeleteListingSchema показан на рис. 10.
//Retrieve the custom schema entity for this listing
string linqQuery =   string.Format(
  @"from e in entities where e.Id == ""{0}"" select e", CategoryID);

List<SSDSClient.Entity> entities =
  ssdsProxy.Query(serviceScope, linqQuery);

foreach (SSDSClient.Entity entity in entities) {

  //If more than 1 flex property on the entity, the
  //header has listing categories attached to it.
  if (entity.Properties.Count > 1) {
    //Calculate the number of Listing Categories
    int propCount = ((entity.Properties.Count - 1) / 2);
    for (int x = 1; x < propCount + 1; x++) {
      //Delete the listing schema for each listing
      //in this category
      DeleteListingSchema(entity.Properties["ListingCategoryID" + 
        x.ToString()].ToString());
    }
  }
}
На этом этапе клиент администрирования Contoso Classifieds завершен, и я могу перейти к реализации веб-узла с помощью интерфейса REST для служб SSDS.

Веб-приложение для тематических объявлений
Как упоминалось выше, доступ к службам SSDS можно получить через ее интерфейс REST, а также интерфейс SOAP (как я сделал для клиента администрирования Contoso Classifieds). Концепции и компоненты идентичны в обеих интерфейсах, но при использовании REST производятся прямые вызовы HTTP (или HTTPS).
Для начала взгляните вновь на основной интерфейс веб-приложения, показанного на рис. 1. В нем имеется древовидное представление, представляющее категории перечней, область основного содержимого и список Datalist всех городов, на поддержку которых настроено приложение. Рассматривая все это, я покажу, насколько просто разрабатывать приложения ASP.NET с использованием служб SSDS.
Взглянув на код ASPX для древовидного представления, можно увидеть, что он довольно прост:
<h3>Groups</h3>
<asp:TreeView ID="TreeView1" runat="server" 
  onselectednodechanged="TreeView1_SelectedNodeChanged">
</asp:TreeView>
Фокус заключается во вспомогательном коде, показанном на рис. 11. В первую очередь стоит подумать о создании идентификатора URI, являющегося указателем на контейнер категорий. Поскольку данные относятся не к отдельным городам, я решил сохранить их в основном центре contosoclassifieds, внутри контейнера категорий.
TreeView1.Nodes.Clear();

appDataUri = string.Format(@"http://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
query = @"from e in entities where e.Kind == ""Category"" select e";

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = 
  HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//Category");

int nodeIndex = 0;

foreach (XmlNode node in nodeList) {
  if (node.ChildNodes.Count > 3) {
    int propCount = ((node.ChildNodes.Count - 1) / 2);

    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText, 
      node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);

    for(int x=1;x<propCount;x++) {
      tn = new TreeNode(node.ChildNodes[(x*2)+2].InnerText, 
        node.ChildNodes[(x*2)+1].InnerText);
      TreeView1.Nodes[nodeIndex].ChildNodes.Add(tn);
    }      
  }
  else {
    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText,       node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);
  }

  nodeIndex++;
}
Вслед за этим я использую запрос LINQ для получения всех сущностей внутри категории вида, а затем использую объект UriBuilder для сведения всего вместе и добавления необходимого отделения HTTP. Затем я двигаюсь дальше и вызывают метод GetHTTPWebRequest, передавая отделенный идентификатор URI и объект NetworkCredential с именем пользователя и паролем SSDS. GetHTTPWebRequest возвращает строковое представление EntitySet со всеми сущностями, удовлетворяющими предикатам запроса.
GetHTTPWebRequest – статический вспомогательный метод для сокрытия некоторых из сведений о HTTP:
WebRequest request = 
  HttpWebRequest.Create(Uri.EscapeUriString(serviceUri));
request.Credentials = requestCredential;
request.Method = "GET";
request.ContentType = XmlContentType;

// Get the response and read it in to a string.
using (HttpWebResponse response = 
  (HttpWebResponse)request.GetResponse()) {

  return ReadResponse(response);
}
Теперь остается лишь создать новый объект WebRequest, установить переданные ему учетные данные, установить соответствующий глагол HTTP, установить ContentType на XML и вызвать метод GetResponse. Затем я вызываю ReadResponse – еще один статический вспомогательный метод, использующий модуль чтения потока для прочтения HTTPResponse и возвращения его вызывающему. Затем вспомогательный код берет возвращенный код XML, загружает его в XmlDocument и загружает древовидное представление с ним.

Десериализация класса
Пока что код лишь управлял кодом XML напрямую, но сущность легко можно взять и десериализовать в класс . В данном случае приложение будет использовать класс CityServed.
Базовый класс Entity («Сущность») довольно прост. Класс содержит свойства для идентификатора и версии, общих для всех сущностей. Он также содержит необходимые атрибуты для обеспечения своей сериализуемости.
Класс CityServed, показанный на рис. 12, порожден от базового класса Entity. Общий метод Query («Запрос») возвращает список сущностей типа CityServed:
foreach (CityServed i in HTTPHelper.Query<CityServed>(
  appDataUri, query, new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword)))
[XmlRoot(ElementName = "CityServed", Namespace = "")]

public class CityServed : Entity {
  [XmlElement(ElementName = "AuthorityUri")]
  public object AuthorityUriField;

  [XmlElement(ElementName = "Name")]
  public object NameField;

  public override string ToString() {
    return Name;
  }

  [XmlIgnore]
  public string AuthorityUri {
    get { return (string)AuthorityUriField; }
    set { AuthorityUriField = value; }
  }

  [XmlIgnore]
  public string Name {
    get { return (string)NameField; }
    set { NameField = value; }
  }
}
Метод Query, включенный в код, прилагаемый к данной статье, является просто общим статическим вспомогательным методом, вызывающим тот же метод GetHTTPWebRequest, что мы использовали раньше. Методы Serialize (сериализация) и Deserialize (десериализация) показаны на рис. 13. Метод Query в сочетании с методами Serialize и Deserialize дает возможность использовать строго типизированные объекты .NET и сохранять их в SSDS.
private static T Deserialize<T>(Stream stm, string xmlPayload) {
  XmlSerializer ser = new XmlSerializer(typeof(T));

  T flex = (T)ser.Deserialize(stm);

  XmlDocument xDom = new XmlDocument();
  xDom.LoadXml(xmlPayload);

  return flex;
}

public static T Deserialize<T>(String xmlPayload) {
  using (MemoryStream stm = new MemoryStream()) {
    Encoding encoding = new UTF8Encoding(false);
    stm.Write(encoding.GetBytes(xmlPayload), 0, encoding.GetByteCount(xmlPayload));
    stm.Position = 0;
    return Deserialize<T>(stm, xmlPayload);
  }
}

public static string Serialize<T>(T flex) {
  using (MemoryStream stm = new MemoryStream()) {
    Serialize(stm, flex);

    stm.Position = 0;

    using (StreamReader reader = new StreamReader(stm)) {
      return reader.ReadToEnd();
    }
  }
}
private static void Serialize<T>(Stream stm, T flex) {
  XmlSerializer ser = new XmlSerializer(typeof(T));
  Encoding encoding = new UTF8Encoding(false);
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.CloseOutput = false;
  settings.ConformanceLevel = ConformanceLevel.Document;
  settings.Encoding = encoding;
  settings.Indent = true;
  settings.OmitXmlDeclaration = true;

  using (XmlWriter writer = XmlWriter.Create(stm, settings)) {
    ser.Serialize(writer, flex);
  }
}

Использование индивидуализированной схемы перечня.
Как уже упоминалось, сущности SSDS гибки, а это значит, что каждой сущности можно придать желаемую форму. Например, клиент администрирования Contoso Classifieds дал возможность добавить схему к категории перечня. Можно также получить индивидуализированную схему из SSDS и динамически создать форму ввода, которую можно использовать для добавления перечней к системе.
Первым этапом является создание таблицы DataTable с четырьмя столбцами: Field («Поле»), Value («Значение»), DataType («Тип данных») и Required («Требуется»). Эта таблица будет содержать поля, определенные администратором для перечня. Далее я выдам запрос, чтобы увидеть, была ли определена для перечня специальная схема:
string appDataUri = string.Format(@"http://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
string query = string.Format(
  @"from e in entities where e[""ListingID""] == ""{0}"" select e", 
  listingCategoryID);

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//ListingSchema");
Затем я могу загрузить возвращенный код XML в DataTable и привязать его к DataList.
В интерфейсе REST необходимо создать представление XML для сущности и направить HTTP POST контейнеру, в который следует вставить сущность. Создать сущность несложно – можно просто создать шаблонную сущность и перебрать все поля, добавляя ее как узел в документ XML:
foreach (DataListItem fieldItem in dlAddFields.Items) {
  Label lblField = (Label)fieldItem.FindControl("lblAddField");
  TextBox tbItem = (TextBox)fieldItem.FindControl("txtAddValue");
  Label lblFieldType = (Label)fieldItem.FindControl("lblAddFieldType");
  entity = entity + String.Format(
    @" <{0} xsi:type='x:{1}'>{2}</{0}>", lblField.Text.Replace(" ", ""), 
    lblFieldType.Text, tbItem.Text);
}
Наконец, создайте идентификатор URI, указывающий на контейнер, которому нужно направить POST, и выдайте команду POST:
string serviceUri = string.Format(@"http://{0}.{1}{2}", 
  postingAuthority, conSSDSUri, postingContainer);

HTTPHelper.PostHTTPWebRequest(serviceUri, entity, 
  new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword));

Заглядывая вперед
Вести разработку в SSDS просто. Я хотел бы повторить, что службы SSDS построены на основе проверенных технологий SQL Server и Windows Server®. Хотя группа разработчиков решила представить лишь ограниченный набор компонентов в первоначальной бета-версии SSDS, в будущем их станет гораздо больше. Поднабор функциональности, доступный в настоящее время, поможет во многих ситуациях, с которыми сталкиваются клиенты.
К тому же можно ожидать добавления поддержки SSDS к другим продуктам в пакете SQL Server. Так что независимо от того, использует ли разработчик C#, Visual Basic®, Java, Ruby или даже Microsoft Office Access®, службы SSDS, поддерживающие открытые протоколы, являются идеальным местом для хранения данных приложения. Чтобы получить дополнительные сведения о них, посетите веб-узел SSDS по адресу microsoft.com/sql/dataservices и подпишитесь на бета-версию. Если желательно увидеть в продукте какие-либо дополнительные компоненты, свяжитесь со мной по адресу david.robinson@microsoft.com.

Дэвид Робинсон (David Robinson) – старший руководитель программы в группе служб данных SQL Server корпорации Майкрософт. Он проводит свое время, внося новые и интересные функции в продукт. Он также с удовольствием устраивает презентации на конференциях для специалистов и работает с отзывами клиентов о SSDS.

Page view tracker