次の方法で共有


データ ポイント

ADO.NET Data Services で Silverlight 2 を使用する

John Papa

コードは MSDN コード ギャラリーからダウンロードできます。
オンラインでのコードの参照

目次

ドメイン間通信
機密データを賢く処理する
EDM で手軽に始める
Silverlight から ADO.NET Data Services を参照する
LINQ を使用してデータを取得する
遅延読み込み
データを保存する
前もってデータを取得する
次回に続く

ADO.NET Data Services を使用すると、非常に簡単にデータを公開し、RESTful (Representational State Transfer) 機能を使用して HTTP 経由で更新できるようになります。Silverlight アプリケーションは、ADO.NET Data Services を利用することで、エンティティにマッピングされる一意の URI を使用して、データを送受信できます。また、Silverlight クライアントで LINQ クエリを使用すると、ADO.NET Data Services Silverlight クライアント ライブラリを使用してサーバー上のエンティティと対話できます。

ADO.NET Data Services と Silverlight は強力な組み合わせになりますが、うまく連携させるには、いくつかの点をよく理解する必要があります。それらの事項は、一見してわかるようなものではないかもしれません。ここでは、ADO.NET Data Services と Silverlight でのアプリケーション構築の成功をいっそう確実なものにするために実施できるいくつかの手順について説明します。

ドメイン間通信

現在、ADO.NET Data Services はドメイン間通信をサポートしていません。ドメイン間通信は、標準の REST サービスと SOAP サービスではサポートされていますが、ADO.NET Data Services ではサポートされていません (参考までに、データ サービス チームは研究を進めており、進展は the Astoria team blog (Astoria チームのブログ) でお知らせします)。つまり、ADO.NET Data Services によって公開されるサービスをホストしているドメインと、Silverlight 2 クライアント アプリケーションをホストしているドメインが異なる場合、Silverlight 2 クライアントはそのサービスと交信できません。ドメイン間ポリシーの詳細については、筆者の 2008 年 9 月号の「データ ポイント」コラムを参照してください。このコラムでは、ファイル形式とポリシーの機能について説明しています。

機密データを賢く処理する

HTTP 通信を使用していると、URI を使って送信されるすべての値が、さまざまなネットワーク スニッフィング ツールから丸見えになります。これに対抗する方法の 1 つは、SSL を使用してすべての HTTP 通信を暗号化することです。また、社会保障番号などの機密データやその他の個人データは、URI では送信しないようにします。機密データを識別子として使用しないようにするのがよい方法です。RESTful 通信を選択する前に、GUID、数字、IDENTITY 値などの意味のない識別子を使用していることを確認してください。たとえば、次のサンプル URI は、従業員 ID の 11 を使用して従業員データを取得します。

http://[YourDomainHere]/MyService.svc/Employee(11)

番号 11 は URI の中で丸見えになっています。これが、機密情報を使用しないことが重要である理由です。さいわい、この例の番号 11 は作られた識別子を表すので、個人データは暴露されません。

EDM で手軽に始める

おそらく、ADO.NET Data Services を使い始めるときの最も簡単な方法は、ADO.NET Entity Framework を使用して論理エンティティ モデルとしてリレーショナル データベースからデータを公開するというものです。Entity Framework のエンティティ データ モデル (EDM) は、エンティティへの読み取りアクセスおよび更新アクセスを可能にする方法を完全に認識しています。EDM のこの組み込み機能により、EDM はほんのわずかな設定だけで ADO.NET Data Services と連携できます。

Entity Framework で作成された EDM を公開する最初の手順は、Visual Studio のテンプレート ダイアログから ADO.NET Data Services サービスを作成することです。これにより作成されるテンプレートは、Entity Framework から EDM を公開するように簡単に変更できます。クラス コンストラクタは DataService<T> 基本クラスから継承します。T はデータ ソース クラスを表し、この場合は Entities という名前の Entity Framework データ ソース クラスです。データ ソース クラスは Entity Framework ではオブジェクト コンテキスト クラスとも呼ばれ、これを使用すると EDM にアクセスしてデータを取得したり保存したりできます。データ ソース クラスは Entity Framework で自動的に作成されるので、EDM を公開する ADO.NET Data Service の作成は非常に簡単です。図 1 のコードは、DataService<Entities> から継承するサービス クラス NWDataService を示しています。

図 1 DataService の作成

public class NWDataService : DataService<Entities>
//public class NWDataService: DataService< /* TODO: put your data source 
//class name here */ >
 {
    // This method is called only once to initialize service-wide 
    //policies.
    public static void InitializeService(IDataServiceConfiguration 
        config)
    {
        //set rules to indicate which entity sets and service operations 
        //are visible, updatable, etc.
        config.SetEntitySetAccessRule("ProductSet", EntitySetRights.All);
        config.SetEntitySetAccessRule("CategorySet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("SupplierSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderDetailSet", 
            EntitySetRights.All);
        config.SetEntitySetAccessRule("CustomerSet", 
            EntitySetRights.AllRead);
        /// The rest of the entity sets are not accessible.
        /// Therefore, no proxy classes are created for them either.
    }
}

次の手順は、図 1 で示されているように、EDM 内の各エンティティ セットへの読み取りおよび書き込みアクセスを、許可または拒否することです。これは、* を使用してすべてのエンティティ セットで、または各エンティティ セット名を指定して個別のエンティティ セットのレベルで、行うことができます。SetEntitySetAccessRule メソッドは、ルールを適用する対象のエンティティ セットの名前と、1 つ以上の EntitySetRights 列挙値を受け取ります。図 2 で、ProductSet、OrderSet、OrderDetailSet、CustomerSet、SupplierSet、CategorySet の各エンティティ セットがすべてのアクセスを許可していることに注意してください。つまり、これら 6 つのエンティティ セットは、読み取り、挿入、更新、および削除を許可します。これらはサービス全体のアクセス設定であり、システムが受信するすべての要求に適用されます。一覧に示されていない EmployeeSet や RegionSet などのエンティティ セットにはアクセスできません。

図 2 System.Data.Services EntitySetRights 列挙
列挙型 説明
All 指定したエンティティに対するすべての読み取りと書き込みを許可します。
AllRead すべての読み取りを許可します。
AllWrite すべての書き込みを許可します。
0 指定したエンティティに対するアクセスを許可しません。
ReadMultiple 複数行の読み取りを許可します。
ReadSingle 単一行の読み取りを許可します。
WriteAppend 新しいデータの作成を許可します。
WriteDelete データの削除を許可します。
WriteMerge 結合ベースの更新を許可します。
WriteReplace 置換を許可します。

EntitySetRights 列挙値は、エンティティ セットに対して許可されるアクセスの種類を示します。図 2 は、すべての有効な列挙値とその説明です。アクセス許可は、すべてのエンティティ セットに対してグローバルに設定することもできます。たとえば、次のコードはすべてのエンティティ セットのすべての読み取りアクセスを許可しますが、書き込みアクセスは許可しません。

config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

権限を組み合わせて、1 つのエンティティ セットに複数の権限を許可することもできます。次のコードは、ProductSet エンティティ セットに対する読み取りと更新のアクセスのみを許可します。

config.SetEntitySetAccessRule("ProductSet", 
EntitySetRights.AllRead | 
EntitySetRights.WriteMerge | 
EntitySetRights.WriteReplace);

この手順に従うと、6 つのエンティティ セット (図 1 で示されているもの) が公開され、ADO.NET Data Service を通して使用できるようになります。他のエンティティ セットとアクセス許可もカスタマイズできます。

LINQ to SQL や NHibernate などの他のオブジェクト リレーショナル マッピング (ORM) で作成されたエンティティ モデルも、ADO.NET Data Services で使用できます。コンテキスト オブジェクトは、クエリ対象の各エンティティ セットに対して IQueryable を実装する必要があります。作成、更新、削除の各操作については、コンテキスト オブジェクトは IUpdatable インターフェイスを実装する必要があります。ADO.NET Data Services に組み込まれている ADO.NET Entity Framework のナレッジにより、ADO.NET Data Services を介したクエリと更新がサポートされるようになります。他の ORM の将来のバージョンでも、ADO.NET Data Services がサポートされるようになるか、または少なくとも簡単に使用開始できるヘルパ クラスが用意されるものと考えられます。ただし、現時点では、Entity Framework 以外の ORM を ADO.NET Data Services で使用する場合は、前に説明したインターフェイスを実装する必要があります。実際には、ADO.NET Data Services はリレーショナル データに固有のものではありません。ここで説明した技法を使用して、任意のデータ ソースを構成できます。

fig03.gif

図 3 サービス参照から生成されるクラス

Silverlight から ADO.NET Data Services を参照する

ADO.NET Data Service の作成と構築が済むと、Silverlight アプリケーションはサービスを参照し、サービスと対話できます。Silverlight アプリケーションから ADO.NET Data Service を参照するには、ソリューション エクスプローラでサービス参照ノードを右クリックし、[サービス参照の追加] ダイアログ ウィンドウを開きます。サービスの URI を [アドレス] ボックスに入力するか、または [探索] ボタンをクリックした後、[実行] ボタンをクリックしてサービスを探すことができます。サービスのメタデータが取得されると、サービスとそれによって公開されるエンティティ セットが一覧に表示されます。最後に、サービス参照の名前を指定して [OK] ボタンをクリックします。

これで、Silverlight クライアントにプロキシ クラスが作成され、そのクラスを使用して ADO.NET Data Service と対話できるようになります。ソリューション エクスプローラ ウィンドウで [すべてのファイルを表示] ボタンをクリックして、NWServiceReference ノードを完全に展開すると、Reference.cs ファイルが表示されます。このファイルには、ADO.NET Data Service との対話を可能にする生成されたプロキシ クラスと、サービスによって公開されたエンティティに対して生成されたクラスが含まれます。図 3 のように、[クラス ビュー] ウィンドウでもクラスの一覧を確認できます。

[クラス ビュー] には Web プロジェクトの Entity Framework のモデルに含まれるすべてのエンティティが表示されるわけではないことに注意してください。Silverlight クライアントから ADO.NET Data Service へのサービス参照を追加すると、データ サービスを介してアクセス可能なエンティティ セットについてのみ、プロキシ クラスが生成されます。[クラス ビュー] に表示される 6 つのエンティティ セットは、ADO.NET Data Service で SetEntitySetAccessRule メソッドを使用して公開されているので、Silverlight クライアントからはこれらのエンティティ セットのみを使用できます。[クラス ビュー] ウィンドウに表示される 7 番目のクラスである Entities クラスは、データ サービス全体を表し、NWDataService.svc の呼び出しを容易にするプロキシ クラスです。

LINQ を使用してデータを取得する

次の手順は、Silverlight プロジェクトで System.Data.Services.Client アセンブリを参照することです (図 4 を参照)。このアセンブリにより、LINQ を使用した ADO.NET Data Services との対話が簡単になります。たとえば、次のコードは、ADO.NET Data Services を使用してすべての製品を選択する LINQ クエリを作成します。

  _products.Clear();
  DataServiceQuery<Product> dq = (from p in _ctx.ProductSet select p)
  as
  DataServiceQuery<Product>;
  dq.BeginExecute(new AsyncCallback(FindProduct_Completed), dq);

fig04.gif

図 4 Silverlight からの ADO.NET Data Services クライアント ライブラリの参照

このコードの LINQ クエリは、ADO.NET Data Services で読み込むことのできる URI に変換されます。クエリは、System.Data.Services.Client 名前空間の一部である DataServiceQuery<T> にキャストされます。クエリは非同期に実行されるので、クエリから結果を受け取るには有効なコールバック メソッドを指定する必要があります。前に示したコード サンプルのクエリが実行されてデータが返されると、FindProduct_Completed メソッドが呼び出されます。

FindProduct_Completed メソッド (図 5 を参照) は、クエリからの結果を含む IAsyncResult パラメータを受け取ります。結果を読み取るには EndExecute メソッドを呼び出します。このメソッドは一連の Product オブジェクトを生成します。各製品が検査されて、PropertyChanged イベントにイベント ハンドラが割り当てられます (サンプル コードでは、部分クラスを使用して INotifyPropertyChanged の実装を追加することで、Product クラスを拡張しています)。この手順により、Silverlight で Product インスタンスを変更すると、Product は UpdateObject メソッドを呼び出して DataServiceContext (図 5 の entities) に通知するようになります。このコードがないと、DatServiceContext はユーザーが Product インスタンスに対して行った変更を認識できません。_products 変数が ObservableCollection<Product> 型で、DataGrid コントロールの DataContext にバインドされているとすると、製品は DataGrid コントロールに表示されます (図 6 を参照)。

図 5 FindProduct_Completed メソッド

private void FindProduct_Completed(IAsyncResult result)
{
    DataServiceQuery<Product> query = (DataServiceQuery<Product>)result.
         AsyncState;
    try
    {
        var entities = query.EndExecute(result);
        foreach (Product item in entities)
        {
            item.PropertyChanged += ((sender, e) =>
                                         {
                                             Product entity = (Product)
                                                                sender;
                                             _ctx.UpdateObject(entity);
                                         });
            _products.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Failed to retrieve data: " + ex.ToString());
    }
}

fig06.gif

図 6 製品と注文明細の読み込み

遅延読み込み

Silverlight の ADO.NET Data Services クライアント ライブラリでは、既に DataServiceContext 内にあるオブジェクトと関連付けられたオブジェクトを読み込むことができます。たとえば、図 6 で示されている Silverlight コントロールの上部の DataGrid で製品が選択されたとき、その製品の注文明細を下部の DataGrid にバインドするには、先に明細を取得する必要があります。このプロセスでの最初の手順は、イベント ハンドラを productDataGrid の SelectionChanged イベントに割り当てることです。製品が選択されると、イベント ハンドラ (図 7 を参照) は BeginLoadProperty メソッドを使用して、選択された製品の OrderDetails オブジェクトを取得するよう ADO.NET Data Services に要求します。

図 7 注文明細の要求

void productDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    _orderDetails.Clear();

    if (product.OrderDetails == null || product.OrderDetails.Count == 0)
    {
        _ctx.BeginLoadProperty(product, "OrderDetails", FindOrderDetail_Completed, null);
    }
    else
    {
        LoadOrderDetails();
    }
}

BeginLoadProperty メソッドは、ネットワーク要求を行って OrderDetails レコードを取得し、戻るときにコールバック メソッド FindOrderDetail_Completed を呼び出します。このコードからコールバック メソッドに他の状態を渡す必要がある場合は、BeginLoadProperty メソッドの 4 番目のパラメータで渡すことができます。たとえば、同じイベント ハンドラを使用して、複数の非同期クエリの結果を受け取ることができます。どの呼び出し元の非同期呼び出しクエリ結果を受け取ったのかをコールバック メソッドで特定できるように、state パラメータで値を渡すことができます。

コールバックが呼び出されたら、次に示すように、EndLoadProperty メソッドを使用して結果を DataServiceContext オブジェクトに読み込みます。

_ctx.EndLoadProperty(result);
Deployment.Current.Dispatcher.BeginInvoke(() => LoadOrderDetails());

注文明細が LoadOrderDetails メソッドで読み込まれます。このコードは非同期操作が完了した結果として実行されるので、このコードが UI スレッドで実行される保証はありません。

コードが UI スレッドで実行されない場合、注文明細の新しいセットは要素の DataGrid に表示されません。基本的に、すべての UI 操作は UI スレッドで実行する必要があります。操作を UI スレッドで確実に実行する 1 つの方法は、Dispatcher オブジェクトの BeginInvoke メソッドを使用することです。前に示したコードでは、Dispatcher を使用して、LoadOrderDetails を呼び出すことで注文明細が UI スレッドに確実に読み込まれるようにしています (図 8 を参照)。

図 8 注文明細の読み込み

private void LoadOrderDetails()
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    var query = (from od in product.OrderDetails
                 orderby od.OrderID ascending
                 select od);
    foreach (OrderDetail item in query)
    {
        item.PropertyChanged += ((sender, e) =>
        {
            OrderDetail entity = (OrderDetail)sender;
            _ctx.UpdateObject(entity);
        });
        _orderDetails.Add(item);
    }
}

LoadOrderDetails は、現在選択されている Product インスタンスを取得し、選択されている Product からすべての OrderDetails オブジェクトを選択する LINQ クエリを作成します。EndLoadProperty を使用して注文明細を DataServiceContext に読み込んでいるため、このようなクエリが可能です。図 8 の LINQ クエリを使用して注文明細を取得すると、各 OrderDetail オブジェクト インスタンスの PropertyChanged イベント ハンドラには、いずれかのプロパティ値が変化した場合に DataServiceContext に通知するラムダ式が割り当てられます。_orderDetails オブジェクトが ObservableCollection<OrderDetail> 要素であり、orderDetailsDataGrid にバインドされているとして、注文明細は図 6 のようになります。

データを保存する

示されているサンプルでは、ユーザーは製品と注文明細の両方に対する変更を保存できます。変更を保存するための基本的な手順は、変更が発生したときに DataServiceContext に通知することと、保存操作を非同期に実行することの 2 つです。

このような階層構造の各データ セットに対し、Silverlight には、Product エンティティと OrderDetail エンティティを拡張する部分クラスがあります。Product の部分クラス (図 9 を参照) は INotifyPropertyChanged インターフェイスを実装し、これには PropertyChanged イベントを実装する必要があります。ADO.NET Data Services へのサービス参照を作成すると生成される部分 Product クラスは、クラスの各パブリック プロパティに対する部分メソッドを作成します。各プロパティには、プロパティが変更される直前に呼び出されるメソッドと、プロパティが変更された後で呼び出されるメソッドが作成されます。たとえば、Product クラスの ProductName プロパティには、OnProductNameChanging 部分メソッドと OnProductNameChanged 部分メソッドがあります。図 9 では、生成されるクラスではなくカスタム コード内の OnProductNameChanged 部分メソッドが PropertyChanged イベントを発生させることが示されています。これは、すべてのエンティティのプロパティ値に対するすべての変更を追跡するために重要です。DataServiceContext は、プロパティの値が変更されたことと変更後の値を知る必要があります。そうしないと変更を保存できません。

図 9 変更通知の実装

public partial class Product :INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    partial void OnProductIDChanged() { FirePropertyChanged("ProductID"); }
    partial void OnProductNameChanged() { FirePropertyChanged("ProductName"); }
    partial void OnDiscontinuedChanged() { FirePropertyChanged("Discontinued"); }
    partial void OnDiscontinuedDateChanged() { FirePropertyChanged("DiscontinuedDate"); }
    partial void OnQuantityPerUnitChanged() { FirePropertyChanged("QuantityPerUnit"); }
    partial void OnReorderLevelChanged() { FirePropertyChanged("ReorderLevel"); }
    partial void OnRowVersionStampChanged() { FirePropertyChanged("RowVersionStamp"); }
    partial void OnUnitPriceChanged() { FirePropertyChanged("UnitPrice"); }
    partial void OnUnitsInStockChanged() { FirePropertyChanged("UnitsInStock"); }
    partial void OnUnitsOnOrderChanged() { FirePropertyChanged("UnitsOnOrder"); }
}

各プロパティのプロパティ変更イベント ハンドラを設定して、すべての変更が DataServiceContext に通知されるようにすると、データ保存の残りの作業は比較的簡単です。ユーザーが保存ボタンをクリックすると、次に示すように DataServiceContext で BeginSaveChanges メソッドが呼び出されます。

private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
    _ctx.BeginSaveChanges(SaveChangesOptions.Batch, 
        new AsyncCallback(Save_Complete), null);
}

このメソッドは ADO.NET Data Services を使用して POST 操作を行い、DataServiceContext が認識したすべての変更を送信します。POST は非同期に発行されるので、結果を処理するにはコールバック メソッドが必要です。SaveChangesOption.Batch パラメータは、すべての変更をバッチ モードのトランザクション内で保存する必要があることを示します。保存操作が 1 つでも失敗した場合は、すべての保存操作が失敗してトランザクションがロールバックされるようにします。この方法は、1 つのエンティティから単一のレコードを保存する場合でも、関連付けられた複数のエンティティから複数のレコードを保存する場合でも使用できます。

前もってデータを取得する

先ほど、DataServiceContext の BeginLoadProperty メソッドを使用して既存のエンティティのレコードを取得し、取得したレコードを DataServiceContext に関連付けて遅延読み込みを実行する方法を説明しました。この方法は、必要に応じて追加データを取得するのに非常に便利です。特に、データが常に必要ではない場合に有効です。遅延読み込みは、ユーザーがデータを要求している場合 (ユーザーが productDataGrid コントロールで Product を選択した場合) にのみデータを取得することで、ユーザーにとって不要なデータを取得するコストを節約します。

階層的なデータを取得するもう 1 つの方法は、すべてのデータを前もって要求することです。たとえば、Product レコードを取得するときに、各 Product の Category と Supplier も取得しておくと便利な場合があります。取得しないと、Product インスタンスの Category プロパティと Supplier プロパティは null になります。BeginLoadProperty の手法を使用することもできますが、多くのネットワーク要求を行う必要があります。すべてのデータを一度に取得するもっとよい方法は、LINQ クエリで Expand メソッドを使用することです。この方法では、必要な HTTP 要求は 1 回だけです。次のクエリでは、LINQ クエリで選択されている製品のすべてのカテゴリと仕入先を要求する Expand メソッドを示します。

   DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("Categories").Expand("Suppliers")
    select p) 
    as DataServiceQuery<Product>;

Expand メソッドを使用すると余分なデータも取得されます。このメソッドは、すべてのデータを前もって取得する必要がある場合にのみ使用するようにしてください。前に示したコードにある Expand メソッドは、各 Product エンティティの子レコードを取得します。つまり、Category と Supplier はどちらも Product の直接の子です。エンティティ セットの名前ではなくプロパティの名前を Expand メソッドに渡していることに注意してください。

複数レベルの階層が必要な場合は、そのようなレコードも取得するように Expand メソッドの構文を適応させることができます。たとえば、各 Product の OrderDetail 要素に加えてその OrderDetail の Orders も取得する場合は、次のような構文を使用します。

DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("OrderDetails/Orders")
    select p) 
    as DataServiceQuery<Product>;

このコードは、各 Product の OrderDetails プロパティのエンティティに加えて Orders の各エンティティをクエリで取得する必要があることを示しています (Product クラスに OrderDetails プロパティがあり、OrderDetail クラスに Orders プロパティがあります)。OrderDetail レコードは、各 Product の Orders プロパティを取得するときに必要なので暗黙的です。このクエリは、8 MB を超える XML データを Silverlight クライアント アプリケーションに返します。これは大量のデータであり、低速の接続ではパフォーマンスが低下する可能性があります。Expand メソッドはデータが必要な場合にのみ使用し、その場合でも、可能な限り制約の厳しいフィルタを適用して不要なデータを取得しないようにすることをお勧めします。

次回に続く

ADO.NET Data Services と Silverlight を組み合わせることで、堅牢なデータ駆動型アプリケーションに役立つ多くの機能を利用できるようになります。今後のデータ ポイントの記事では、再びこのトピックを取り上げて、さらにヒントを提供したいと思います。今のところは、私のブログ johnpapa.net、Silverlight.net Web サイト、およびこれまでのデータ ポイントの記事で、Silverlight のデータ中心アプリケーションの詳細をご覧ください。

ご質問やご意見は、John (mmdata@microsoft.com) まで英語でお送りください。

John Papa (johnpapa.net) は、ASPSOFT の上級コンサルタントです。野球ファンで、夏の夜を家族と共にヤンキースの応援に費やします。C# の MVP、Silverlight の事情通、そして INETA の講演者でもある John は、何冊かの書籍を発表しており、最新の著書は『Data-Driven Services with Silverlight 2』(O'Reilly、2009 年) です。また、主にカンファレンス (Mix、DevConnections、VSLive など) での講演で活躍しています。