開発者がアプリケーションへのメリットを理解するようになるに従い、Model-View-ViewModel(MVVM)パターンがよく使用されるようになっています。その結果、アプリケーションの保守とテストが簡単になり、特に WPF と Silverlight アプリケーションの場合は、UI の設計者とそれを機能させるコードの作成者の間の作業分担が格段に明確になっています。しかし、MVVM パターンに基づいてプログラム開発を支援する効果的なツールがないと、余分なレイヤ(ビューモデル)を実装して、そのレイヤとモデルとの間でデータが正しく同期されているかどうかを確認する必要があるため、実質的に作業が難しくなります。少数の単純なコレクションを使用するだけの比較的小さなアプリケーションの場合(さらに MVVM のベストプラクティスとしてデータソースに ObservableCollection を使用する場合)、この余分な負担はそれほど大きなものではありませんが、アプリケーションが大きくなるほど、そして生成するコレクションの数が増えるほど、負担も大きくなります。Entity Framework DataSource(EF DataSource)は、この負担を和らげることができます。
EF DataSource は、ビューモデルとしてライブビューを使用します。それには、モデルコレクションに対してライブビューを作成し、それをビューモデルとして使用するだけです。ライブビューは、ソース(モデル)と自動的に同期されます。つまり、同期コードは何も必要ではなく、すべてが自動的に実行されます。また、コードで ObservableCollection を使用し、手作業でコレクションにデータを挿入して、独自のビューモデルクラスを記述する作業と比較して、ライブビューは格段に簡単に作成することができます。LINQ のすべての能力を自由に使用して、モデルデータをライブビューに再形成できます。したがって、同期コードが不要になるだけでなく、ビューモデルを作成するコードが劇的にシンプルになります。
ライブビューを使用して簡単に MVVM パターンに準拠できることを具体的に示すため、前の2つの例の機能(「コードでのデータソースの操作|document=WordDocuments\C1DataStudio-WPF.docx;topic=Working with Data Sources in Code」の Category-Products マスター/詳細および「ライブビュー|document=WordDocuments\C1DataStudio-WPF.docx;topic=Live Views」のデータの再形成/フィルタ処理/ソート)をすべて組み合わせたフォームを作成します。Category-Products ビューに、単価が 30 以上の販売終了になっていない製品を単価順に表示します。また、マスター/詳細フォームにカスタマイズされた製品フィールドをいくつか表示し、そこでカテゴリを選択してそのカテゴリの製品を表示できるようにします。ここでは MVVM パターンに従います。CategoryProductsView という名前のフォーム(ビュー)は、GUI コントロール(コンボボックスとグリッド)をホストするだけです。データソースをビューモデルに設定するコード以外のコードは含みません。
すべてのロジックは、GUI とは別のビューモデルクラスにあります。さらに正確に言えば、3つのクラス CategoryViewModel、ProductViewModel、CategoryProductsViewModel があります。最初の2つは、追加コードなしでプロパティを定義する単純なクラスです。
| Visual Basic |
コードのコピー
|
|---|---|
Public Class CategoryProductsViewModel
Private _scope As EntityClientScope
Private _categories As ICollectionView
Public Property Categories As ICollectionView
Get
Return _categories
End Get
Private Set(value As ICollectionView)
_categories = value
End Set
End Property
Private _products As ICollectionView
Public Property Products As ICollectionView
Get
Return _products
End Get
Private Set(value As ICollectionView)
_products = value
End Set
End Property
Public Sub New()
_scope = Application.ClientCache.CreateScope()
Categories =
From c In _scope.GetItems(Of Category)()
Select New CategoryViewModel With
{
.CategoryID = c.CategoryID,
.CategoryName = c.CategoryName
}
Products =
From p In _scope.GetItems(Of Product)().AsFilteredBound(Function(p) p.CategoryID.Value)
.BindFilterKey(Categories, "CurrentItem.CategoryID").Include("Supplier")
Select New ProductViewModel With
{
.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
}
End Sub
End Class
|
|
| C# |
コードのコピー
|
|---|---|
public class CategoryViewModel { public virtual int CategoryID { get; set; } public virtual string CategoryName { get; set; } } public class ProductViewModel { 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; } } public class CategoryProductsViewModel { private C1.Data.Entities.EntityClientScope _scope; public System.ComponentModel.ICollectionView Categories { get; private set; } public System.ComponentModel.ICollectionView Products { get; private set; } public CategoryProductsViewModel() { if (App.ClientCache == null) return; _scope = App.ClientCache.CreateScope(); Categories = from c in _scope.GetItems<Category>() select new CategoryViewModel() { CategoryID = c.CategoryID, CategoryName = c.CategoryName }; Products = from p in _scope.GetItems<Product>().AsFilteredBound(p => p.CategoryID) .BindFilterKey(Categories, "CurrentItem.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 }; } } |
|
基本的に、2つの LiveLinq ステートメントが含まるほかは、何も含まれていません。
「コードでのデータソースの操作」で説明したように、AsFilteredBound を使用して、サーバー側でフィルタ処理を行うことができます。「コードでのデータソースの操作」では、コンボボックスイベントを使用して選択された CategoryID にフィルタキー(Product.CategoryID プロパティ)を接続しました。ここでは、コードを GUI から独立に保つために、この方法は使用できません。したがって、BindFilterKey メソッドを使用して、Categories コレクションで現在選択されている項目の Category.CategoryID プロパティにフィルタキーを連結します。
Include("Supplier")演算子は厳密には必要ではありませんが、ここでは、パフォーマンスを最適化するために使用されています。これがないと、Entity Framework は、まだ Supplier がフェッチされていない Products コレクションの要素にユーザーがアクセスするたびに、Supplier オブジェクトを1つずつ必要に応じてフェッチします。これにより、遅延が発生する可能性があります。また、バッチではなく、単一行のデータをフェッチすると一般に効率が大きく低下します。したがって、ここでは、製品と同じクエリーでサプライヤ情報をフェッチするように Entity Framework に指示する Include("Supplier")を使用して、遅延ロードを回避します。
最後に、フォーム(ビュー)CategoryProductsView.xaml 内の GUI コントロールに対してデータ連結を指定する必要があります。MVVM パターンに準拠し、XAML(コードではない)で次のように指定します。
| XAML |
コードのコピー
|
|---|---|
<Grid>
<Grid.DataContext>
<local:CategoryProductsViewModel />
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox HorizontalAlignment="Left" Margin="5" Name="comboBox1"
Width="221" ItemsSource="{Binding Categories}"
DisplayMemberPath="CategoryName" />
<DataGrid Grid.Row="1" AutoGenerateColumns="True" Name="dataGrid1"
ItemsSource="{Binding Products}"/>
</Grid>
|
|
次のように DataContext を定義します。
| XAML |
コードのコピー
|
|---|---|
<Grid.DataContext>
<local:CategoryProductsViewModel />
</Grid.DataContext>
|
|
フォームのデータソースとして CategoryProductsViewModel オブジェクトを作成し、"{Binding Categories}" および "{Binding Products}" を使用してそこにコンボボックスとグリッドを連結しました。