DataSource for Entity Framework for WinForms
MVVM の簡略化
設計時の機能 > MVVM の簡略化

開発者がアプリケーションへのメリットを理解するようになるに従い、Model-View-ViewModel(MVVM)パターンがよく使用されるようになっています。その結果、アプリケーションの保守とテストが簡単になり、特に WPF と Silverlight アプリケーションの場合は、UI の設計者とそれを機能させるコードの作成者の間の作業分担が格段に明確になっています。しかし、MVVM パターンに基づいてプログラム開発を支援する効果的なツールがないと、余分なレイヤ(ビューモデル)を実装して、そのレイヤとモデルとの間でデータが正しく同期されているかどうかを確認する必要があるため、実質的に作業が難しくなります。少数の単純なコレクションを使用するだけの比較的小さなアプリケーションの場合(さらに MVVM のベストプラクティスとしてデータソースに ObservableCollection を使用する場合)、この余分な負担はそれほど大きなものではありませんが、アプリケーションが大きくなるほど、そして生成するコレクションの数が増えるほど、負担も大きくなります。DataSource for Entity Framework は、この負担を和らげることができます。

C1DataSource は、ビューモデルとしてライブビューを使用します。それには、モデルコレクションに対してライブビューを作成し、それをビューモデルとして使用するだけです。ライブビューは、ソース(モデル)と自動的に同期されます。つまり、同期コードは何も必要ではなく、すべてが自動的に実行されます。また、コードで ObservableCollection を使用し、手作業でコレクションにデータを挿入して、独自のビューモデルクラスを記述する作業と比較して、ライブビューは格段に簡単に作成することができます。LINQ のすべての能力を自由に使用して、モデルデータをライブビューに再形成できます。したがって、同期コードが不要になるだけでなく、ビューモデルを作成するコードが劇的にシンプルになります。

ライブビューを使用して簡単に MVVM パターンに準拠できることを具体的に示すため、前の2つの例の機能(「コードでのデータソースの操作」の Category-Products マスター/詳細および「ライブビュー」のデータの再形成/フィルタ処理/ソート)をすべて組み合わせたフォームを作成します。Category-Products ビューに、単価が 30 以上の販売終了になっていない製品を単価順に表示します。また、マスター/詳細フォームにカスタマイズされた製品フィールドをいくつか表示し、そこでカテゴリを選択してそのカテゴリの製品を表示できるようにします。ここでは MVVM パターンに従います。CategoryProductsView という名前のフォーム(ビュー)は、GUI コントロール(コンボボックスとグリッド)をホストするだけです。次のように、データソースをビューモデルに設定するコード以外のコードは含みません。

コードのコピー
PublicClassCategoryProductsView
     PrivateviewModel AsCategoryProductsViewModel= NewCategoryProductsViewModel()
     PublicSubNew()
         InitializeComponent()
         comboBox1.DisplayMember = "CategoryName"
         comboBox1.ValueMember = "CategoryID"
         comboBox1.DataSource = viewModel.Categories
         dataGridView1.DataSource = viewModel.Products
     EndSub
 EndClass
コードのコピー
publicpartialclassCategoryProductsView: Form
 {
     CategoryProductsViewModelviewModel = newCategoryProductsViewModel();
     publicCategoryProductsView()
     {
         InitializeComponent();
         comboBox1.DisplayMember = "CategoryName";
         comboBox1.ValueMember = "CategoryID";
         comboBox1.DataSource = viewModel.Categories;
         dataGridView1.DataSource = viewModel.Products;
     }
 }

すべてのロジックは、GUI とは別のビューモデルクラスにあります。さらに正確に言えば、3つのクラス CategoryViewModelProductViewModelCategoryProductsViewModel があります。最初の2つは、追加コードなしでプロパティを定義する単純なクラスです。

コードのコピー
PublicClassCategoryViewModel
     PublicOverridablePropertyCategoryID AsInteger
     PublicOverridablePropertyCategoryName AsString
 EndClass
 Public ClassProductViewModel
     PublicOverridablePropertyProductID AsInteger
     PublicOverridablePropertyProductName AsString
     PublicOverridablePropertyCategoryID AsInteger?
     PublicOverridablePropertyCategoryName AsString
     PublicOverridablePropertySupplierID AsInteger?
     PublicOverridablePropertySupplierName AsString
     PublicOverridablePropertyUnitPrice AsDecimal?
     PublicOverridablePropertyQuantityPerUnit AsString
     PublicOverridablePropertyUnitsInStock AsShort?
     PublicOverridablePropertyUnitsOnOrder AsShort?
 EndClass
コードのコピー
public classCategoryViewModel
{
    public virtual int CategoryID { get; set; }
    public virtual string CategoryName { get; set; }
}
public classProductViewModel
{
    public virtual int ProductID { get; set; }
    public virtual string ProductName { get; set; }
    public virtual int? CategoryID { get; set; }
    public virtual string CategoryName { get; set; }
    public virtual int? SupplierID { get; set; }
    public virtual string SupplierName { get; set; }
    public virtual decimal? UnitPrice { get; set; }
    public virtual string QuantityPerUnit { get; set; }
    public virtual short? UnitsInStock { get; set; }
    public virtual short? UnitsOnOrder { get; set; }
}

次は、CategoryProductsViewModel クラスのコードです。

コードのコピー
PublicClassCategoryProductsViewModel
     Private_scope AsEntityClientScope
     Private_categories AsBindingSource
     PublicPropertyCategories AsBindingSource
         Get
             Return_categories
         EndGet
         PrivateSet(value AsBindingSource)
             _categories = value
         EndSet
     EndProperty
     Private_products AsBindingSource
     PublicPropertyProducts AsBindingSource
         Get
             Return_products
         EndGet
         PrivateSet(value AsBindingSource)
             _products = value
         EndSet
     EndProperty
     PublicSubNew()
         _scope = Program.ClientCache.CreateScope()
         DimCategoriesView AsObject=
             Fromc In_scope.GetItems(OfCategory)()
             SelectNewCategoryViewModelWith
             {
                 .CategoryID = c.CategoryID,
                 .CategoryName = c.CategoryName
             }
         Categories = NewBindingSource(CategoriesView, Nothing)
         DimProductsView AsObject=
             Fromp In_scope.GetItems(OfProduct)().AsFilteredBound(Function(p) p.CategoryID.Value).BindFilterKey(Categories, "Current.CategoryID").Include("Supplier")
             SelectNewProductViewModelWith
             {
                 .ProductID = p.ProductID,
                 .ProductName = p.ProductName,
                 .CategoryID = p.CategoryID,
                 .CategoryName = p.Category.CategoryName,
                 .SupplierID = p.SupplierID,
                 .SupplierName = p.Supplier.CompanyName,
                 .UnitPrice = p.UnitPrice,
                 .QuantityPerUnit = p.QuantityPerUnit,
                 .UnitsInStock = p.UnitsInStock,
                 .UnitsOnOrder = p.UnitsOnOrder
           }
          Products = NewBindingSource(ProductsView, Nothing)
     EndSub
 EndClass
コードのコピー
public classCategoryProductsViewModel
{
    private C1.Data.Entities.EntityClientScope _scope;

    publicBindingSource Categories { get; private set; }
    public BindingSource Products { get; private set; }

    public CategoryProductsViewModel()
    {
        _scope = Program.ClientCache.CreateScope();

        Categories = new BindingSource(
            from c in _scope.GetItems<Category>()
            select new CategoryViewModel()
            {
                CategoryID = c.CategoryID,
                CategoryName = c.CategoryName
            }, null);

        Products = new BindingSource(
            from p in _scope.GetItems<Product>().AsFilteredBound(p =>
            p.CategoryID).BindFilterKey(Categories,
                 "Current.CategoryID").Include("Supplier")
            select new ProductViewModel()
            {
                ProductID = p.ProductID,
                ProductName = p.ProductName,
                CategoryID = p.CategoryID,
                CategoryName = p.Category.CategoryName,
                SupplierID = p.SupplierID,
                SupplierName = p.Supplier.CompanyName,
                UnitPrice = p.UnitPrice,
                QuantityPerUnit = p.QuantityPerUnit,
                UnitsInStock = p.UnitsInStock,
                UnitsOnOrder = p.UnitsOnOrder
            }, null);
        }
}

基本的に、2つの LiveLinq ステートメントが含まるほかは、何も含まれていません。これらのステートメント(ライブビューを作成。「ライブビュー」を参照)は、BindingSource のコンストラクタでラップされており、ビューモデルクラスから公開される Categories コレクションと Products コレクションにカレンシー、現在の項目などのサポートを追加します。WinForms の場合にのみ BindingSource を使用する必要があることに注意してください。これは、WinForms プラットフォームが、WPF や Silverlight ほど MVVM に適していないことによります。また、BindingSource を使用するため、以下のステートメントをコードファイルに追加する必要があります(WinForms のみ)。

C#
コードのコピー
using System.Windows.Forms;

コードでのデータソースの操作」で説明したように、AsFilteredBound() を使用して、サーバー側でフィルタ処理を行うことができます。また、コンボボックスイベントを使用して選択された CategoryID には、フィルタキー(Product.CategoryID プロパティ)を接続しました。ここでは、コードを GUI から独立に保つために、この方法は使用できません。したがって、BindFilterKey メソッドを使用して、Categories コレクションで現在選択されている項目の Category.CategoryID プロパティにフィルタキーを連結します。これは、Categories コレクションでカレンシーをサポートする必要があること、そのコレクションを BindingSource でラップして WinForms でカレンシーサポートを利用することの理由の1つです。

Include("Supplier")演算子は厳密には必要ではありませんが、ここでは、パフォーマンスを最適化するために使用されています。これがないと、Entity Framework は、まだ Supplier がフェッチされていない Products コレクションの要素にユーザーがアクセスするたびに、Supplier オブジェクトを1つずつ必要に応じてフェッチします。これにより、遅延が発生する可能性があります。また、バッチではなく、単一行のデータをフェッチすると一般に効率が大きく低下します。したがって、ここでは、製品と同じクエリーでサプライヤ情報をフェッチするように Entity Framework に指示する Include("Supplier")を使用して、遅延ロードを回避します。