Partager via


Tech・Ed Japan 2010、多くの皆様のご来場、誠に有難うございました!フォローアップ-1-DomainDataSource編

皆様、こんにちは!

Tech・Ed 2010 Japan、多くの方にご来場いただき、本当にありがとうございました。私のセッションも本当に大盛況で、立ち見の方まで出てしまい大変失礼いたしました。この場を借り増して本当御礼を申し上げます。今後ともSilverlight、Windows Phone 7、Internet Explorer 9 等、色々なUX / Client系のセッションでお目にかかれる日を楽しみにしております。

さて、早速、最初のフォローアップとして、まずはスクラッチでDomainDataSourceを使って作るアプリケーションの作成手順についてご紹介します。

サンプルプロジェクトはこちらにありますので、ぜひダウンロードしてご覧ください。

※アクセス許可を”全員”に変更しました。大変失礼しました。再度お試しください。

※※ラップアップセッション用に追加したwWebカム部分は未完成です。撮影はできますが、DomainDataSourceを準備してどこかに格納した方が良いかと思います。印刷機能は(本来はXAMLで帳票を作ってそちらに渡す方がSmartですが)、今回はBitmapで生成しています。

1.データベースの作成

今回は、BookClubというDBを使いますので、こちらのフォルダに入っているBookClub.SQLスクリプトを使って、まずは作成をお願いします。SQL Server Management Studioで、最初にDB名”BookClub"を作っておいて、それからこのBookClub.SQLを実行するのが一番速いでしょう。

※なお、このデータベースは英語版です。私がセッションで使用したものは独自に日本語化したものです。

2.Entity Data Modelの追加(TechEdDemo7.Web)

image

BookClubのDBを作って接続確認をしたら、Entity Data Modelを作ります。名前はBookShelfModel.edmxで、テーブルで全てのテーブルをチェックして、進みます。下記のようなModelが生成されたら、一度Buildしておきましょう。

image

3.DomainServicesの追加(TechEdDemo7.Web)

BookShelfService.csという名前で、DomainServiceを追加します。

image

出来上がったら、下記のようなダイアログが出ますので、Bookのみを選択し、Enable EditingをチェックしてOKです。image

BookShelfService.csのソースウィンドウが開きますので、CRUDのメソッド(Get、Insert、Delete、Update)が入っていることを確認してください。

この後BookShelfService.csを開き、GetBooks()を下記のように登録日でソートするようにLINQのクエリーを書き加えておきます。

         public IQueryable<Books> GetBooks()
        {
            return this.ObjectContext.Books.
                OrderByDescending
                (b => b.AddedDate);
        }

以上で取り急ぎサーバー側は終了です。

4.MainPage.xamlの編集(TechEdDemo7)

クライアント側に移ります。まずは、MainPage.xamlを下記のように大きさ変更します。

 [MainPage.xaml]
    d:DesignHeight="600" d:DesignWidth="840">

Visual StudioのUIデザイナーでXAMLの方を表示して、最初の<Grid/>の直下に、下記のようにGridを分割するための値を設定します。

 最初のGrid直下
[MainPage.xaml]
        <Grid.RowDefinitions>
            <RowDefinition Height="52*" />
            <RowDefinition Height="436*" />
            <RowDefinition Height="36*" />
            <RowDefinition Height="39*" />
            <RowDefinition Height="37*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="320*" />
            <ColumnDefinition Width="112*" />
            <ColumnDefinition Width="118*" />
            <ColumnDefinition Width="118*" />
            <ColumnDefinition Width="132*" />
        </Grid.ColumnDefinitions>

次に、この段階で、Expression Darkのテーマを適用してしまいましょう。Visual StudioのUIデザイナーでXAMLの方を表示して、最初の<Grid/>の直ぐ上に、ツールボックスから、下記をドラッグ&ドロップで挿入します。これはToolkitに含まれているテーマです。

image

一番下の</Grid>の直ぐ下に、<・・と入力すると、インテリセンスが働きますので、ExpressionDarkThemeのタグを選んで挿入します。

5.メタデータ(アトリビュート)による列名表示の変更とバリデーション設定

次に、すでに出来上がったDomainDataSourceを、このMainPageの各分割されたGridにドラッグ&ドロップして、マスター・ディテール的な画面を作っていきます。

まずは、セッションでもご紹介したように、列名の変更とバリデーションを同時に設定する方法です。これには、BookShelfService.metadata.cs を編集します。これで宣言的に設定することができ、ビルドすると同時に、クライアントに波及させることができるのです。

まずは、BookShelfService.metadata.cs を開き、下記のように編集します。

             [Display(Name = "登録日", Order = 4)]
            public DateTime AddedDate { get; set; }

            [Required(ErrorMessage = "Amazon IDは必須項目です。")]
            [RegularExpression("^[A-Za-z0-9]{10}$",
            ErrorMessage = "ASIN は、数字のディジットで 10 桁です。")]
            [Display(Name = "Amazon ID", Order = 5)]
            public string ASIN { get; set; }

            [Required(ErrorMessage = "著者名は必須項目です。")]
            [Display(Name = "著者", Order = 2)]
            public string Author { get; set; }

            [Display(Name = "書籍ID", Order = 6)]
            public int BookID { get; set; }

            [Display(Name = "カテゴリID", Order = 7)]
            public int CategoryID { get; set; }

            [Required(ErrorMessage = "詳細は必須項目です。")]
            [Display(Name = "詳細", Order = 3)]
            public string Description { get; set; }

            [Display(Name = "メンバーID", Order = 8)]
            public int MemberID { get; set; }

            [Required(ErrorMessage = "出版日付は必須項目です。")]
            [Display(Name = "出版日付", Order = 9)]
            public DateTime PublishDate { get; set; }

            [Required(ErrorMessage = "タイトルは必須項目です。")]
            [Display(Name = "タイトル", Order = 1)]
            public string Title { get; set; }

これを行ってから、再度ソリューションをビルドします。

その後は、MainPage.xamlの左側のエリアにDataGrid形式(Title以外”なし”に設定)、右側のエリアにDataForm形式で、同じDomainDataSourceをドラッグ&ドロップします。

実行した結果は、このような感じです。これを実行してみてください。左側にタイトル、右側がそれに連動して詳細が表示されるはずです。

image

なお、セッションでは割愛しましたが、この時点で、ツールボックスから、”DataPager”を選び、DataGridの下のエリアに貼り付けておきあす。そして、同じDomainDataSourceをこの上にドラッグ&ドロップすると、デフォルトで10件ごとにDataGridのページが切り替わるように設定できます。

 image

6. 更新(U)・削除(D)および保存(MainPage.xaml.cs)

セッションでもご紹介しましたが、このDomainDataSourceを使ったコントロールは、最初から編集ができるようになっています。しかし、その編集を、データソースに反映させる(確定する)には、保存を明確に指示しなければなりません。またデータソースに反映させる必要がなければ、すべての編集を破棄すれば良い訳です。

まず削除ボタンは、この画面にある内容をビューとしてとらえて削除し、保存ボタンを使って変更を確定させるようにします。また保存ボタンは、更新と削除を確定させる処理を書きます。キャンセルの場合には、全ての変更を破棄します。

保存ボタンを配置し、Clickイベントハンドラで下記の記述をします。SubmitChanges()が確定、RejectChanges()がすべての変更の破棄を行うメソッドで、同じ名前のサーバー側(DomainService)のメソッドを呼び出します。

         private void Save_Click(object sender, RoutedEventArgs e)
        {
            if (MessageBox.Show("書籍情報の編集を確定してよろしいですか?", "編集確定の確認",
                MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                booksDomainDataSource.SubmitChanges();
            }
            else
            {
                booksDomainDataSource.RejectChanges();
                booksDomainDataSource.Load();
                booksDataGrid.SelectedItem = booksDataGrid.Cursor;
            }
        }

また非同期処理であるゆえ、実際に成功したかどうかを取得したいと考えます。その時には、DomainDataSourceのプロパティを開いて、SubmittedChanges()というイベントを探し、イベントハンドラ内を下記のように記述します。

         private void booksDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
        {
            if (e.HasError)
            {
                MessageBox.Show(e.Error.Message, "データ保存エラー", MessageBoxButton.OK);
                e.MarkErrorAsHandled();
            }
            else
            {
                MessageBox.Show("データが保存できました!", "データ保存成功", MessageBoxButton.OK);
            }
        }

次に削除処理です。これはあくまでも画面のViewに対して行えばいいので、そのような処理を書いて、確定させるときには、保存ボタンを使うことにします。

         private void Delete_Click(object sender, RoutedEventArgs e)
        {
            if (MessageBox.Show("書籍情報を削除してよろしいですか?", "削除の確認",
                MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                booksDomainDataSource.DataView.Remove(booksDomainDataSource.DataView.CurrentItem);
            }
        }

画面はこのようになります。

image

7. 追加( C )(MainPage.xaml.cs、ChildWindow1.xaml.cs)

次に追加の処理です。画面が一枚しかないので、クライアント側のアプリケーションに、ChildWindowを新規項目の追加→追加します。

image

 

そして、このWindowにも、UIのLook & Feelが同じになるよう、ExpressionDarkのThemeを適用し、大きさは、640 x 800にしておきます。

MainPage.xamlの右側と同じようにデータフォーム形式で入力したいので、booksDomainDataSourceをドラッグ&ドロップして、おきます。このとき、booksDomainDataSourceのAutoLoadプロパティ → falseにしておくことを忘れないでください。これを行わないと、最新のデータが入力された状態でChildWindow1が立ち上がってしまいます。

<ChildWindow1.xaml.cs>

下記の内容を追加します。

まずは、booksという変数を宣言します。これはデータ保持のために使われます。

Books books = null;

次に、

using TechEdDemo7.Web;

これを指定しておかないと、DomainContextやDomainDataSourceを参照できませんので、参照します。

         public ChildWindow1() 
        { 
            InitializeComponent(); 

            books = new Books(); 
            ((BookShelfContext)booksDomainDataSource.DomainContext).Books.Add(books);
            grid1.DataContext = books;
        } 

新しい書籍情報のインスタンスを作成し、DomainDataSourceに追加した上で、それを入力フォームに関連付けます。

         private void OKButton_Click(object sender, RoutedEventArgs e)
        {

            booksDomainDataSource.SubmitChanges();
            if (!books.HasValidationErrors)
                this.DialogResult = true; 
        }

OKボタンの処理の中で、SubmitChanges()を発行して更新を確定します。エラー処理はもっとちゃんとした方が良いかもしれません。

次にMainPage.xamlの方に移ります。

<MainPage.xaml>

追加ボタンを貼り付け名前をAddにして、Clickイベントハンドラに下記の内容を入力します。

         private void Add_Click(object sender, RoutedEventArgs e)
        {

        ChildWindow1 cw = new ChildWindow1();

       cw.Closed += (s, ev) => 
     { 
              if (cw.DialogResult == true) 
               { 
                      booksDomainDataSource.LoadedData += 
                            new EventHandler<LoadedDataEventArgs>(Inserted); 
                     booksDomainDataSource.Load(); 
              } 
          }; 

     cw.Show(); 

        }

データを再度Loadした時の処理を行うイベントハンドラ(Inserted)を追加したので、そちらの処理も記述します。

 
        private void Inserted(object sender, LoadedDataEventArgs e)
        {
 dataPager1.PageIndex = dataPager1.PageCount;
    booksDomainDataSource.LoadedData -= Inserted
        }

 

とりあえず、モデル作成・サービス作成・データの追加/削除/保存/新規作成の箇所まで説明しました。

この後、WCF RIA Services Libraryを使った別のテーブルの参照や、[Include]による関連テーブルの読込と、表示(カテゴリ別)、を説明します。

8. WCF RIA Services Class Libraryによるメンバー参照

WCF RIA Servicesの特徴は、セッションでもご紹介した通り、コードの自動生成による、サーバー側(DomainService)とクライアント側(DomainContext(DomainDataSource))間における処理の共有です。ルールや制約、バリデーション等がすべて即時共有できるのです。

ただ、別のサービスとして管理しておきたい場合等、サービス側の変更を波及したくない場合もあります。疎結合的に参照できれば良い場合がある訳です。またLibraryにすれば、スケーラビリティが向上するので、多数のクライアントから参照される場合には、Libraryにしておいた方が都合が良いです。

そこで、WCF RIA Services Class Library を追加してみます。

やり方は、簡単です。同じソリューション内の場合には、プロジェクトの追加→WCF RIA Services Class Libraryを選んで、適当な名前を付けてOKを押してください。

image

これで通常のWCF RIA Services を有効にしたSilverlightアプリケーションと同じように、クライアント側のプロジェクトと、サーバー側のプロジェクトができますので、同じようにして、ここではMembersのテーブルをEntity Data Modelとして追加してください。これは名前も変えておいた方が良いでしょう。

(例;MembersDataModel.edmx / MembersDomainService.cs 等)

ビルドすると、サービス側のDomainServiceは同じように完成します(GetMembers、他)。

その後、ソリューション全体のBuildを行い、TechEdDemo7からRIASerivicesLibrary1(例)を、TechEdDemo7.WebからRIA ServicesLibrary.Web(例)を、それぞれ”参照の追加”で追加しておきます。

再度ソリューションをビルドします。

<ChildWindow2.xaml>

そこで、今度は元々のプロジェクトのクライアント(ここではTechEdDemo7)側に移り、表示・編集用にChildWindow2を準備し、ChildWindow1の時と同じように設定しておきます(640x800、ExpressionDarkのTheme適用)。ここにデータソースウインドウからMembersデータグリッドを、ドラッグ&ドロップします。

その前に、日本語表記にしたいので、例によって、MembersDomainServices.metadata.csを開き、下記のように編集しておきます。

 [MemberDomainService.metadata.cs]
            [Display(Name = "登録日", Order = 1)]
            public DateTime JoinDate { get; set; }

            [Display(Name = "初回ログイン日", Order = 2)]
            public DateTime LoginDate { get; set; }
            
            [Display(Name = "メンバーアリアス", Order = 3)]
            [Required(ErrorMessage = "メンバーアリアスは必須項目です。")]
            public string MemberAlias { get; set; }

            [Display(Name = "メンバーID", Order = 4)]
            public int MemberID { get; set; }

            [Display(Name = "メンバー名", Order = 5)]
            public string MemberName { get; set; }

            [Display(Name = "所属オフィス", Order = 6)]
            public string MemberOffice { get; set; }

            [Display(Name = "パスワード", Order = 7)]
            [Required(ErrorMessage = "パスワードは必須項目です。")]
            public string Password { get; set; }

その後、ソリューション全体のBuildを行い、ChildWindow2を表示し、データソースウインドウから、MembersのDataGridをドラッグ&ドロップします。

image

MainPage.xamlにボタンを配置して、下記のように記述します(呼び出すだけ)。

 
        private void showMembers_Click(object sender, RoutedEventArgs e)
        {
            ChildWindow2 cw2 = new ChildWindow2();
            cw2.Show();
        }

<RIAServicesLibrary1(例).WebのApp.ConfigからTechEdDemo7.WebのWeb.Configへの文字列コピー>

上記に加えて、RIAServicesLibrary1(例).WebのApp.configにある下記の部分(ConnectionStringセクション)を、TechEdDemo7.WebのWeb.Configの同セクションにコピー&ペーストしてください。

   <connectionStrings>
    <add name="BookClubEntities" connectionString="metadata=res://*/BookShelfModel.csdl|res://*/BookShelfModel.ssdl|res://*/BookShelfModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.\sqlexpress;Initial Catalog=BookClub;Integrated Security=True;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
    <add name="MembersEntities" connectionString="metadata=res://*/MembersModel.csdl|res://*/MembersModel.ssdl|res://*/MembersModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.\sqlexpress;Initial Catalog=BookClub;Integrated Security=True;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>

これでBuildした上で実行すれば、疎結合的に、Membersテーブルを呼び出せます。

9. サーバー側に新しいメソッドを付加してカテゴリーによる参照(サーバー側DomainServiceを追加)

前半の最後に、メタデータによる拡張として、ご紹介したのが、この話です。”Service Methodのメタデータ公開”の一部になりますね。(本番ではDemoは最後にご紹介しました)

BookShelfModel.edmxは、Entity Data Modelとして作ってある訳ですから、Booksテーブルから参照されているテーブルもJoinできるはずです。そこで、CategoriesとBooksの1対多関係から、Category別にBooksを参照できる(番号を入れたらそのCategory番号の書籍を一覧表示できる)ウインドウを作りましょう。

まずは、BookShelfService.metadata.cs を再び開いて、ここにBooks以外のテーブル(ここではCategories)を読み込めるよう、[Include]設定を行います。最初の頃に実施した列名表記修正・制約/ルール設定の直前に、こちらを入れてください。

             [Include]
            public EntityCollection<Categories> Categories { get; set; }

そして、BookShelfService.cs を開いて、下記のメソッドをDomainServiceに追加します。GetBooks()の後に入れるのが良いでしょう。

         public IQueryable<Books> GetBooksByCategory(int CategoryID)
        {
            return this.ObjectContext.Books.
               Include("Categories").
              Where(c => c.CategoryID == CategoryID);
        }

修正したらソリューションをBuildします。

<ChildWindow3.xaml>

新しいChildWindowを追加し、それにこの新しいGetBooksByCategoryクエリーをドラッグ&ドロップします。データソースウインドウのBooksのDomainContextのところを展開すると下記のようになっていること(GetBooksByCategoryが選べること)が確認できます。

 image

 

MainPage.xamlにボタンを配置し、これを呼び出します。

         private void Categorized_Click(object sender, RoutedEventArgs e)
        {
            ChildWindow3 cw3 = new ChildWindow3();
            cw3.Show();
        }

以上で、前半の1)CRUD処理の部分と2)サービス拡張部分のデモは全てです。いかがでしょうか?

ぜひサンプルソリューションをダウンロードして確かめてみてください。

それでは、次回は、最後の3)MVVM編か、この続きのいずれか、をご紹介します。

鈴木 章太郎