WCF RIA サービス

以前から興味があったWCF RIA サービスのヘルプを読んだのでポイントを整理しました。アプリケーション基盤として、名前付き更新やコンポジット階層、プレゼンテーションモデルなど興味深い仕組みが幾つかあります。好みが分かれるのでしょうが個人的には好きですね。

ドメインサービス

ドメイン サービスは、WCF RIA サービス アプリケーションのビジネス ロジックをカプセル化した Windows Communication Foundation (WCF) サービス

基本クラス
  • DomainService クラスは、ドメイン サービスとして機能するすべての基本クラス
  • LinqToEntitiesDomainService: Entity Framework用
  • LinqToSqlDomainService :Linq to SQL
ドメインサービスの公開
  • ドメインサービスをEnableClientAccessAttribute 属性でマークする
  • ウイザードで [クライアント アクセスの有効化] チェック ボックスをオンすると付与される

ドメインサービスの処理パターン

ドメインサービスの処理にはパターンがありそのルールで実装する必要がある

CRUD操作
  • Query
  • Update
  • Insert
  • Delete
public void InsertCustomer(Customer customer)
{
    if (customer.SalesPerson == String.Empty)
    {
        customer.SalesPerson = RetrieveSalesPersonForCompany(customer.CompanyName);
    }

    if ((customer.EntityState != EntityState.Detached))
    {
        this.ObjectContext.ObjectStateManager.ChangeObjectState(customer, EntityState.Added);
    }
    else
    {
        this.ObjectContext.Customers.AddObject(customer);
    }
}
複雑な操作
public void ResetPassword(Customer customer)
{
    // Implement logic to reset password
}
//クライアントでは上記を以下のように呼び出す
selectedCustomer.ResetPassword();
customerContext.SubmitChanges(OnSubmitCompleted, null);

クライアントではエンティティのメソッドとしても公開される。MIX-INされる。

  • Invoke:エンティティを介さずに実行できるメソッド
[Invoke]
public int GetLocalTemperature(string postalcode)
{
    // Implement logic to look up temperature
}
//クライアントでは上記を以下のように呼び出す
InvokeOperation<int> invokeOp = customerContext.GetLocalTemperature(selectedPostalCode);

ドメインサービスのデータ

ドメインサービスで処理可能なデータはエンティティ(Key項目が必須)だけであるが、アグリゲート(コンポジットデータ)やプレゼンテーションモデルの概念も取り込んでいる。

コンポジットデータ(Compositional Hierarchies)

複数のエンティティを一つ塊にして処理できる仕組みが提供される。たとえばクライアントプロジェクトでは、子孫エンティティが変更される、変更の通知が親エンティティまで自動伝達される(HasChanges)

  • 子孫エンティティのプロパティにCompositionAttribute 属性を付与
  • 子孫エンティティを読み込むためにはIncludeAttribute 属性を適用し、クエリ メソッドに子孫エンティティを含める必要がある
[MetadataTypeAttribute(typeof(SalesOrderHeader.SalesOrderHeaderMetadata))]
public partial class SalesOrderHeader
{
    internal sealed class SalesOrderHeaderMetadata
    {
        private SalesOrderHeaderMetadata()
        {
        }
        [Include]
        [Composition]
        public EntitySet<SalesOrderDetail> SalesOrderDetails;

    }
}
プレゼンテーションモデル

データ アクセス層内の複数のエンティティからデータを合成するデータ モデル

  • 独自のプレゼンテーションモデル(普通のクラス)を作成してカスタムのドメインサービスを作成
  • プレゼンテーションモデルにも一意識別用のKey項目が必要
  • 更新処理を定義することも可能
//プレゼンテーションモデルの定義
public class CustomerPresentationModel
{
    [Key]
    public int CustomerID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
    public string Phone { get; set; }
    public string AddressType { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
}

//プレゼンテーションモデルの検索
public IQueryable<CustomerPresentationModel> GetCustomersWithMainOffice()
{
    return from c in context.Customers
        join ca in context.CustomerAddresses on c.CustomerID equals ca.CustomerID
        join a in context.Addresses on ca.AddressID equals a.AddressID
        where ca.AddressType == "Main Office"
           select new CustomerPresentationModel()
           {
               CustomerID = c.CustomerID,
               FirstName = c.FirstName,
               LastName = c.LastName,
               EmailAddress = c.EmailAddress,
               Phone = c.Phone,
               AddressType = ca.AddressType, 
               AddressLine1 = a.AddressLine1, 
               AddressLine2 = a.AddressLine2,
           };
}
継承

公開する派生型を KnownTypeAttribute 属性に含める必要がある

  • 継承はエンティティ型でのみサポート
[KnownType(typeof(PublicSectorCustomer)), KnownType(typeof(PrivateSectorCustomer))]
public class Customer
{
    [Key]
    public int CustomerID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string StateProvince { get; set; }
    public string PostalCode { get; set; }
    [Association("CustomerOrders", "CustomerID", "CustomerID")]
    public List<Order> Orders { get; set; }
}

public class PublicSectorCustomer : Customer
{
    public string GSARegion { get; set; }
}

public class PrivateSectorCustomer : Customer
{
    public string CompanyName { get; set; }
}
関連付け

関連をナビゲートするプロパティにAssociationAttributeを付与する

  • Entity Frameworkで生成している場合はAssociationAttributeを特別指定する必要はない
  • Include属性の付与を忘れないように。これをつけないとクライアントプロジェクトにプロパティが生成されない
複合型(Complex Type)

Entity Frameworkで定義。クライアントではComplexObjectがベースクラスになる

共有エンティティ

エンティティのインスタンスWCF RIA サービス アプリケーション内の複数のドメイン サービス間で共有することができる。要は他のドメインサービスのエンティティを使えるということ。ちなみに、それぞれのDomainServiceでエンティティを宣言した場合はドメインサービスのContextで型は同じでもインスタンスは別になる。

クライアント・サーバー間の共有コード

共有ソース ファイル(*.shared.cs または *.shared.vb))とリンク ファイル方式がある

データの同時実行

元のエンティティ全体で同時実行を確認することはない。RoundtripOriginalAttribute 属性でマークされたメンバーのみ渡される。なお、Entity FrameworkでConcurrencyCheckAttribute 属性、または TimestampAttribute 属性でマークされたメンバーにはRoundtripOriginalAttributeが適用される。

ドメインサービスの利用

ドメインサービスからビルド時に自動生成されるDomainContextクラスを利用してドメインサービスを利用する。
DomainContextはWCF RIA サービのクライアント アプリケーションのクライアント プロキシ クラス。HRService という名前のドメイン サービスには、HRContext という名前の対応するドメイン コンテキストが生成される

クエリ

ドメイン サービスのクエリ メソッド毎に作成される。 Query という接尾辞を加えられたメソッドが生成される。クエリを実行するには、Load メソッドにパラメーターとして クエリメソッドの戻り値のEntityQuery オブジェクトを渡す。

  • クエリは非同期的に実行される
  • EntityQuery に対して追加の条件を付与できる。利用可能なクエリ演算子はWhere、OrderBy、ThenBy、Skip、Take(ただしLINQを利用するためにはSystem.ServiceModel.DomainServices.Clientの名前空間をインポートする)
public partial class MainPage : UserControl
{
    private CustomerDomainContext _customerContext = new CustomerDomainContext();

    public MainPage()
    {
        InitializeComponent();
        EntityQuery<Customer> query = 
            from c in _customerContext.GetCustomersQuery()
            where c.Phone.StartsWith("583")
            orderby c.LastName
            select c;
        LoadOperation<Customer> loadOp = this._customerContext.Load(query);
        CustomerGrid.ItemsSource = loadOp.Entities;
    }
}
データの変更

エンティティを更新、挿入、および削除するためのメソッドはドメイン コンテキスト内に生成されない。代わりに、エンティティを編集してドメイン コンテキストの SubmitChanges メソッドを使用すると、クライアントの変更内容がサーバにまとめて送信され処理される

名前付き更新メソッド

ドメイン サービスのメソッドと同じ名前が生成
クライアント プロジェクトで、メソッドを呼び出しても、実際には SubmitChanges が呼び出されるまでこのメソッドは実行されない。エンティティのインスタンスを介しても呼び出すことができる。

呼び出し操作

ドメイン サービスの各呼び出し操作に使用するメソッドが生成される
呼び出し操作はすぐに実行される。SubmitChanges メソッドの呼び出しは必要ない