Data 2.0:
Web サービスの世界でデータを公開および使用する
Elisa Flasko および Mike Flasko
この記事は、ASP.NET 3.5 および Microsoft AJAX Library のプレリリース版に基づいて書かれています。ここに記載されているすべての情報は、変更される場合があります。
この記事では、次の内容について説明します。
- データ サービスの意味
- データを公開および使用する
- Entity Data Model でデータを記述する
- データ セキュリティ
|
この記事では、次のテクノロジを使用しています。
ADO.NET Data Services、LINQ、Entity Data Model
|

目次
自分で構築した最後の Rich Internet Application (RIA) を思い起こしてください。データをどのように取得したでしょうか。ブラウザに送信されるプレゼンテーションおよび UI の情報とデータとを、どのように分けたでしょうか。もっと簡単な方法があったらどうでしょう。
プレゼンテーションとデータを分けるという考えは新しいものではありませんが、AJAX や Silverlight™ などの RIA テクノロジの人気が高まっていく中で、その考えが一般化してきました。これらのテクノロジは、プレゼンテーションとデータを分けることにより、さらにインタラクティブで応答性に優れたアプリケーションを実現するという考えに基づいて構築されています。
たとえば、Silverlight ベースの RIA アプリケーションは、プレゼンテーションを駆動するコードをプリコンパイルします。このコードは、Web ブラウザを通してクライアントに展開されます。Web ブラウザにアクセスしたコードは、Web サーバーを呼び出してデータを取得し、ユーザー インターフェイスに表示します。このようなテクノロジにより、通常は、データとプレゼンテーション コードを混在させるような、サーバー側のレンダリング プロセスというオプションがなくなります。
よりリッチでインタラクティブな Web エクスペリエンスを駆動するためにプレゼンテーションとデータを分けるということだけでなく、Web でユーザー インターフェイスに依存せずにスタンドアロン データを公開および使用するというトレンドがあります。"マッシュアップ" のようなデータ駆動型アプリケーションの急増は、すぐに利用できる興味深いデータの可用性が広がったことで、新しいアプリケーション シナリオが可能になっているという状況を示しています。
このようなトレンドを分析したうえで、ADO.NET Data Services Framework は、RIA アプリケーションからサービスを経由してデータを公開および使用することを目指す開発者が利用できる手法として開始されました。この新しい世界を探索すると、2 つの主要な点が浮かび上がってきます。データ中心のサービスへの現行のアプローチで汎用的なクライアント ライブラリおよびツールを構築するというのは、その概念自体が非常に困難であること、そして、そういったサービスの作成および保守にはかなりの開発者の投資が必要であるということです。この記事では、データ サービスの概要に焦点を絞り、主な機能のいくつかについて説明します。データ サービスの背景となる考え方について、詳しい解説を参照する場合は、「Why ADO.NET Data Services? (なぜ ADO.NET Data Services なのか)」(
go.microsoft.com/fwlink/?LinkId=120530) というタイトルのブログ記事をご覧ください。
一般的に、ADO.NET Data Services Framework の目的は、データ中心のサービスを公開および使用するために、Representational State Transfer (REST) に基づくシンプルなフレームワークを作成することです。そのようなサービスは、企業イントラネットやインターネットを経由し、一貫性のあるインターフェイスを通して、Web クライアントで使用されるデータを公開します。このフレームワークを構成しているのは、データをサービスとして安全に公開するためのサーバー ライブラリと、Microsoft® .NET Framework や Silverlight などマイクロソフトの特定のアプリケーションおよびテクノロジでサービスを使用できるように構築されたクライアント ライブラリのセットです。図 1 に、そのアーキテクチャを示します。
図 1 ADO.NET Data Services Framework のアーキテクチャ (クリックすると拡大画像が表示されます)
Entity Data Model でデータを記述する
ADO.NET Data Services は、概念スキーマ定義言語 (CSDL: Conceptual Schema Definition Language) を使用して Entity Data Model (EDM) で記述されます。EDM では、エンティティとアソシエーション (リレーションシップ) という 2 つの主要な概念を使用します。エンティティは、エンティティ型 (Customer や Employee など) のインスタンスであり、キーを含む構造化されたレコードです。エンティティ キーは、エンティティ型のプロパティのサブセットから作成されます。このキー (CustomerId と OrderId のそれぞれ) により、エンティティ インスタンスへの更新を一意に識別し、その更新を許可し、また、インスタンスをリレーションシップに含めることを許可します。エンティティは、EntitySets によりグループ化されます (Customers は Customer インスタンスのセットです)。アソシエーションは、2 つ以上のエンティティ型の間のリンクを形成します (Employee WorksFor Department など)。
EDM が ADO.NET Data Services のデータ記述言語として選択されたのは、なぜでしょう。EDM は Web のリソースおよびリンクの主要な概念に非常にうまく対応し、理想的な候補を提供します (リソースに対してエンティティ、リンクに対してアソシエーション)。
また、EDM は、一連の開発者およびサーバー向けテクノロジに共通のコア データ モデルとなることを目標として開発されました。多数のアプリケーションに唯一のコア データ モデルを使用することで、アプリケーションの保守が単純化されます。EDM を使用すると、ADO.NET Data Services で構築されたカスタム アプリケーションに対してだけでなく、レポートおよび視覚化アプリケーション、イントラネット ポータル アプリケーション、またはワークフロー アプリケーションへの入力としても、モデルを定義できるようになります。ADO.NET Data Services は、EDM の概念に基づいて構築された 2 番目のマイクロソフト テクノロジです (もちろん、1 番目は ADO.NET Entity Framework です)。EDM および ADO.NET Entity Framework の詳細については、msdn.microsoft.com/data および MSDN
® Magazine 2008 年 7 月号の「ADO.NET: Entity Framework で柔軟なデータ モデリングを実現する」(
msdn.microsoft.com/magazine/700331) を参照してください。
リレーショナル データ
ADO.NET Data Services は、既定により、ADO.NET Entity Framework と緊密に連携して、SQL Server® または他のサードパーティ データベース (Oracle、DB2、MySQL など) に格納されたデータのデータ モデルに対する接続やその公開のプロセスを簡略化します。
Entity Framework では、開発者がデータを操作する抽象化レベルを引き上げられます。つまり、リレーショナル データの行や列に対するコーディングではなく、より高いレベルの概念的なモデルが (Entity Data Model として) 定義された後で、このモデルを基づいたアプリケーションのプログラミングを行うことになります。アプリケーションで認識する必要のあるデータは、アプリケーションに対して意味のある形式のデータのみです。また、その形式は、継承、複合型、明示的なリレーションシップなどの概念を網羅する豊富な語彙を使用して表現されます。
一般に、ADO.NET Data Services は、リソースへの操作を実行するための要求 (たとえば、URI に対する HTTP 要求操作) が、リソースが表現するデータ モデルの同等の操作に変換されることにより動作します。ここでは、データ モデルはリレーショナル データベースに基づいているため、URI は Entity Framework の Object Services メソッド呼び出しに変換されます。
Entity Framework を使用して作成された既存のデータ モデルは、Visual Studio® の ASP.NET Web アプリケーション プロジェクトでいくつかの手順を実行するだけで公開できます (Windows® Communication Foundation、WCF、WebServiceHost など、他の種類のプロジェクトおよびホスティング メカニズムもサポートされています)。まず、ADO.NET Entity Data Model を ASP.NET Web アプリケーションに作成またはインポートします。モデルが利用可能な状態になると、新しい ADO.NET Data Services を追加できます。新しい項目の追加ウィザードで、基本サービスを生成し、ソリューション エクスプローラで表示できるようにします。次に示すように、クラス定義を変更することにより、サービスをモデルにフックできます。
using NorthwindModel;
public class NorthwindService : DataService <NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
}
}
また、既定によりサービスがロックされているため、サービスを使用するにはサービス全体のセキュリティ ポリシーを設定する必要があります。次のコードでは、サービスの初期化時に EntitySet レベルでセキュリティを設定します。
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
ご覧のとおり、これは ADO.NET Data Services に対するセキュリティ設定の単純な例です他のセキュリティの手法および考慮事項については後で説明します。
この時点で、ビルドと実行に使える作業用 ADO.NET Data Services が準備できました。
クライアント アプリケーションなしでサービスをテストする簡単な方法があります。サービスの各エントリ ポイント (<host>/<vdir>/service.svc など) で Web ブラウザを参照するだけです。サービスのエントリ ポイントからは、データ サービスが公開する EntitySets のリストを含む XML として応答が返されます。Atom (ADO.NET Data Services により返される既定の形式) を Internet Explorer® で表示するには、Internet Explorer でフィードの読み取りビューがオフになっていることを最初に確認する必要があります。これは、[ツール] メニューの [インターネット オプション] の [コンテンツ] タブで設定できます。
非リレーショナル データ
他のすべてのデータ ソースの場合、または LINQ to SQL など追加のデータベース アクセス テクノロジを使用する場合は、プラグイン モデルを使用してあらゆるデータ ソースを ADO.NET Data Services として公開できるようにするメカニズムがあります。
このケースでは、URI は LINQ クエリに変換されます。ADO.NET Data Services では、IQueryable インターフェイスを実装するオブジェクトを EntitySets にマッピングすることで、この手法を実現できます。したがって、ADO.NET Data Services は、専用に記述された IQueryable プロバイダを持つあらゆるデータ ソースを公開できます。そのために、ADO.NET Data Services では、CLR オブジェクトと EDM ベースのデータ モデルのアーティファクトとのマッピングを定義します。
後ほど、簡単な CLR オブジェクト グラフに基づいた ADO.NET Data Services の作成について順番に説明します。この手順は、オブジェクトのメモリ内コレクションに対して作成されるデータ サービスの生成方法を示しています。ただし、一般的な実稼動環境への展開では、EntitySets を表す IQueryable プロパティによってメモリ内データを公開する代わりに、IQueryable 式ツリーをデータ ソース固有のクエリに変換します。たとえば、LINQ to Entities により、IQueryable 式ツリーを SQL ステートメントに変換します。
非リレーショナル ストアに対するデータ サービスを作成する場合も、データ サービスを ASP.NET Web アプリケーション プロジェクト内で作成します。ただし、このケースでは、ADO.NET Entity Data Model を作成するための手順をスキップし、新しい ADO.NET Data Services を追加する手順から始めます。新しい項目の追加ウィザードにより、ソリューション エクスプローラで表示可能な基本サービスを生成します。この簡単な例を先に進めるために、ここで、サービスを通じて公開する対象となるメモリ内データを作成する必要があります。そのために、3 つのクラスを作成します。2 つのクラスは User と Contact で、各 User が Contact のセットを持ちます。3 つ目は MyDataService クラスです。図 2 に示すように、2 つのパブリック プロパティ (Contact と User) を IQueryable<Contact> および IQueryable<User> として公開します。

図 2 メモリ内 IQueryable データ ソースを作成する
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Contact> Contacts { get; set; }
}
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class MyDataService
{
static User[] _users;
static Contact[] _contacts;
static MyDataService()
{
_users = new User[]{ new User{ID=1, Name="Mike"},
new User{ID=2, Name="Elisa"} };
_contacts = new Contact[]{ new Contact{ID=1, Name="Joe",
Email="Joe@contoso.com"},
new Contact{ID=2, Name="Bob", Email="Bob@contoso.com"},
new Contact{ID=3, Name="Sam", Email="Sam@contoso.com"},
new Contact{ID=4, Name="Carl", Email="Carl@contoso.com"},
new Contact{ID=5, Name="Abby", Email="Abby@contoso.com"},
new Contact{ID=6, Name="Annie", Email="Annie@contoso.com"},
};
_users[0].Contacts = new List<Contact>();
_users[0].Contacts.Add(_contacts[0]);
_users[0].Contacts.Add(_contacts[1]);
_users[0].Contacts.Add(_contacts[4]);
_users[1].Contacts = new List<Contact>();
_users[1].Contacts.Add(_contacts[2]);
_users[1].Contacts.Add(_contacts[3]);
_users[1].Contacts.Add(_contacts[5]);
}
public IQueryable<User> Users
{
get { return _users.AsQueryable<User>();}
}
public IQueryable<Contact> Contacts
{
get { return _contacts.AsQueryable<Contact>(); }
}
}
データ ソースを決めたところで、慣れ親しんだ領域に戻りましょう。次に示すように、クラス定義を変更してこのデータ ソースを指すようにします。
public class contacts :
DataService<ADONETDataServiceNonRelSample.MyDataService>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
}
ここでも、初期サービスは既定でロックされているため、セキュリティ ポリシーを設定するための最初の作業が必要です。次のコードは、前の例と同じように、EntitySet レベルで設定されるセキュリティ ポリシー セットを示しています。この時点で、ビルドと実行に使える作業用 ADO.NET Data Services が準備できました。Internet Explorer を使用してサービス エンドポイントまで移動するだけで、サービスをテストできます。
一貫性のある URI 形式
一貫性のある URI (Uniform Resource Identifier) は、ADO.NET Data Services を構成する主要な概念の 1 つです。比較的単純でなおかつ表現力のある URI 形式を定義することで、アプリケーションにおいてエンティティのセットまたは単一のエンティティでクエリを実行したり、これらのエンティティ間に存在するリレーションシップを移動したりできます。この URI の構造により、エージェントおよびコントロールは、サービスにより提供されるデータ内を移動する方法を簡単に認識できます。
最初のサービスのテストを開始したときに、データ サービス自体を指す基本的な URI 形式を使用しました。この基本的な URI に基づいて、次に示すような形式を使用してサービスのクエリを実行できます。
http://<host>/<vdir>/<service.svc>/<EntitySet>[(<Key>)
[/<NavigationProperty>[(<Key>)/...]]]
たとえば、最初の Northwind の例を見てみましょう。データ サービス URI の末尾に /Customers を追加して <host>/<vdir>/NorthwindService.svc/Customers とすることにより、Customers Entity Set のすべての顧客 (この場合は Northwind データベースのすべての Customers) を取得できます。また、Customer はシングルキー エンティティであるため、Northwind サービスから 1 つの顧客エンティティを取得するには、エンティティのキー値を使用できます。URI <host>/<vdir>/NorthwindService.svc/Customers('ALFKI') を要求することにより、"ALFKI." キーを持つ 1 つの Customer が返されます。同様の方法で、ナビゲーション プロパティを 1 つのエンティティ クエリ URI に追加します。/Customers('ALFKI')/Orders を追加することにより、Customers と Orders とのモデル内のリレーションシップをたどり、"ALFKI." キーで識別される Customer に関連するすべての Orders を取得することができます。
上記の URI 形式を使用すると、ストア内のエンティティに対する基本的なクエリやトラバーサルを実行できますが、クエリの出力を制御する場合や出力に制約を設定する場合については考慮されていません。このような目的を達成するには、$filter、$expand、$orderby、$skip、$top など、ADO.NET Data Services でサポートされるオプションのクエリ文字列パラメータのセットを使用します。URI の後ろに、? 文字、クエリ文字列パラメータの順に追加します。
たとえば、London に居住するすべての顧客のクエリを実行するには、サービス URI に /Customers?$filter=City eq 'London' を追加します。('ALFKI' キーで) 特定の顧客にクエリを実行し、関連する販売注文を取得するには、サービス URI に /Customers('ALFKI')?$expand=Orders を追加します。結果を City ごとに昇順 (asc) で並べ替えるには、サービス URI に /Customers?$orderby=City asc を追加します (降順の場合は desc)。
ADO.NET Data Services でサポートされる URI 形式の完全な一覧については、Data Services のドキュメント (
go.microsoft.com/fwlink/?LinkId=120539 からアクセス可能) を参照してください。
データ サービス セキュリティ
Web ベース テクノロジ、特に、重要なデータにアクセスして操作するための技術を論じる場合は、常にセキュリティを第一に考慮する必要があります。したがって、セキュリティは、データ サービスの開発サイクルを通して主要な考慮事項となってきました。
認証を提供するために、ADO.NET Data Services では既存の ASP.NET および WCF 認証インフラストラクチャの多くを活用して、アプリケーション間で、およびサービスを通して統合認証環境を実現します。認証 (組み込みの認証プロバイダのいずれか、または HTTP コンテキスト プリンシパルを適宜設定するカスタム プロバイダ) を使用している ASP.NET サイトがあるとします。ADO.NET Data Services では、現在の ID (プリンシパル) を確立するために選択されているメカニズムをあらゆる要求に対して利用できます。同様に、ASP.NET の外部で (WCF、またはカスタム ホスト経由で) ホストされるデータ サービスの場合は、データ サービス作成者が要求のプリンシパルにアクセスするための API がホストで提供される限り、そのホストは任意の認証メカニズムの使用を選択できます。
既定では、新しい ADO.NET Data Services はすべて完全にロックされており、すべてのエンティティ、サービス操作、およびメタデータに対して、読み取りまたは書き込みアクセス権なしでアクセスできません。このため、データ サービスに対して認証ポリシーを制御し、要求ごとに検証を実行するためのさまざまなメカニズムがあります。
データ サービス開発者が行う最初の段階の手順の 1 つは、データ サービスにより公開されたリソースへのアクセスを開くことです。今回の例では、サービス全体の読み取り/書き込みアクセス ポリシーを設定する簡単なケースについて見てきました。これは、各サービスに対して InitializeService メソッドを使用して行われます。設定されたのは EntitySet ポリシーです。これにより、モデル内の Entity Sets へのアクセスを許可または制限し、どの Entity Sets と関連するアソシエーションをサービスのメタデータ エンドポイントから利用可能にするかを設定します。これらの例で示したポリシーは両方とも、読み取りおよび書き込みのためにモデル全体へのアクセスを開くものでしたが、図 3 に示すように、さらに具体的に各 EntitySets に個別のポリシーを設定し、必要に応じて複合ポリシーを含めることもできます。

図 3 サービス全体のアクセス ポリシーを設定する
public class MyService : DataService<NorthwindEntities>
{
public static void InitializeService(
IDataServiceConfiguration config)
{
// '/Customers' entities are enabled for all read and write
// operations
config.SetEntitySetAccessRule("Customers",
EntitySetRights.All);
// URI '/Orders' is disabled, but '/Orders(1)' is enabled for read
// only
config.SetEntitySetAccessRule("Orders",
EntitySetRights.ReadSingle);
// Can insert and update, but not delete Products
config.SetEntitySetAccessRule("Products",
EntitySetRights.WriteInsert |
EntitySetRights.WriteUpdate);
}
}
多くのシナリオにおいて、データ サービスでは、エンティティがデータ サービスに入るとき (挿入、更新、または削除のため) に検証ロジックを実行し、要求ごとにエンティティへのアクセスを制限することも必要です。このようなシナリオでインターセプタを活用することにより、データ サービス開発者は、データ サービスのパイプラインを処理する要求/応答に対して、カスタム検証またはアクセス ポリシー ロジックを関連付けることができるようになります。たとえば、それぞれの顧客が他の顧客の注文は取得せずに自分の注文のみを取得できるようにするには、図 4 に示すように、クエリ インターセプタを実装する必要があります。クエリ インターセプタはクエリに追加する述語を返します。この述語はデータ ストアにプッシュされるため、アクセス制御情報を取得するためにデータ ストアにさらにアクセスする必要はなくなります。

図 4 クエリ インターセプタ メソッド
public class nw : DataService<NorthwindModel.NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
}
[QueryInterceptor("Orders")]
public Expression<Func<Orders,bool>> OnQueryOrders()
{
return o => o.Customer.ContactName ==
HttpContext.Current.User.Identity.Name
}
}
同様に、インターセプタは、挿入、更新、および削除のために変更インターセプタとして追加することもできます。変更インターセプタは戻り値を持ちませんが、2 つの引数を使用します。EntitySet に含まれる型のオブジェクトと、リソースで要求されるアクション (更新、挿入、または削除) を定義する UpdateOperations 列挙です。インターセプタでは、渡されるオブジェクトを変更するか、またはまったく別のインスタンスへの参照を設定することができます。メソッドが例外をスルーした場合、操作は中止され、データベースの変更は行われず、クライアント エージェントにエラーが返されます。
クライアント側 : サービスにアクセスする
一般的に、HTTP をデータ サービスの API として扱ってきたため、HTTP レベルで直接使用するのが簡単だということを知るまでに時間がかかりました。つまり、サービス ペイロードの解釈が簡単であるというだけでなく、HTTP スタックを含むプラットフォームはすべてデータ サービスを簡単に使用できるということです。また、マイクロソフトのプラットフォームを使用してデータ サービスにアクセスしている場合、クライアント側の開発は ADO.NET Data Services クライアント ライブラリを使用してさらに簡単に行うことができます。これにより、.NET Framework、Silverlight、および ASP.NET AJAX を使用して記述するアプリケーション (データ サービスをターゲットとし、オブジェクトという形式で結果を操作し、アソシエーション トラバーサルなどの操作に対応する) に対して、より自然なプログラミング モデルが提供されます。クライアント ライブラリは、HTTP 要求および応答の詳細を抽象化し、各クライアント スタックでのエクスペリエンスの一貫性を確実に向上させます。
.NET Framework および Silverlight クライアント ライブラリは、DataServiceContext クラスと DataServiceQuery クラスという 2 つの主要な型で構成されています。DataServiceContext は、指定のデータ サービスの実行時コンテキストを表します。データ サービス自体はステートレスですが、開発者が対話するコンテキストはステートレスではありません。ID 解決やオプティミスティックな同時実行などの機能をサポートするために、クライアントの状態が対話と対話の間で維持されます。DataServiceQuery オブジェクトは、URI 構文を使用して定義されたストアに対する特定のクエリを表します。クエリを実行して結果を .NET オブジェクトの形式で取得するには、クエリ オブジェクトを列挙するだけです。たとえば、標準の "foreach" (C# の場合) 構成要素または "For Each" (Visual Basic®) を使用して行います。
データ サービスで定義されたエンティティを .NET オブジェクトとしてクライアントで使用するには、クライアント アプリケーション用に対応するクラスを定義する必要があります。これには、3 つの主要な方法があります。その 1 つは、手動で定義することです。図 5 には、手動で記述された単純な Region クラスの定義と、Region に対してクエリを実行して ID および説明を出力するコードの一部が示されています。

図 5 .NET アプリケーションから ADO.NET データ サービスへのアクセス
namespace TestApplication
{
public class Region
{
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
class Program
{
static void Main(string[] args)
{
DataServiceContext ctx = new
DataServiceContext("http://localhost:25115/NorthwindService.svc");
DataServiceQuery<Region> regions =
ctx.CreateQuery<Region>("/Region?$orderby=RegionID");
foreach (Region r in regions)
{
Console.WriteLine(r.RegionID + ", " + r.RegionDescription);
}
}
}
}
より一般的なアプローチは、Visual Studio で生成されたコードを使用することです。クラスを手動で記述することは、サービスで保持する型の数が少ないときには有効ですが、データ サービス スキーマがより複雑になると、手動で作成して維持する必要のあるクラスの数が大幅に増加し、サイズも大きくなります。
必要なクラスを生成する最も一般的な方法は、Visual Studio でサービス参照の追加ウィザードを使用することです。WCF サービスで使用する [サービス参照の追加] と同じように、プロジェクトを右クリックして、[サービス参照の追加] をクリックするだけです (図 6 を参照)。
図 6 サービス参照の追加 (クリックすると拡大画像が表示されます)
[サービス参照の追加] ダイアログで、[アドレス] にサービスのエントリ ポイントの URL を入力します。この例では、「<host>/<vdir>/NorthwindService.svc」と入力します。これにより、データ サービス定義に基づいたクラスが生成され、プロジェクトに追加されます。
もう 1 つの方法は、"datasvcutil.exe" コマンド ライン ツールを使用してデータ サービス定義に基づいたクラスを生成することです。このツールは、ADO.NET Data Services のリリースに付属しており、\Windows\Microsoft.Net\Framework\V3.5 ディレクトリに格納されています。このツールでは、データ サービスのエントリ ポイントの URI、および生成される出力ファイルの名前と場所の URI を引数として使用します。
"\Windows\Microsoft.Net\Framework\V3.5\datasvcutil.exe"
/out:C:\NorthwindSample\northwind.cs /uri:"http://<host>/<vdir>/
NorthwindService.svc"
コマンド ライン ツールからの既定の出力は、データ サービスで記述された各エンティティ型のクラスを含む C# ファイルです (Visual Basic の型は /language:VB switch を使用して生成できます)。生成されたクラスはそれぞれ、サービスで記述されたプリミティブ値およびアソシエーションを表すメンバを持ちます。これにより、開発者は、オブジェクト モデルを直接使用して関連付けられたエンティティのグラフ内を移動できます。
.NET クライアント ライブラリ
ADO.NET Data Services の .NET クライアント ライブラリは、.NET Framework およびデータ サービスを使用してアプリケーションを記述する開発者が慣れ親しんだプログラミング モデルを提供します。実際には、クライアント ライブラリは HTTP および AtomPub 形式を使用しているため、企業のネットワークとインターネット環境の両方で自然に動作します。必要とされるのは、データ サービスに対する簡単な HTTP レベルの直接または間接の (たとえば、プロキシ経由の) 接続だけです。
クライアント ライブラリは、Windows フォーム、Windows Presentation Foundation (WPF)、Web プロジェクトなど、あらゆる種類のプロジェクトで使用できます。上記のクライアント ライブラリを使用するには、System.Data.Services.Client.dll アセンブリへの参照を追加する必要があります。
Silverlight クライアント ライブラリ
Silverlight クライアントは、ADO.NET Data Services の一部としてではなく、Silverlight 2 SDK の一部として提供されます。Silverlight クライアントにより、Silverlight アプリケーション開発をより完全に統合された環境で行うことができます。Silverlight クライアント ライブラリと .NET および AJAX クライアント ライブラリの違いの 1 つは、Silverlight 2 で同期開発がサポートされない点です。したがって、Silverlight クライアント ライブラリによる開発では、一般的な begin/end async パターンに従って非同期 API を利用する必要があります。また、他の 2 つのクライアント ライブラリで使用する API のいくつかは、Silverlight でまだ有効になっていないということも意味しています。
AJAX クライアント ライブラリ
ASP.NET AJAX ライブラリは現在、
codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=13357 から利用可能です。先に説明した .NET クライアント ライブラリのように、AJAX クライアント ライブラリでは HTTP の詳細が抽象化されるため、アプリケーション開発者は、HTTP 要求および応答を手動で解析および作成する代わりに JavaScript オブジェクトで直接作業できます。CodePlex サイトでは、AJAX アプリケーションでデータ サービス ライブラリを使用する方法を示しているさまざまな Web ページも利用可能です (
codeplex.com を参照)。
データ サービスにクエリを実行する
図 5 では、DataServiceQuery.CreateQuery メソッドを呼び出して URI クエリに渡すことにより、サービスに対するクエリが構築されています。別の方法としては、ライブラリを使用することで、LINQ によってデータ サービス クエリを作成することもできます。クライアント ライブラリでは、LINQ ステートメントをターゲット データ サービスの URI にマッピングし、指定のリソースを .NET オブジェクトとして取得します。次のコードは、London のすべての顧客を取得して結果を会社名ごとに表示する方法を示しています。
var q = from c in ctx.Customers
where c.City == "London"
orderby c.CompanyName
select c;
foreach (var cust in q)
{
Console.WriteLine(cust.CompanyName);
}
LINQ to ADO.NET Data Services を使用すると、LINQ 構文で表現可能なクエリのセットは、Data Services の REST ベースの URI 構文で可能なものよりも広範です。クエリをターゲット データ サービスの有効な URI にマッピングできない場合は、例外がスローされます。
クエリの種類が URI へのマッピングが可能であるかどうかを知る 1 つの方法は、階層トラバーサルについて考えることです。2 つ以上のピボットが必要なクエリ (たとえば、Any のような句を使用する結合やサブクエリ) は、現在、URI にマッピングできません。一方、1 つのピボットおよびアソシエーション トラバーサルを含むクエリは、通常、適切にマッピングできます。
ここまで、単純なエンティティに対するクエリを実行してきました。オブジェクトのアソシエーションも DataServiceContext により追跡および管理されます。関連付けられたオブジェクトは、「一貫性のある URI 形式」セクションで説明した URL 形式を使用して即時に読み込みむか、または必要に応じて読み込むことができます。関連付けられたエンティティを必要に応じて読み込む場合 (遅延読み込み) では、図 7 に示すように、DataServiceContext クラスで LoadProperty メソッドを使用します。

図 7 関連するエンティティの遅延読み込みを LoadProperty で実行する
// get a single category
DataServiceQuery<Categories> categories =
context.CreateQuery<Categories>("/Categories(1)");
foreach (Categories c in categories)
{
Console.WriteLine(c.CategoryName);
context.LoadProperty(c, "Products");
foreach (Products p in c.Products)
{
Console.WriteLine("\t" + p.ProductName);
}
}
シナリオによっては、通信の追加のラウンドトリップでエンティティをフェッチするのではなく、元のクエリでエンティティを読み込む必要があります。その場合は、URI に expand オプションを指定します。クライアント ライブラリは、最上位エンティティおよび関連付けられたエンティティが両方とも結果に含まれていることを認識し、すべてのエンティティを 1 つのグラフとして具体化します。図 8 では、データ サービスへの 1 つのラウンドトリップで、関連する製品を即時に読み込みます。

図 8 関連するエンティティの即時読み込みを expand で実行する
public IList<Products> GetProducts(string productName,
Categories category)
{
int categoryId = category.CategoryID;
string uri = serviceUri + "/Categories(" + categoryId + ")/Products?";
if (!String.IsNullOrEmpty(productName))
{
uri = uri + "$filter=ProductName eq '" + productName + "'&";
}
uri = uri + "$expand=Categories";
Uri queryUri = new Uri(uri);
try
{
IEnumerable<Products> products =
context.Execute<Products>(queryUri);
return products.ToList();
}
catch (Exception)
{
return null;
}
}
コマンド マッピング
ADO.NET Data Services のクエリは HTTP の Get 要求にマッピングされますが、Create、Update、Delete の各要求はどのように実行されるでしょうか。4 つの CRUD 操作 (Create、Retrieve、Update、Delete) はそれぞれ、別の HTTP 動詞にマッピングされます。Retrieve は GET、Create は POST、Update は PUT、および Delete は DELETE です。クエリと同様、使用するクライアント ライブラリ (この場合は .NET クライアント ライブラリ) により詳細が抽象化され、適切な HTTP 動詞が使用されるため、開発者は提供された API を非常に簡単に使用できます。
データ サービスでエンティティのインスタンスを作成するには、次に示すように、.NET オブジェクトを作成して目的のデータを設定した後で、DataServiceContext オブジェクトで AddObject を呼び出して、新しいオブジェクトとそのオブジェクトの追加先となる EntitySet の名前を渡すだけです。
public void AddProduct(Products product)
{
context.AddObject("Products", product);
context.AttachTo("Categories", product.Categories);
context.SetLink(product, "Categories", product.Categories);
DataServiceResponse r = context.SaveChanges();
}
また、.NET オブジェクト間のアソシエーションを追加または変更して、その変更をアソシエーションの作成/削除の操作としてクライアント ライブラリに反映させることもできます。たとえば、前のコードでは、Northwind データベースの Product を作成し、既存の Category に関連付けました。カテゴリおよび製品は、1 対多のアソシエーションに含まれるため、1 つの製品が 1 つの固有のカテゴリを持つことになります。
エンティティが作成された後で、データ サービスは、データベース内のトリガや自動生成されたキーによって更新された値を含む最新コピーを返します。次に、クライアント ライブラリは自動的に新しい値で .NET オブジェクトを更新します。
既存のエンティティ インスタンスを変更するには、最初にクライアントによってオブジェクトをフェッチして、DataServiceContext で追跡する必要があります。開発者は最初にオブジェクトのクエリを実行するか、またはオブジェクトをコンテキストにアタッチして、目的の変更をプロパティに行ってから、UpdateObject メソッドを呼び出します。UpdateObject メソッドは、そのオブジェクトの更新を送信するようにクライアント ライブラリに指示します。
public void UpdateProduct(Products product)
{
Categories newCategory = product.Categories;
context.AttachTo("Products", product);
context.AttachTo("Categories", newCategory);
context.UpdateObject(product);
context.SetLink(product, "Categories", newCategory);
context.SaveChanges();
}
エンティティ インスタンスを削除する場合も、クライアントによってオブジェクトを保持し、DataServiceContext で追跡する必要があります。追跡した後で、コンテキスト インスタンスで単純な DeleteObject の呼び出しを行い、オブジェクトに削除のマークを付けます。
public string DeleteProduct(Products product)
{
context.AttachTo("Products", product);
context.DeleteObject(product);
context.SaveChanges();
}
上述のすべての例において、コンテキストの最後のメソッド呼び出しは SaveChanges メソッドになっています。変更は DataServiceContext インスタンスで追跡されますが、サーバーにすぐには送られません。指定のアクティビティに対して必要な変更がすべて完了した後で、SaveChanges の呼び出しによりデータ サービスへの変更が送信されます。
これまでの例では、それぞれが各クライアント操作に対する 1 つの HTTP およびサーバー要求になっています。このアプローチ (HTTP 要求ごとの単一の操作) は一部のシナリオでは正常に機能しますが、多くの場面においては、複数の操作をまとめて単一の HTTP 要求でデータ サービスに送るという方法が適しています。これにより、データ サービスへのラウンドトリップの数が減り、操作のコレクションに対するアトミック性の論理スコープが有効になります。これらの要件をサポートするために、クライアントでは、CUD (Create、Update、Delete) 操作のグループおよび Query 操作のグループを単一の HTTP 要求でデータ サービスに送信する機能を備える必要があります。次のコードは、2 つ以上のクエリを 1 つのバッチとしてデータ サービスに送る方法を示しています。
var q = (DataServiceRequest)from o in context.SalesOrder
select o;
// send two queries in one batch request to the data service
DataServiceResponse r = service.ExecuteBatch(
new DataServiceRequest<Customer>(new
Uri("http://localhost:25115/NorthwindService.svc/Customers")),
q);
CUD 操作のグループを 1 つのバッチとしてデータ サービスに送るには、単純な SaveChanges メソッドの呼び出しを使用し、SaveChangesOptions.Batch を唯一のパラメータとして渡します。このパラメータ値は、すべての保留中の変更操作を 1 つのバッチにまとめて、アトミック グループとしてデータ サービスに送るように、クライアントに指示します。SaveChangesOptions.Batch を使用して変更操作を 1 つのバッチとして送った場合、すべての変更が正常に適用されるか、または変更がすべて適用されないかのどちらかです。
詳細
この記事では、ADO.NET Data Services を使い始めるには十分な情報を提供していますが、さらにいくつかのトピックについて詳しい解説をご覧になる場合は、Data Services チームのブログ (
blogs.msdn.com/astoriateam) および MSDN デベロッパー センター (
msdn.microsoft.com/data) で ADO.NET Data Services のトピックを参照してください。
Elisa Flasko は、マイクロソフトのデータ プログラマビリティ チームのプログラム マネージャであり、ADO.NET テクノロジ、XML テクノロジ、SQL Server 接続テクノロジなどを担当しています。
blogs.msdn.com/elisaj で彼女のブログをご覧いただけます。