DataSource for Entity Framework for WPF/Silverlight
コードでのデータソースの操作
DataSource for Entity Framework > コードでのデータソースの操作

ここまでは、ほとんどコードを記述せずに、デザインサーフェスで直接データソースを設定してきました。これらの設定は DataSource for Entity Framework によってたいへん簡単になりましたが、すべてをコードで行いたい場合もあります。C1DataSource はこれも可能にします。これまでに行ったことはすべて、実行時にコードで行うことができます。

その取りかかりとしてわかりやすい方法は、C1DataSourceViewSourceCollection の要素として実質的にデザイナで設定してきた ClientViewSource オブジェクトを C1DataSource なしで独自に作成できるなら、これを使用することです。ただし、一歩下がって、下位レベルのクラス ClientView<T> を使用することもできます。これにより、サーバーからのデータのロードを完全に制御できます。また、このクラスは C1.LiveLinq.LiveViews.View<T> から派生しているため、任意の LiveLinq 演算子を適用できます。データソースを View<T> に設定できる任意の GUI コントロールに対してこのクラスを連結できるということは、完全に編集可能なデータビューが手に入るということでもあります。

サーバー側のフィルタ処理は、おそらく最もよく使用されている操作です。普通、データベーステーブル全体を無制限のままクライアントに提供しようとは誰も考えないからです。先ほどは、C1DataSource を使用してこれをコードなしで簡単に実行できることを確認しましたが、ここでは実行時コードを使用します。

C1DataSource なしの実行時コードでクライアント側のデータキャッシュを使用するには、プロジェクトのメインクラスに数行を追加して、グローバルなクライアント側データキャッシュを作成します。C1DataSource を使用する場合、これはバックグラウンドで作成されました。今回は、次のコードを使用して明示的に作成します。

VisualBasic
コードのコピー
Imports C1.Data.Entities
Class Application
     Public Shared ClientCache As EntityClientCache
     Private Sub Application_Startup(sender As Object, e As
System.Windows.StartupEventArgs) Handles Me.Startup
         ClientCache = EntityClientCache.GetDefault(GetType(NORTHWNDEntities))
     End Sub
End Class

C#
コードのコピー
using C1.Data.Entities;
public partial class App : Application
{
    public static EntityClientCache ClientCache;
    public static NORTHWNDEntities ObjectContext;
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        ObjectContext = new NORTHWNDEntities();
        ClientCache = new EntityClientCache(ObjectContext);
    } 
}

このコードは、アプリケーションレベルの(静的な)ObjectContext を1つ作成し、それを EntityClientCache に関連付けます。先に「クライアントデータキャッシュの能力」トピックで説明したように、アプリケーション全体で1つのコンテキスト(およびキャッシュ)を保持できることは、C1DataSource によって可能になった大幅な簡略化です。

実行時コードでサーバー側のフィルタ処理を実行するには、次の手順に従います。

  1. 新しいフォームを追加し、フォームにグリッド(dataGridView1)、コンボボックス(comboBox1)、およびボタン(btnSaveChanges)を追加してフォームクラスに次のコードを追加します。 
    VisualBasic
    コードのコピー
    Imports C1.Data.Entities
            Imports C1.Data
    Public Class DataSourcesInCode
         Private _scope As EntityClientScope
                 Public Sub New()
             ' This call is required by the designer.
                     InitializeComponent()
             ' Add any initialization after the InitializeComponent() call.
             _scope = Application.ClientCache.CreateScope()
             Dim viewCategories As ClientView(Of Category) = _scope.GetItems(Of Category)()
             comboBox1.DisplayMemberPath = "CategoryName"
                     comboBox1.ItemsSource = viewCategories
             BindGrid(viewCategories.First().CategoryID)
                 End Sub
         Private Sub BindGrid(categoryID As Integer)
                     dataGrid1.ItemsSource =
                            (From p In _scope.GetItems(Of Product)().AsFiltered(
                                  Function(p As Product) p.CategoryID.Value = categoryID)
                              Select New With
                              {
                                  p.ProductID,
                                  p.ProductName,
                                  p.CategoryID,
                                  p.Category.CategoryName,
                                  p.SupplierID,
                                  .Supplier = p.Supplier.CompanyName,
                                  p.UnitPrice,
                                  p.QuantityPerUnit,
                                  p.UnitsInStock,
                                  p.UnitsOnOrder
                              }).AsDynamic()
                 End Sub
                 Private Sub comboBox1_SelectionChanged(sender As System.Object, e
    As System.Windows.Controls.SelectionChangedEventArgs) Handles comboBox1.SelectionChanged
                     If comboBox1.SelectedValue IsNot Nothing Then
                         BindGrid(CType(comboBox1.SelectedValue, Category).CategoryID)
                     End If
                 End Sub
         Private Sub btnSaveChanges_Click(sender As System.Object, e As
    System.Windows.RoutedEventArgs) Handles btnSaveChanges.Click
                     Application.ClientCache.SaveChanges()
                 End Sub
    End Class
    

    C#
    コードのコピー
    using C1.Data.Entities;
    using C1.Data;
    public partial class DataSourcesInCode : Window
    {
        private EntityClientScope _scope;
        public DataSourcesInCode()
        {
            InitializeComponent();
            _scope = App.ClientCache.CreateScope();
            ClientView<Category> viewCategories = _scope.GetItems<Category>();
            comboBox1.DisplayMemberPath = "CategoryName";
            comboBox1.ItemsSource = viewCategories;
            BindGrid(viewCategories.First().CategoryID);
        }
        private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (comboBox1.SelectedValue != null)
                BindGrid(((Category)comboBox1.SelectedValue).CategoryID);
        }
        private void BindGrid(int categoryID)
        {
            dataGrid1.ItemsSource =
                (from p in _scope.GetItems<Product>().AsFiltered(
                      p => p.CategoryID == categoryID)
                 select new
                 {
                     p.ProductID,
                     p.ProductName,
                     p.CategoryID,
                     CategoryName = p.Category.CategoryName,
                     p.SupplierID,
                     Supplier = p.Supplier.CompanyName,
                     p.UnitPrice,
                     p.QuantityPerUnit,
                     p.UnitsInStock,
                     p.UnitsOnOrder
                 }).AsDynamic();
        }
        private void btnSaveChanges_Click(object sender, RoutedEventArgs e)
        {
            App.ClientCache.SaveChanges();
        }
    }
    
  2. アプリケーションを保存、ビルド、および実行します。「サーバー側のフィルタ処理」の例と同じ結果が得られますが、今回は、すべてをコードで実装した点が異なります。

それでは、先ほど記述したコードを見ていきます。

プライベートフィールド _scope は、フォームからグローバルデータキャッシュへのゲートウェイになります。C1DataSource コンポーネントを直接使用する場合はこれが自動的に行われるため、直接使用しない場合はこのパターンに準拠することが推奨されます。これにより、フォームが有効な間、フォームが必要とするエンティティはキャッシュに留まり、それらのエンティティを保持するすべてのフォーム(スコープ)が解放されると、エンティティも自動的に解放されます。

コンボボックスにすべてのカテゴリを表示するビューを作成することは簡単です。

VisualBasic
コードのコピー
Dim viewCategories As ClientView(Of Category) = _scope.GetItems(Of Category)()

C#
コードのコピー
ClientView<Category> viewCategories = _scope.GetItems<Category>();

ビューをグリッドに連結し、コンボボックスで選択されたカテゴリに関連する製品だけを提供するには、追加の演算子 AsFiltered(<predicate>) が必要です。

VisualBasic
コードのコピー
From p In _scope.GetItems(Of Product)().AsFiltered(Function(p As Product)
p.CategoryID.Value = categoryID)

C#
コードのコピー
from p in _scope.GetItems<Product>().AsFiltered(p => p.CategoryID == categoryID)

このクエリーが実行されても、要求された製品を取得するために必ずしもサーバーへのラウンドトリップが必要にならないことに注意してください。この要求データがこのフォームまたはアプリケーション内の別のフォームから以前に要求されたことがあるために、既にキャッシュに含まれていないかどうかが最初に調べられます。または、アプリケーション内のどこかで完全に別のクエリーが実行されて、すべての製品を返すように要求されたため、すべての製品データがキャッシュに既に含まれている場合もあります。これも、C1DataSource の基本的な長所の1つです。アプリケーションにデータのグローバルキャッシュを提供することで、アプリケーションの全ライフタイムを通してパフォーマンスが継続的に改善されます。

ここでは、ユーザーがコンボボックスで新しいカテゴリを選択するたびに(コンボボックスの SelectedValueChanged イベントを参照)、新しいビューを作成し、そこにグリッドを連結することにしました。ただし、いつも新しいビューを作成するのではなく、特別な BindFilterKey を使用してビューを1つ作成するだけで済ますこともできます。これについては、「MVVM の簡略化」で詳しく説明します。

要するに、ここでは、「サーバー側のフィルタ処理」で C1DataSource を使用してデザインサーフェス上で行った作業をコードでも繰り返したということです。さらに、少しばかり手を加えて、「ビューのカスタマイズ」で LiveLinq ステートメントに Select を追加して行ったように、グリッド列に表示されるフィールドをカスタマイズしました。

コードのコピー
Select New With
 {
     p.ProductID,
     p.ProductName,
     p.CategoryID,
     p.Category.CategoryName,
     p.SupplierID,
     Supplier = p.Supplier.CompanyName,
     p.UnitPrice,
     p.QuantityPerUnit,
     p.UnitsInStock,
     p.UnitsOnOrder
 }

コードのコピー
select new
{
     p.ProductID,
     p.ProductName,
     p.CategoryID,
     CategoryName = p.Category.CategoryName,
     p.SupplierID,
    Supplier = p.Supplier.CompanyName,
    p.UnitPrice,
     p.QuantityPerUnit,
     p.UnitsInStock,
     p.UnitsOnOrder
 };

特別な書式設定なしで、生の製品データをテーブルから返すだけなら、次のように記述できます。
  select p;