SilverLight之路(七)
接上回,数据表格搞定了,不过当数据量超大时,比如我这个项目,客户数将近100W,那么选择这种分页会死人的,传统作法,服务器端分页吧。
我们还是使用DataPager分布控件,想一下之前的实现,原理就是它与DataGrid绑定同一数据源,分页的操作其实也是在数据源上进行的,这里是PagedCollectionView。而如果我们要实现服务器端分页,它们就不能绑在一起啦,这里是从网上找到的一个实现方法,实现类似如下
先看服务器WCF端,加入客户实体类(前面有说过步骤,不再累述),然后定义操作契约
public List<DAL.WFT_Batch_CustomerClassification> GetCustomerListPager(CustomerClassificationFilter filter, out int totalCount)
{
using (var entities = new DAL.WFT_101104Entities())
{
int rowsCount = 0;
var query = entities.WFT_Batch_CustomerClassification;
if (filter.PageIndex <= 0)
rowsCount = query.Count();
totalCount = rowsCount;
query = query.OrderBy(t => t.GUID).Skip(filter.PageIndex * filter.PageSize).Take(filter.PageSize);
return query.ToList();
}
}
数据契约
[DataContract]
public class CustomerClassificationFilter
{
[DataMember]
public int PageIndex { set; get; }
[DataMember]
public int PageSize { set; get; }
}
这里同意提供该服务器端分页实现方法的网友的说法,建议把查询条件也进行实体类包装。目前我们的查询条件并未包含具体业务逻辑条件,只包括了分页大小与当前页索引。这里有一个问题,排序的时候没有指定排序字段,且每次都是固定排序,这个问题我们稍候再来解决,现在我们还是把重点放到分页上。
SL端实现,首先给DataPager增加一个扩展方法
public static class DataPageExtension
{
/// <summary>
/// BindSource扩展方法
/// </summary>
/// <param name="dataPager">分页控件</param>
/// <param name="totalCount">记录总数</param>
/// <param name="pageSize">分页大小</param>
public static void BindSource(this DataPager dataPager, int totalCount, int pageSize)
{
List<int> list = new List<int>(totalCount);
for (int i = 0; i < totalCount; i++) list.Add(i);
PagedCollectionView pcv = new PagedCollectionView(list);
pcv.PageSize = pageSize;
dataPager.Source = pcv;
}
}
这里本想给pcv指定一个记录总数,似乎没有,所以只能用一个循环来模拟数据源了。
数据绑定时
private void BindGrid(int pageIndex)
{
CustomerClassificationFilter ccf = new CustomerClassificationFilter();
ccf.PageIndex = pageIndex;
ccf.PageSize = this.dpCustomerList.PageSize;
client.GetCustomerListPagerAsync(ccf);
}
事件的回调可以放到初始化时去做,否则会多次注册事件,嗯,也可以叫做注册了多个事件响应方法。
client.GetCustomerListPagerCompleted += new EventHandler<WcfService.GetCustomerListPagerCompletedEventArgs>(
(s, ex) =>
{
PagedCollectionView pcv = new PagedCollectionView(ex.Result);
this.dgCustomerList.ItemsSource = pcv;
if (this.dpCustomerList.PageIndex <= 0)
{
this.dpCustomerList.PageIndexChanged -= dpCustomerList_PageIndexChanged;
this.dpCustomerList.BindSource(ex.totalCount, this.dpCustomerList.PageSize);
this.dpCustomerList.PageIndexChanged += dpCustomerList_PageIndexChanged;
}
});
注意分页事件的注册,如果不这样做,会进行两次绑定,因为在分页时也要进行数据查询的,而绑定时会自动触发分页事件,这种情况在做下拉列表默认选择项时也会存在,也可以考虑用这种办法。
这里还有一个要注意的地方,注意我们的WCF操作契约的实现,里面使用了一个Out关键字,大家都知道这个是什么意思吧。如果我们这里这样定义了,在wcf服务的代理类生成时,会把它作为result的参数传回到客户端的,如:ex.totalCount,并不需要我们在调用时指定,这与传统的使用方法有区别,所以要注意一下。调用时只传一个参数就可以了,如:client.GetCustomerListPagerAsync(ccf);
分页事件
void dpCustomerList_PageIndexChanged(object sender, EventArgs e)
{
BindGrid(this.dpCustomerList.PageIndex);
}
上面就是全部的实现代码,它的原理是使DataPager绑定一个虚拟的数据源,只是使用它的分页功能,具体的说就是页面大小、当前页索引与分页事件。还有一点,为了性能的考虑,原作者做了页索引为零时的判断,这样就只有在未获得结果之前进行结果总数的计算,这一点很重要,特别是数据量大的时候。
现在我们再来看看排序的功能,因为进行了服务器端分页,那么排序功能当然也要在服务器端来实现了,否则就只有在当前页(因为是服务器端分页,所以结果就只有当前页数据)进行了。
原理很好理解,把排序字段也传到服务器端就好办了,我们先在条件参数中把排序字段加上
[DataContract]
public class CustomerClassificationFilter
{
[DataMember]
public SortDescription Sorted { set; get; }
[DataMember]
public int PageIndex { set; get; }
[DataMember]
public int PageSize { set; get; }
}
在查询时加入排序的逻辑
public List<DAL.WFT_Batch_CustomerClassification> GetCustomerListPager(CustomerClassificationFilter filter, out int totalCount)
{
using (var entities = new DAL.WFT_101104Entities())
{
int rowsCount = 0;
var query = entities.WFT_Batch_CustomerClassification;
if (filter.PageIndex <= 0)
rowsCount = query.Count();
totalCount = rowsCount;
if (filter.Sorted != null)
query = SilverlightClassLibrary.DBHelper.DataSorting<DAL.WFT_Batch_CustomerClassification>(query, filter.Sorted).Skip(filter.PageIndex * filter.PageSize).Take(filter.PageSize);
else
query = query.OrderBy(t => t.GUID).Skip(filter.PageIndex * filter.PageSize).Take(filter.PageSize);
return query.ToList();
}
}
DataSorting方法我稍候介绍。
在SL端放一变量来存储上一次的排序字段,默认可以指定一个,如
//上一次的排序列
private SortDescription oldsort = new SortDescription("GUID", ListSortDirection.Ascending);
在调用时把它传到服务器端
private void BindGrid(int pageIndex)
{
CustomerClassificationFilter ccf = new CustomerClassificationFilter();
ccf.Sorted = oldsort;
ccf.PageIndex = pageIndex;
ccf.PageSize = this.dpCustomerList.PageSize;
client.GetCustomerListPagerAsync(ccf);
}
下面我们要做的就是如何取得这个字段了,那么,如何得到这个值呢?其实排序也与分页类似,其实也是在数据源上进行的,我们的数据源就是PagedCollectionView(注意这个与DataPager的Source可不是同一个,这个是真实的数据源)。那么我们通过它的SortDescriptions属性就可以得到了,而默认它是支持多列排序的(按shift实现),因此我们得到的是一个集合,而恰好它实现了INotifyCollectionChanged接口,我们这里就是要利用这个接口的CollectionChanged事件,代码如下:
System.Collections.Specialized.INotifyCollectionChanged scn = pcv.SortDescriptions;
scn.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
(scnS, scnE) =>
{
if (scnE.Action == NotifyCollectionChangedAction.Remove && scnE.NewStartingIndex == -1)
{
return;
}
if (scnE.Action == NotifyCollectionChangedAction.Reset)
return;
pcv.SortDescriptions.Clear();
oldsort = (SortDescription)scnE.NewItems[0];
BindGrid(this.dpCustomerList.PageIndex);
});
这个方法也是花费了我好久才在网上找到的,现在再感叹一下吧!!!!不过这里只实现了单列排序,可以通过DataGrid的属性来控制是否使用多列排序功能,如果想实现多列排序的话,就要再进行扩展了,目前我没有用到这个功能。
回过头再看看服务器端动态排序的实现,这个与sl与wcf无关了,纯属linq的应用了,按我们对Linq的理解,我们会写出类似这样的DataSorting方法的实现
switch (filter.Sorted.PropertyName)
{
case "姓名":
if (filter.Sorted.Direction == ListSortDirection.Ascending)
query = query.OrderBy(t=>t.客户姓名);
else
query = query.OrderByDescending(t=>t.客户姓名);
break;
case "年龄":
if (filter.Sorted.Direction == ListSortDirection.Ascending)
query = query.OrderBy(t => t.年龄);
else
query = query.OrderByDescending(t => t.年龄);
break;
}
这肯定不行啊,继续百度吧,于是我找到了这个解决办法。
Linq初体验——Order By 通过属性名动态排序
http://www.cnblogs.com/xxfss2/archive/2010/12/13/1905023.html
稍作修改,实现主要代码如
public static IQueryable<T> DataSorting<T>(IQueryable<T> source, SortDescription sort)
{
string sortExpression = sort.PropertyName;
string sortingDir = string.Empty;
if (sort.Direction == ListSortDirection.Ascending)
sortingDir = "OrderBy";
else if (sort.Direction == ListSortDirection.Descending)
sortingDir = "OrderByDescending";
ParameterExpression param = System.Linq.Expressions.Expression.Parameter(typeof(T), sortExpression);
PropertyInfo pi = typeof(T).GetProperty(sortExpression);
Type[] types = new Type[2];
types[0] = typeof(T);
types[1] = pi.PropertyType;
System.Linq.Expressions.Expression expr = System.Linq.Expressions.Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, System.Linq.Expressions.Expression.Lambda(System.Linq.Expressions.Expression.Property(param, sortExpression), param));
IQueryable<T> query = source.AsQueryable().Provider.CreateQuery<T>(expr);
return query;
}
至此,这个列表功能主体就基本完成了。