2008-10-04

解決 ADO.NET Data Services 搭配 LINQ to SQL Classes 的問題

在 MSDN 或是網上看到的 ADO.NET Data Services 範例通常都是搭配 ADO.NET Entity Data Model 來展示, 比較少看到使用 LINQ to SQL Classes 的案例, 後來自己試了一下, 才發現原來是因為使用 LINQ to SQL Classes 會有一些小問題, 就是 Table 的 Key 必需要符合 {TableName}ID 的命名原則, 否則就會產生如下圖的伺服器例外, 但如果是使用 ADO.NET Entity Data Model 的話就不會有這樣的問題!

image

 

要解決這個問題是有方法的, 一是打開 .dbml 檔將類別的屬性名稱改成 TableNameID 的格式, 但是這樣好像有點笨, 僅適合用在剛開始開發的階段, 否則光測試就要人命了, 另一種解決方案比較優, 是利用 System.Data.Services.Common.DataServiceKeyAttribute 這個特徵項來標記每個 Table 相關的 partial lass, 如以下的範例:

[DataServiceKey("CustomerID")]
public partial class Customers { }

建議有類似問題的人可以自己新建一個 .cs 檔, 把它和 .dbml 放在一起, 並用類似的命名讓它們在 Solution Browser 裡可以很容易的就被看到, 像是 Northwind.DataServiceKey.cs 之類的, 然後在裡面統一管理所有的 Table classes, 替每個 Table class 加上 DataServiceKey 的特徵項, 這樣子未來才比較好管理! 如果有其他定義 partial method 的需要, 最好再另外建立一個 partial class 檔案來管理, 才不會影響到原來單純的 DataService 定義! 嗯~從這個例子剛好可以實際體會 partial class 在管理上所帶來的好處, 當然前提是命名方式和檔案存放的位罝要先自己規劃好才能享受到它所帶來的好處, 否則打散的類別反而更難了解.

附帶一提, 所有的 Table 都必需要有 key, 如果沒有的話 ADO.NET Data Services 就無法正常運作, 一般來說有經驗的 DBA 是比較不會建立一張沒有 key 的資料表的吧!

其實這個機械式的動作應該由 Visual Studio 幫開發人員處理掉才對, 而不是把這種冗長又容易出錯的工作留給開發人員去處理, 我想 Microsoft 應該也知道這點, 只是可能因為時程或是其他因素而沒有處理到吧!

反正實作上也不會太複雜, 如果有需要的話乾脆就自己寫一小段程式去產生相關的程式碼就可以了, 一時興起寫了兩個版本, 一個使用一般的寫法, 另一個使用 LINQ 的寫法, 很明顯的 LINQ 的語法自然又簡潔多了, 如下:

一般版:

private static void GenerateDataServiceKeyAttributePartialClasses(Assembly asm, StringBuilder sb)
{
    Type[] types = asm.GetTypes();
    foreach (Type t in types)
    {
        object[] attrs = t.GetCustomAttributes(typeof(TableAttribute), true);
        if (attrs.Length == 0) continue;

        List<string> keys = new List<string>();

        PropertyInfo[] pis = t.GetProperties();
        foreach (PropertyInfo pi in pis)
        {
            object[] propAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
            if (propAttrs.Length == 0) continue;

            if (((ColumnAttribute)propAttrs[0]).IsPrimaryKey)
            {
                keys.Add(pi.Name);
            }
        }
        sb.AppendFormat("\r\n[DataServiceKey(\"{0}\")]\r\n", String.Join(@""", """, keys.ToArray()));
        sb.AppendFormat("public partial class {0} {{ }}\r\n", t.Name);
    }
}

LINQ 版:

private static void GenerateDataServiceKeyAttributePartialClass(Assembly assembly, TextWriter writer)
{
    var tableInfoList = from t in assembly.GetTypes()
                        where t.GetCustomAttributes(typeof(TableAttribute), true).Length == 1
                        select new
                        {
                            TypeName = t.Name,
                            KeyFields = (from p in t.GetProperties()
                                         let columns = p.GetCustomAttributes(typeof(ColumnAttribute), true)
                                         where columns.Length == 1 && ((ColumnAttribute)columns[0]).IsPrimaryKey
                                         select p.Name).ToList()
                        };

    writer.Write("using System.Data.Services.Common;\r\n\r\n");
    foreach (var info in tableInfoList)
    {
        writer.Write("\r\n[DataServiceKey(\"{0}\")]\r\n", String.Join(@""", """, info.KeyFields.ToArray()));
        writer.Write("public partial class {0} {{ }}\r\n", info.TypeName);
    }
}

 

keywords: ado.net, data services