绑定数据
在本文的需求中,在数据库取出的数据并没有固定的格式,这导致不能使用固定的静态结构传输服务数据。为了传输这样的数据,需要设计一个类似System.Data.DataTable这样的数据结构,如下:
[DataContract]
public class SimpleTable
{
[DataMember]
public string[] Columns
{ get; set; }
[DataMember]
public Row[] Rows
{ get; set; }
}
[DataContract]
public struct Row
{
[DataMember]
public RowItem[] Items
{ get; set; }
}
[DataContract]
public struct RowItem
{
[DataMember]
public string Name
{ get; set; }
[DataMember]
public string Content
{ get; set; }
}
SimpleTable.columns属性记录数据有多少列,然后每行的数据都放入SimpleTable.Rows中。这样任意形式的行列数据都可以处理了。
服务把数据传递给silverlight后,DataGrid可以绑定这些数据。但这里遇到了一个问题,DataGrid可以绑定静态类型的数据,而我们的数据是动态的。为了能让DataGrid使用这些数据,我想到过下面两个方法:
1.使用List<object>做数据源,数据源里放匿名对象:
List<object> list = new List<object>();
foreach (Row row in Rows)
{
list.Add(new { ColumnName1 = row.column1Value, ColumnName2 = row.column2Value });
}
实际做时就会发现这样根本行不通,这种做法要求ColumnName是可以直接写在代码中的简单名称,但我们的ColumnName是动态的。这时类似javascript的Eval机制或许能派上用场,但在.net的编译器作为服务(Complier as a Servie)功能推出以前,很好的实现这个机制并不容易。最重要的是,就算能做到也不应该这样去做,这样把原本问题复杂化是在钻牛角尖,会过度设计,误入歧途。
2.使用List<dynamic>做数据源,数据源里放ExpandoObject对象:
List<dynamic> list = new List<dynamic>();
foreach (Row row in Rows)
{
dynamic data = new ExpandoObject();
(data as ICollection<KeyValuePair<string, object>>).Add(new KeyValuePair<string, object>(row.columnName, row.columaValue));
list.Add(data);
}
这样倒是没有第1种方法那样问题,但依然失败了。DataGrid不识别dynamic类型的数据类型。
看来动态数据源是不行了,如果必须使用静态类型数据,剩下的路还有一条:静态数据也是可以用代码动态创建的。.net提供了“反射发出”机制,可以使用代码在程序中生成程序集、模块、类、方法等,这成了能解决需求的唯一救命稻草。
下面是具体做法:
public static IEnumerable GetDataGridSource(SimpleTable simpleTable)
{
//动态定义程序集、模块、类
AssemblyName assemblyName = new AssemblyName("DynamicGridViewModelAssembly");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicGridViewModelModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicGridViewModelType", TypeAttributes.Public);
//动态定义类的构造方法
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator constructorIL = constructorBuilder.GetILGenerator();
constructorIL.Emit(OpCodes.Ldarg_0);
constructorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
constructorIL.Emit(OpCodes.Ret);
//动态定义类的属性,每个属性对应数据集中的一列
foreach (string column in simpleTable.Columns)
{
FieldBuilder fieldBuidler = typeBuilder.DefineField("m_" + column, typeof(string), FieldAttributes.Private);
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(column, PropertyAttributes.HasDefault, typeof(string), null);
MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder methodGetBuilder = typeBuilder.DefineMethod("get_" + column, methodAttributes, typeof(string), Type.EmptyTypes);
ILGenerator methodGetIL = methodGetBuilder.GetILGenerator();
methodGetIL.Emit(OpCodes.Ldarg_0);
methodGetIL.Emit(OpCodes.Ldfld, fieldBuidler);
methodGetIL.Emit(OpCodes.Ret);
MethodBuilder methodSetBuilder = typeBuilder.DefineMethod("set_" + column, methodAttributes, null, new Type[] { typeof(string) });
ILGenerator methodSetIL = methodSetBuilder.GetILGenerator();
methodSetIL.Emit(OpCodes.Ldarg_0);
methodSetIL.Emit(OpCodes.Ldarg_1);
methodSetIL.Emit(OpCodes.Stfld, fieldBuidler);
methodSetIL.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(methodGetBuilder);
propertyBuilder.SetSetMethod(methodSetBuilder);
}
//动态创建类
Type modelType = typeBuilder.CreateType();
Type listType = typeof(List<>).MakeGenericType(modelType);
object list = Activator.CreateInstance(listType);
MethodInfo methodAdd = listType.GetMethod("Add");
//动态创建类的实例,每个实例对应数据集中的一行
foreach (Row row in simpleTable.Rows)
{
object model = Activator.CreateInstance(modelType);
foreach (RowItem item in row.Items)
{
PropertyInfo propertyInfo = modelType.GetProperty(item.Name);
propertyInfo.SetValue(model, item.Content, null);
}
methodAdd.Invoke(list, new object[] { model });
}
return (IEnumerable)list;
}
通过这个方法创建DataGrid的数据源,虽然类型是动态发出的,但它确实是静态的:List<DynamicGridViewModelType>,这样就可以绑定了:
client.GetTableCompleted += (s, a) =>
{
dataGrid1.ItemsSource = GetDataGridSource(a.Result);
};
client.GetTableAsync("ExampleTable");
其实这套解决方法也不直观简单,但我没能找到更靠的方案。
到此,silverlight的DataGrid绑定动态类型的工作也完成了,整个需求全部完工。如果大家有什么建议或指导,请指教,谢谢。