现在的位置: 首页 > 综合 > 正文

[Silverlight入门系列]EntityList/DomainCollectionView/DomainCollectionViewLoader

2012年11月10日 ⁄ 综合 ⁄ 共 7177字 ⁄ 字号 评论关闭

EntityList<T>/DomainCollectionView<T>/DomainCollectionViewLoader<T>都包含在Microsoft.Windows.Data.DomainServices这个dll里面,添加这个dll引用,就可以用这三个东西了。用这3个东西可以实现Server-Side Filtering, Paging, and Sorting(在ViewModel中通过调用WCF Ria Service实现服务器端分页、排序、过滤,而不要用Domain DataSource这个MVVM不友好的控件):

  • EntityList<T>  is an ObservableCollection backed by an EntitySet, its important to know that when an entity is removed from the list it also gets removed from associated EntitySet but it is not automatically added when a new entity is added to backed EntitySet.
  • DomainCollectionView<T> is a ICollectionView implementation, so it exposes everything you need to Page/Sort your data and accepts a CollectionViewLoader and a IEnumerable<T> as constructor parameters.
  • DomainCollectionViewLoader<T> is a CollectionViewLoader implementation that accepts the method to invoke to load data and another method to invoke when load operation completes.

具体背景:

最后实现的截图(过滤/搜索/分页/排序,都是服务器端按需加载):

2011-06-24 14h25_03

看看是不是服务器端分页,用Fiddler看看的确是返回当前页数据:

2011-06-24 14h26_34

Fiddler看看分页的http请求是这样的:

2011-06-24 14h27_32

Fiddler看看过滤/搜索的http请求是这样的:

2011-06-24 14h28_59

实现原理

看看源码实现,重点是WCF Ria Service的DomainService有个Count函数,正如我上文说的, 这个分页取得总数的函数对服务器分页来说是省不了的。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Threading;
namespace DomainCollectionViewSample.Web
{
    [EnableClientAccess()]
    public class SampleDomainService : DomainService
    {
        private const int UiWait = 1000;
        // Overridden to support paging over POCO entities.
        // If you're extending a derived DomainService
        // implementation this is already done for you.
        protected override int Count(IQueryable query)
        {
            return query.Count();
        }
        public IQueryable GetAllEntities()
        {
            return this.GetEntities(byte.MaxValue);
        }
        public IQueryable GetEntities(byte count)
        {
            this.WaitALittle();
            List entities = new List();
            for (byte i = 1; i < count; i++)
            {
                entities.Add(this.GetEntity(i));
            }
            return entities.AsQueryable();
        }
        public SampleEntity GetEntity(byte key)
        {
            SampleEntity entity = new SampleEntity();
            entity.Key = key;
            entity.Byte = key;
            entity.UInt16 = key;
            entity.Int32 = key;
            entity.Single = key;
            entity.String = SampleDomainService.GetEntityString(key);
            entity.Guid = Guid.Parse("00000000-0000-0000-0000-" + entity.String);
            return entity;
        }
        private void WaitALittle()
        {
            if (SampleDomainService.UiWait > 0)
            {
                Thread.Sleep(SampleDomainService.UiWait);
            }
        }
        /// 
        /// Returns a unique string for the specified .
        /// 
        /// 
        /// Returns a 12-character string of decending numbers. For instance,
        /// "000000054321" is returned for 5 and "111098765432" is returned for
        /// 11.
        /// 
        /// The key to get the string for
        /// A unique string
        private static string GetEntityString(byte key)
        {
            string str = string.Empty;
            while ((str.Length < 12) && (key > 0))
            {
                str += key--;
            }
            while (str.Length < 12)
            {
                str = "0" + str;
            }
            return str.Substring(0, 12);
        }
    }
    public class SampleEntity
    {
        [Key]
        public int Key { get; set; }
        public Byte Byte { get; set; }
        public UInt16 UInt16 { get; set; }
        public Int32 Int32 { get; set; }
        public Single Single { get; set; }
        public String String { get; set; }
        public Guid Guid { get; set; }
    }
}

 

ViewModel部分,用来给DataGrid和DataPager绑定的对象是DomainCollectionView<T>,这个视图由DomainCollectionViewLoader<T>来加载,排序和分页内部通过EntityQuery<T>.SortAndPageBy来实现,这个有一定的局限性。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ServiceModel.DomainServices.Client;
using System.Windows.Input;
using DomainCollectionViewSample.Web;
using Microsoft.Windows.Data.DomainServices;
namespace DomainCollectionViewSample
{
    public class SampleViewModel : INotifyPropertyChanged
    {
        private readonly SampleDomainContext _context = new SampleDomainContext();
        private readonly DomainCollectionView _view;
        private readonly DomainCollectionViewLoader _loader;
        private readonly EntityList _source;
        private readonly ICommand _searchCommand;
        private bool _canLoad = true;
        private string _searchText = string.Empty;
        public SampleViewModel()
        {
            this._source = new EntityList(this._context.SampleEntities);
            this._loader = new DomainCollectionViewLoader(
                this.LoadSampleEntities,
                this.OnLoadSampleEntitiesCompleted);
            this._view = new DomainCollectionView(this._loader, this._source);
            #region DesignerProperties.IsInDesignTool
            // Swap out the loader for design-time scenarios
            if (DesignerProperties.IsInDesignTool)
            {
                DesignTimeLoader loader = new DesignTimeLoader();
                this._view = new DomainCollectionView(loader, loader.Source);
            }
            #endregion
            this._searchCommand = new RelayCommand(this.OnSearch);
            // Go back to the first page when the sorting changes
            INotifyCollectionChanged notifyingSortDescriptions =
                (INotifyCollectionChanged)this.CollectionView.SortDescriptions;
            notifyingSortDescriptions.CollectionChanged +=
                (sender, e) => this._view.MoveToFirstPage();
            using (this.CollectionView.DeferRefresh())
            {
                this._view.PageSize = 10;
                this._view.MoveToFirstPage();
            }
        }
        #region View Properties
        public bool CanLoad
        {
            get 
            {
                return this._canLoad;
            }
            private set
            {
                if (this._canLoad != value)
                {
                    this._canLoad = value;
                    this.RaisePropertyChanged("CanLoad");
                }
            }
        }
        public ICollectionView CollectionView
        {
            get { return this._view; }
        }
        public string SearchText
        {
            get
            {
                return this._searchText;
            }
            set
            {
                if (this._searchText != value)
                {
                    this._searchText = value;
                    this.RaisePropertyChanged("SearchText");
                }
            }
        }
        public ICommand SearchCommand
        {
            get { return this._searchCommand; }
        }
        #endregion
        private LoadOperation LoadSampleEntities()
        {
            this.CanLoad = false;
            EntityQuery query = this._context.GetAllEntitiesQuery();
            if (!string.IsNullOrWhiteSpace(this.SearchText))
            {
                query = query.Where(e => e.String.Contains(this.SearchText));
            }
            return this._context.Load(query.SortAndPageBy(this._view));
        }
        private void OnLoadSampleEntitiesCompleted(LoadOperation op)
        {
            this.CanLoad = true;
            if (op.HasError)
            {
                // TODO: handle errors
                op.MarkErrorAsHandled();
            }
            else if (!op.IsCanceled)
            {
                this._source.Source = op.Entities;
                if (op.TotalEntityCount != -1)
                {
                    this._view.SetTotalItemCount(op.TotalEntityCount);
                }
            }
        }
        private void OnSearch()
        {
            // This makes sure we refresh even if we're already on the first page
            using (this._view.DeferRefresh())
            {
                // This will lead us to re-query for the total count
                this._view.SetTotalItemCount(-1);
                this._view.MoveToFirstPage();
            }
        }
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        protected void RaisePropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
        #endregion
        #region Sample data
        private class DesignTimeLoader : CollectionViewLoader
        {
            private static readonly IEnumerable source = new[]
                {
                    new SampleEntity { Guid = Guid.NewGuid() },
                    new SampleEntity { Guid = Guid.NewGuid() },
                    new SampleEntity { Guid = Guid.NewGuid() },
                };
            public IEnumerable Source { get { return DesignTimeLoader.source; } }
            public override bool CanLoad { get { return true; } }
            public override void Load(object userState)
            {
                this.OnLoadCompleted(new AsyncCompletedEventArgs(null, false, userState));
            }
        }
        #endregion
        #region RelayCommand
        // This is just a light version I wrote for this sample. In most cases you'll
        // want to use the RelayCommand type provided by an MVVM framework.
        private class RelayCommand : ICommand
        {
            private readonly Action _action;
            public RelayCommand(Action action)
            {
                this._action = action;
            }
            public event EventHandler CanExecuteChanged;
            public bool CanExecute(object parameter) { return true; }
            public void Execute(object parameter)
            {
                this._action();
            }
        }
        #endregion
    }
}

 

前台Xaml代码就不解释了,很简单,就是绑定到ViewModel,另外DataGrid和DataPager绑定对象是DomainCollectionView<T>。

全部源码下载,可以直接运行,如果却引用,看看我的这篇文章。

抱歉!评论已关闭.