我们都知道Silverlight端
不能直接使用DataTable,DataSet等数据存储。但某些情况下
实体类的传递方式根本无法满足我们的需求。 举个例子,在进行
查询建模时我们根本不知道
用户从哪张表选择字段,更有甚者我们还可能不知道
用户从哪个数据源选择表(这个问题我通过
smartDAL的特 性加以
支持)。这种情况下我们无法建立实体
类定义。这也是我使用C#,Java这些静态语言感觉最不爽的地方。动态语言中这种处理相当简单。
我在XCenter中的实现思想就是Server端(WCF实现端)将
smartDAL取出的DataTable分为数据和元数据两部分存储并通过WCF 端进行传递。在Silverlight端根据元数据信息动态
生成并编译出动态
实体类实例。并填充真实的待绑定集合。由于在Silverlight端动态生 成并编译
实体类的过程比较耗时所以在Silverlight端建立了缓存。并用元数据中的唯一Id进行标识。
元数据定义在XCenter.Framework.Public.DataSetData命名空间。主要结构就是3个类。
Field.cs 字段信息(部分定义)
Code
/// <sumMary>
/// 字段信息
/// </sumMary>
[DataContract]
public class Field:XCenter.Framework.Public.Core.ICloneable
{
public Field()
{
}
#region props
private String dataType;
private String caption;
private String fieldName;
// private String note;
private String expression;
// private int precision;
// private int scale;
private int maxLength;
private bool isrequire;
private bool isKey;
private bool isReadOnly;
/// <sumMary>
/// 数据类型
/// </sumMary>
[DataMember]
public String DataType
{
get { return dataType; }
set { dataType = value; }
}
/// <sumMary>
/// 显示名称
/// </sumMary>
[DataMember]
public String Caption
{
get { return caption; }
set { caption = value; }
}
MetaData.cs 元数据定义
Code
/// <sumMary>
/// 元数据信息
/// </sumMary>
[DataContract]
public class MetaData : XCenter.Framework.Public.Core.ICloneable
{
#region prop
// 字段列表
private List < Field > fieldsList = new List < Field > ();
// 名称-字段缓存
private Dictionary < String , Field > fieldCache = null ;
private bool specialChar = false ;
private object lockObject = new object ();
/// <sumMary>
/// 字段列表
/// </sumMary>
[DataMember]
public List < Field > Fields
{
get { return fieldsList; }
set { fieldsList = value; }
}
..
DataSetData.cs 数据集定义
@H_944_692@
Code
/// <sumMary>
/// 数据集用于前后台传递数据
/// </sumMary>
[DataContract]
public class DataSetData : XCenter.Framework.Public.Core.ICloneable
{
#region prop
private MetaData md;
private List < List < object >> datas = new List < List < object >> ();
/// <sumMary>
/// 元数据信息
/// </sumMary>
[DataMember]
public MetaData MetaData
{
get { return md; }
set { md = value; }
}
Field对应于DataTable中的列信息。
MetaData
主要就是对Field进行封装,并实现一些公共的操作
方法。DataSetData封装了
MetaData并用List<List<ob
ject>>存放具体数据。
这里要说明一下,由于从DataTable取出的实际数据都是基本类型(int,
String,byte[]...)等WCF中可以直接传递的数据。所以 这里用List<List<ob
ject>>传递数据不会有问题。如果List中有
用户定义类型,不在
serviceK
NownType中事先声明,那么Silverlight端反序列化时会报错。
以IQEOb
jectStorage这个WCF服务接口的GetData
方法可以看出具体的WCF声明:
[OperationContract]
DSDReturnDataVO GetData(QueryBaseVO qbd, serviceCo@R_197_10443@t co@R_197_10443@t);
这里的DSDReturnDataVO只是XCenter中为所有能返回DataSetData的服务定义的统一的返回值。读者可以直接返回 DataSetData。这个服务其实就是手工
sql设计的取数
方法。其内部处理逻辑是将
查询模型解析成
sql送到@L_
696_6@执行并取得DataTable组 装DataSetData。这里的具体逻辑我们暂时不关心。
下面看看SL端对传来的DataSetData的处理过程。我们可以看Quer
ymain.xam
l.cs的
service_GetDataCompleted
方法,这个
方法最初是由工具栏中的运行
查询按钮触发。
Code
void service_GetDataCompleted( object sender, GetDataCompletedEventArgs E)
{
if (e.Result.IsSucC)
{
dialogGrid.Children.Clear();
DataVisio dv = new DataVisio();
dv.onOK += new routedEventHandler(dv_OnOK);
DataSetData dsd = e.Result.ReturnValue;
List < object > result = dsd.ToDatasource();
dv.DataContainer.Itemssource = result;
dialogGrid.Children.Add(dv);
WindowHelper.HideLoading();
dv.Visibility = Visibility.Visible;
}
else
{
WindowHelper.HideLoading();
WindowHelper.Alert(e.Result.ErrormessagE);
}
}
可以看到主要的处理逻辑被封装在DataSetData的扩展
方法:ToData
source()中,这里
代码就不贴了,读者可以结合本贴看看里面的具体 实现步骤。
主要就是先在缓存中根据
MetaData的唯一标识查找动态
生成的
类实例,如果
没有找到那么就启动动态编译并把
生成的实例加入缓存以备下次再 用。要说一下的是这个缓存其实就是定义在App中的字典字段:
Code
public partial class App : Application,ISessionApp
{
#region Prop
private Dictionary < String , object > session =
new Dictionary < String , object > ();
private Dictionary < String , Type > tempClassDic =
new Dictionary < String , Type > ();
只不过通过Window
Helper进行了引用。Window
Helper.TempClass
Dic。
最后要提醒读者,到这里这个
解决方案并不完美,因为
我们没有考虑数据量的问题。当数据量很大时WCF传递数据会有问题。我们可以将DataSetData 中的List<List<ob
ject>>数据的形式改为Byte[]并进行压缩再传递(或者是JSON),这样的话SL端又多 了一道加压缩的过程。但个人认为这并不是
最完善的
解决方法。最终突破数据量的问题可能最好的还是分块传递。可能用到WCF双向通讯的技术。这方面有经验的 朋友可以
分享一下经验。