WCF RIA Services中的集合(2)
这是本文的第二部分。
在第一部分中,我们讨论了两个相对简单的集合类型:EntitySet和EntityList。在本文中,我们将更进一步的了解其他两个更高级的类型:ICollectionView和DomainCollectionView。
ICollectionView
ICollectionView并不是一个新的接口,已经有大量的Silverlight控件对其进行了实现,如DataGrid。现在,我们可以直接在ViewModel中使用它。为了允许控件绑定到一个ICollectionView的实现(如我们熟悉的CollectionViewSource和PagedCollectionView),我们可以这样做:
private ICollectionView CreateView(IEnumerable source) {
CollectionViewSource cvs = new CollectionViewSource();
cvs.Source = source;
return cvs.View;
}
private ICollectionView _books;
public ICollectionView Books {
get {
if (this._books == null) {
this._books = CreateView(this.Context.Books);
this._books.Filter = new Predicate<object>(BookCorrespondsToFilter);
}
return this._books;
}
}
当载入Book数据时会自动反映到View中:
public CollectionViewViewModel() {
InstantiateCommands();
// load books
Context.Load<Book>(Context.GetBooksQuery().Take(10));
}
ICollectionView:添加和移除数据
可以通过直接从Context添加和移除实体来完成,这些EntitySet的变化变化都会被CollectionViewSource跟踪到。
那么,这么做的意义到底是什么?目前为止我们还没看到这种方式与直接使用EntitySet的区别是吧?其实,ICollectionView的真正特别之处体现在可以添加过滤、排序和分组规则。
ICollectionView的过滤
ICollectionView定义了一个Predicate<object>类型的Filter属性,让我们略加修改我们的代码:
private ICollectionView _books;
public ICollectionView Books {
get {
if (this._books == null) {
this._books = CreateView(this.Context.Books);
this._books.Filter = new Predicate<object>(BookCorrespondsToFilter);
}
return this._books;
}
}
public bool BookCorrespondsToFilter(object obj) {
Book book = obj as Book;
if (filterActive) {
return book.Title.Contains("Silverlight");
}
return true;
}
BookCorrespondsToFilter方法执行时会检查每一个Book的Title属性是否包含“Silverlight”这个单词,如果不包含,则它不会被显示在View中。
当前代码提供的功能仅当你明确知道过滤时机时使用,然而大部分的应用程序具有要用户自己确定过滤时机的需求,那么我们再来进行一些改动:添加filterActive属性,当用户点击Add Filter时它被置为true。
public bool BookCorrespondsToFilter(object obj) {
Book book = obj as Book;
if (filterActive) {
return book.Title.Contains("Silverlight");
}
return true;
}
然当我们点击按钮的时候,我们会发现界面并没有发生任何变化,为什么呢?
当我们针对过滤条件做出改变或Book实体发生变化时(如更改它的书名),ICollectionView的实现不回自动再次执行过滤:Filter方法仅在将实体添加到EntitySet时执行。这意味你不得不明确的通知它使用新的过滤条件重新检查已经载入的实体,我们可以通过调用ICollection的Refresh()方法实现:
Refresh = new RelayCommand(() => {
Books.Refresh();
});
现在,View会被重新创建,这会让所有的Book实体被重新过滤。当然这仅当我们改变过滤条件或EntitySet发生改变时才是必要的。
ICollectionView的排序和分组
ICollectionView具有SortDescriptions和GroupDescription这两个有趣的属性,可以使用它们定义针对EntitySet的排序和分组规则。
排序操作可以通过点击绑定了ICollectionView的DataGrid列头实现,但当我们使用其他的一些诸如ListBox一类没有表头的控件时则需要通过代码的方式改变它们的排序规则:
AddSort = new RelayCommand(() => {
Books.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
});
对集合的分组操作类似:
AddGrouping = new RelayCommand(() => {
Books.GroupDescriptions.Add(new PropertyGroupDescription("Author"));
});
效果如图:
有一点要注意,一旦对集合进行分组操作,UI虚拟化将会被关闭-所以在操作大量数据时要谨慎一些。当对大量数据进行分组时一般要和分页相配合。
当我们要进行排序、过滤和分组操作时,ICollection是一个非常好的选择。然而它只能作用于内存中的数据,这意味着所有的数据都必须载入到客户端。这适合大部分的应用场景。而其他的情况我们可以通过DomainCollectionView解决。
DomainCollectionView
有很多的企业级应用中会有成千上万甚至百万千万级的数据要进行排序、过滤和分组。面对这类场景,ICollectionView就不再适用了,原因上文已经说明。我们需要一个允许服务端排序、过滤、分组以及更重要的分页操作的集合。
这就是DomainCollectionView的职责,你可以在WCF RIA Services Toolkit中的Microsoft.Windows.Data.DomainServices程序集中找到它(该程序集已经包含在示例代码中)。使用DomainCollectionView需要我们进行相比其他集合更多的设置,不过一旦你掌握了这些设置你会发现它们依然十分简单。DomainCollection初始化时需要Source和Loader(默认是CollectionViewLoader)属性。
public DomainCollectionView<Book> Books
{
get {
return this.view;
}
}
Source定义了用于View的源实体(任意的实现了IEnumerable的集合),典型的例子就是实现了INotifyCollectionChanged的集合。
this.source = new EntityList<Book>(Context.Books);
而Loader关注点在于数据的载入。当我们使用默认的CollectionViewLoader时需要同事传入两个回调:OnLoad和OnLoadCompleted,它们分别定义当数据必须被载入和载入操作完成时的发生的事件(当然如果你愿意的话,也可以用一个简单些的LoadOperation代替CollectionViewLoader)。
this.loader = new DomainCollectionViewLoader<Book>(
this.OnLoadBooks,
this.OnLoadBooksCompleted);
private LoadOperation<Book> OnLoadBooks()
{
return this.Context.Load(this.query.SortPageAndCount(this.view));
}
private void OnLoadBooksCompleted(LoadOperation<Book> op)
{
if (op.HasError)
{
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this.source.Source = op.Entities;
if (op.TotalEntityCount != -1)
{
 
补充:Web开发 , ASP.Net ,