天天看點

由一個需求聊聊WCF(四)

綁定資料

在本文的需求中,在資料庫取出的資料并沒有固定的格式,這導緻不能使用固定的靜态結構傳輸服務資料。為了傳輸這樣的資料,需要設計一個類似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綁定動态類型的工作也完成了,整個需求全部完工。如果大家有什麼建議或指導,請指教,謝謝。

本文示例下載下傳位址:http://download.csdn.net/detail/wantalcs/4494183