Dynamic LINQ 讓 LINQ 的世界變的更美好

身為一個程式設計師最討厭的就是被侷限在框框裡寫程式,我們唯一的限制應該是在我們的創意而非架構。像 LINQ 剛出來的時候我覺得超級好用,不過等用在專案上實做的時候才發現綁手綁腳的,想要動態組成一個 LINQ 語法難上加難,結果過沒多久就在 ScottGu's Blog 看到一篇 Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library),看到的時候好像挖到寶一樣,以前在開發時卡住的問題全部都解決了,從今年 2 月份用到現在簡直是幾乎忘了它的存在,以致於這幾個月都沒有提及 Dynamic LINQ 這個好東西,直接上週一個朋友問我:『LINQ 的 Where 條件可以像以前組 SQL Command 一樣動態組裝嗎?』,我馬上跟他說有 Dynamic LINQ 這個東西,不用多說,他跟我剛看到 Dynamic LINQ 的時候一樣覺得:Bravo! ( 太棒了 )

這個 Dynamic LINQ 其實也跟我曾經介紹過的 好用的 Linq Samples and the Sample Query Explorer 一樣,早就出現在 VS2008 的 CSharpSample 裡面了,有需要的人可以到這裡下載,下載回來後這個 Library 放在 \LinqSamples\DynamicQuery\ 專案裡,專案裡面有個 Dynamic.cs 是一堆 Extension Methods,用來擴充 LINQ 的現有功能,程式碼有點複雜,想專精 LINQ 的人可以好好研究一下這些原始碼,共有 2,027 行。如果沒空看也沒關係,會用就很夠了,你可以看專案內的 Program.cs 提供一些簡單的範例,不用多說,看個範例你就知道他在做什麼了:

Northwind db = new Northwind(connString); 
db.Log = Console.Out;

var query =
    db.Customers.Where("City == @0 and Orders.Count >= @1", "London", 10).
    OrderBy("CompanyName").
    Select("New(CompanyName as Name, Phone)");

Console.WriteLine(query);
Console.ReadLine();

我介紹到這裡就好,大家如果有感覺的話,可以到 Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)动态LINQ (第一部分:使用LINQ动态查询库) 查看更完整的範例,或者可以用搜尋引擎查詢 Dynamic LINQ 也可以找到一堆資料。

不過,使用 Dynamic LINQ 有個缺點,就是這種寫法就會失去編譯時期偵錯(Compile-time Debugging)的機會,如果你自己組成的 LINQ 語法有問題的話,就必須要到執行時期(Runtime)才有可能找到錯誤了,這時你就必須要做好錯誤處理(Error Handling)才行!

  

此文章由 will 發表於 2008/6/23 下午 05:17:02

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: .Net | C# | LINQ

標籤: ,

收藏:

善用 LINQ to SQL 中的 partial class 機制進行資料格式驗證

我們通常會使用 DetailsView 或 FormView 控制項進行資料的新增、編輯、刪除等動作,而通常我們會在裡面放置許多 Validator 控制項以驗證資料格式是否正確,不過當進行較大的專案時,開發人數通常會比較多,而且人員之間的分工也會比較明確,有的人做 SA、有的人做 DBA、有的人做 ASP.NET UI Process 開發...等等,在分層負責的情況下若能夠有效分割工作,不但專案品質會比較高,大家在寫程式的過程中也會比較專注。

就拿驗證輸入資料的部分來討論,SA/SD 在傳遞專案規格給開發人員時,總是難免有東西沒講清楚,而導致某些資料輸入的驗證沒有做好;或者是專案需求不斷變更,而導致可能有些輸入驗證的規則遺漏了更新程式碼,若能從「資料的角度」出來來驗證輸入的資料,而非「操作介面的角度」來驗證資料,應該是比較直覺且有效的方式,而 LINQ to SQL 正好就實做出了這一點。

當我們用 LINQ to SQL Designer 設計完資料物件模型,預設就有定義一些你可以自行實做的 partial method,例如說之前文章提到過的 OnCreated() 方法,可用於資料物件(Entity)產生時給予資料物件的預設值。今天要講的是另一個 OnValidate() 方法,可用於當 LINQ to SQL 在執行 Insert, Update 或 Delete 等動作之前,對資料物件進行資料驗證的動作。

底下是一個簡單的驗證範例:

public partial class 產品資料
{
    partial void OnValidate(System.Data.Linq.ChangeAction action)
    {
        if (action == System.Data.Linq.ChangeAction.Insert 
            || action == System.Data.Linq.ChangeAction.Update)
        {
            if (!this._繳費方式.HasValue)
            {
                throw new Exception("您沒有輸入「繳費方式」無法儲存產品資料");
            }

            if (this.產品類別.名稱 == "電子商務類")
            {
                if (this.產品售價 < 2000)
                {
                    throw new Exception("電子商務類產品不得設定低於 2000 元。");
                }
            }
        }
    }
}

上面這段程式的「白話文」就是,當使用者要新增資料時或更新資料時,會先檢查「繳費方式」有沒有輸入資料,若沒輸入資料則丟出一個例外事件(Exception)。若「產品資料」的「產品類別」定義為「電子商務類」,當產品售價低於 2000 時則丟出一個例外事件(Exception)。

你可以看到我對產品類別的判斷條件跟「資料格式(Data Format)」是無關的,而是跟「商業邏輯(Business Logic)」有關係,在實務上資料格式異動的機會不太大(總是有例外的),但是商業邏輯就很有可能經常改變,將商業邏輯的判斷寫在這裡會比寫在每一支 ASP.NET 頁面程式裡來的清楚明瞭而直覺,不過這並不代表在 ASP.NET 頁面中就不需額外做出判斷,要做好一個完美的使用者介面,需要考量的細節是很多的,況且客戶的需求總是讓你料想不到,只是你可以把 OnValidate() 方法視為從 ASP.NET 輸入資料到 SQL Server 中的最後一道防線。

當然,你也可以把「最後一道防線」設定在資料庫系統中,例如說撰寫觸發程序(Trigger)或預儲程序(Stored Procedure),但這沒有一定的規則,全看你怎麼規劃整個系統開發的架構,而我個人是偏好寫 C# 大於 T-SQL 啦。^_^

  

此文章由 will 發表於 2008/6/17 上午 01:00:08

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | LINQ

標籤: ,

收藏:

解決 LINQ to SQL 針對更新大型物件的效率極差的問題

我有一個頁面是使用 DetailsView 進行資料的新增與更新動作,其中有個圖片欄位在資料庫中的資料格式為 VarBinary(MAX),直接用來儲存圖檔的二進位內容,在新增資料的時候上傳檔案並存入資料庫中,在更新的時候直更新該欄位的內容,不過在更新的時候執行的時間都非常久,即便是上傳 10KB 左右的圖檔,在執行到 db.SubmitChanges(); 時都會執行個 30 秒以上,如果上傳幾 MB 的檔案就要執行好幾分鐘,所以常常會發生 Timeout 的情況。

這個問題大約困擾我將近一個月的問題,因為實在已經嘗試了太多作法(都是用 LINQ to SQL 的方式)都無法改善效能問題,直到今天用 MSN 問一個朋友這個狀況的時候,問題才剛問完自己就突然靈機一動想到了一個解決方法,也就是嘗試將該 VarBinary(MAX) 欄位設定 UpdateCheck="Never" 看看,結果就真的完全解決效能問題了。

有時候真的很奇怪,自己想不通的問題在問別人時通常會將問題重新思考過一遍,進而更了解問題本身。所以人家才會常說:「當你會問題的時候,問題就已經先解決一半」,我非常的感同身受。

首先,先開啟你的 *.dbml 檔,找出這個欄位:

<Column Name="ContentData" Type="System.Data.Linq.Binary" DbType="VarBinary(MAX) NOT NULL" 
        CanBeNull="false" />

然後將該欄位加上 UpdateCheck="Never" 屬性,然後儲存。

<Column Name="ContentData" Type="System.Data.Linq.Binary" DbType="VarBinary(MAX) NOT NULL" 
        CanBeNull="false" UpdateCheck="Never" />

加上這個屬性之後,LINQ to SQL 對這個表格就會採用「閉式並行存取控制(Pessimistic Concurrency Control)」的方式進行更新作業,也就是說當 LINQ to SQL 在更新 Binary 格式的欄位時就不會在去判斷檔案的內容是否有異動,直接將更新的檔案內容直些寫入資料庫。

相關連結

  

此文章由 will 發表於 2008/6/2 下午 02:03:45

永久連結 | 評論 (1) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | C# | LINQ

標籤: , ,

收藏:

解決 LINQ to SQL 資料庫更新衝突的情形

我前陣子遇到一個偶發的錯誤狀況,就是我在我某個頁面中需要計算文件下載的次數,因此需要每次進入頁面時都要讓該筆資料的 num 欄位的值自動加 1,也就是每次都要更新資料庫,但是每過幾天就有可能收到幾個 System.Data.Linq.ChangeConflictException 例外狀況,錯誤訊息如下:

中文版

System.Data.Linq.ChangeConflictException: 資料列找不到,或者已變更。
英文版
System.Data.Linq.ChangeConflictException: Row not found or changed.

這原因就出在當我用 LINQ to SQL 將資料取出之後,一直到寫回資料庫的過程中,資料庫中的該筆資料發生了變更,而導致衝突狀況,我的程式碼如下:

MyTable m = (from p in db.MyTable
             where p.ID.CompareTo(id) == 0
             select p).FirstOrDefault();
if (m != null)
{
    m.num = m.num + 1;
    db.SubmitChanges();
}

雖然這段程式從取出之後就立即將更新的值送回資料庫更新,不過當網站流量大的時候這種資料更新的衝突現象似乎無法避免,我這幾天研究出 3 種可能的解決方案:

第一種:直接對資料庫下 SQL 指令(不使用 LINQ 的標準更新方式)

db.ExecuteCommand("UPDATE [dbo].[MyTable] SET num=num+1 WHERE ID = @p0", m.ID);

這應該是最簡單直覺的作法了,也不會有衝突的狀況發生,如果你只是要做簡單的「計數器」功能,建議用這一招就好了,否則請看第二種方法。

第二種:使用 LINQ to SQL 變更衝突的處理方法

在 MSDN 的 HOW TO:管理變更衝突 (LINQ to SQL) 文章有列出一些關於此主題的說明,建議要寫 LINQ to SQL 的開發人員務必熟讀此章節。

除了以上這些文章外,應該也要看看 開放式並行存取概觀 (LINQ to SQL),如果覺得中文看不懂也可以看看英文版的 Optimistic Concurrency Overview (LINQ to SQL),因為我在看文章時有些翻譯說實在還看不太習慣。

底下是解決衝突問題的範例程式(參考  ObjectChangeConflict.Resolve 方法 (RefreshMode) 說明)

try
{
    db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
}
catch (System.Data.Linq.ChangeConflictException ex)
{
    foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)
    {
        // *********************************************
        // 底下三個範例是 3 選 1 喔,不要三行都寫在一起!
        // **********************************************

        // 採用資料庫的查詢出來的值,目前物件的值將會被資料庫最新查到的複寫
        occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
        
        // 採用目前物件中的值,並更新資料庫中的版本
        occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
        
        // 僅更新此物件中變更的欄位,僅將變更的欄位寫入資料庫(或稱為合併更新)
        occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
    }

    // 注意:解決完衝突之後要記得重新再 SubmitChanges() 一次,否則一樣不會更新資料庫
    db.SubmitChanges();
}

我在驗證變更衝突的測試程式的完整原始碼如下:

db = new NEXCOMDataContext();

MyTable m = (from p in db.MyTable
             where p.ID.CompareTo(MyTableID) == 0
             select p).FirstOrDefault();

if (m != null)
{
    // 刻意引發變更衝突
    db.ExecuteCommand(@"UPDATE [dbo].[MyTable] SET num = num - 1 WHERE ID={0}", MyTableID);

    m.num = m.num + 1;

    try
    {
        db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
    }
    catch (System.Data.Linq.ChangeConflictException ex)
    {
        Response.Write(String.Format("<xmp>ChangeConflictException = {0}</xmp>", ex.Message));
        
        foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)
        {
            // 採用目前物件中的值,並更新資料庫中的版本
            //occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);

            // 採用資料庫的查詢出來的值,目前物件的值將會被資料庫最新查到的複寫
            //occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
           
            // 僅更新此物件中變更的欄位,僅將變更的欄位寫入資料庫(合併)
            occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
        }
        // 注意:解決完衝突之後要記得重新再 SubmitChanges() 一次,否則一樣不會更新資料庫
        db.SubmitChanges();
    }
}

Response.End();

變更衝突是開發資料庫應用經常會發生的問題,觀念務必要清楚明瞭,下次遇到問題的時候才能快速反應出最正確的解決方案。

第三種:採用封「閉式並行存取控制(Pessimistic Concurrency Control)」,或也有人稱為「悲觀同步存取控制」

只要在 LINQ to SQL Designer 中將特定的欄位的 UpdateCheck 屬性設定為 Never,就可以避免在更新資料時發生變更衝突。只不過當衝突發生的時候,資料庫中新的值可能會被目前物件的值給蓋過去,數字會有點不精確就是了。

    在 LINQ to SQL Designer 中將特定的欄位的 UpdateCheck 屬性設定為 Never

相關連結

  

此文章由 will 發表於 2008/5/24 下午 06:52:05

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | C# | LINQ

標籤: , ,

收藏:

設計 LINQ to SQL Class 的注意事項 (2)

延續之前寫的設計 LINQ to SQL Class 的注意事項,最近又有一些小心得可以分享。

  1. 避免從 Server Explorer 拖曳 Table 進 LINQ to SQL Designer 時在 Web.config 中多產生一組新的 ConnectionString

    我們常會從 Server Explorer 直接拖曳表格進 LINQ to SQL Designer,尤其是新增表格的時候,第一次用的時候他其實會自動幫我們建立一筆 ConnectionString 到 web.config 中,預設的名稱是 "資料庫名稱ConnectionString"。

    從 Server Explorer 拖曳 Table 進 LINQ to SQL Designer  

    不過如果你如果日後有將該 ConnectionString 改變了之後,下次再從 Server Explorer 拖曳 Table 進 LINQ to SQL Designer 時,Web.config 就會又多新增一筆 ConnectionString!

    要解決這個問題的方法就是重新設定 Server Explorer 中的連線,且 Server Explorer 中的連線的所有參數設定必須跟你在 Web.config 中的設定完全一模一樣才行,包括 Data Source, Initial Catalog, User ID, Password 或其他相關參數都要一樣才行。

    重新設定的步驟如下:

    刪除現有的連線 

    新增連線 
     
    設定連線參數必須跟你在 Web.config 中的設定完全一模一樣才行

    設定完成之後下次再拖曳表格進 LINQ to SQL Designer 時就不會再莫名其妙的新增連線參數了!
  2. 之前提及的「在設計 LINQ to SQL Class 的時候,有預設資料的欄位要特別設定 Auto Generated Value 為 True,否則會新增失敗」可以改由 DBML 的 Partial Class 解決。

    我在 SQL 2000 的 Uniqueidentifier 欄位在 LINQ to SQL 的問題 有提到這個技巧,就是在 OnCreated() 事件中加入一些原本要設定預設值(Default Value)的欄位,這樣你就不用每次重新拖曳表格時又要全部手動修改一次 Auto Generated Value 為 True 了,算是一勞用逸的方法。
  

此文章由 will 發表於 2008/5/22 下午 11:56:13

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: LINQ | Visual Studio

標籤: , ,

收藏:

好用的 Json.NET 2.0 已發佈

JSON ( Javascript Object Notation ) 是一種很方便的資料格式,常用於 AJAX 的相關應用中,主要是可以將 JavaScript 的物件資料變成一種字串的格式,以方便網路傳輸,也是序列化的一種方式。

當然在 .NET 這個領域也少不了他,我之前就用過 Json.NET 1.3 版,真的是還蠻方便的,但因為當時文件不多,也只有提供一些範例程式而已,所以應用的範圍並不廣,不過還是幫我省去了許多 ASP.NET 與 JavaScript 之間交換資料的困擾。

新版的 Json.NET 2.0 除了提供相當完整的線上文件之外,還提供了 LINQ to JSON 的 LINQ Provider 可以更方便的存取 JSON 物件,今後將可比以往用更輕鬆的方式用 .NET 撰寫 JSON 相關的程式了,新版的 Json.NET 2.0 大概有以下特色:

  1. 支援 LINQ to JSON
  2. 快速的 JsonReader 與 JsonWriter 類別
  3. 可透過 JsonSerializer 輕易且快速的轉換你現有的 .NET 物件為 JSON 格式(也可從 JSON 格式轉回 .NET 物件)
  4. Json.NET 也可幫你將 JSON 字串格式化成有縮排的格式,用以方便除錯與檢視
  5. 可設定 JsonIgnore 與 JsonProperty 屬性(Attribute)到你的類別中,用以宣告物件要如何序列化
  6. 能夠將 JSON 轉成 XML 格式,也可將 XML 轉成 JSON 格式

如果要下載 Json.NET 2.0 可以到 Json.NET CodePlex Project 網站下載。

相關連結

  

此文章由 will 發表於 2008/5/19 下午 11:30:21

永久連結 | 評論 (2) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | C# | JavaScript | LINQ | Web

標籤: , , , , , ,

收藏:

在撰寫 LINQ to SQL 時應注意的幾個小地方

這陣子的專案幾乎每個都會用到 LINQ to SQL 技術,但我發現有些人在撰寫程式碼的時候有些不太好的習慣,會對資料庫進行一些多餘的查詢動作或建立多餘的 DataContext,以下是我最近觀察到的幾種狀況與建議的寫法:

1. 一個頁面中只需要共用一個 DataContext

錯誤的程式碼

var q = from p in SQLHelper.GetDataContext().News
        select p;

建議的程式碼

MyDataContext db = SQLHelper.GetDataContext();

var q = from p in db.News
        select p;

說明:所有 LINQ to SQL 語法都共用 db 變數即可,不需要每次執行 LINQ to SQL 都產生一個新的 DataContext 浪費資源。

2. 取出結果的第一筆資料不要重複執行 q.First() 方法

錯誤的程式碼

var q = from p in db.News
        where p.ID.CompareTo(new Guid(Request.QueryString["id"])) == 0
        select p;
if(q.First().Title.Contains(strKeyword) || 
   q.First().Content.Contains(strKeyword)) {
    return true;
}

建議的程式碼

var q = from p in db.News
        where p.ID.CompareTo(new Guid(Request.QueryString["id"])) == 0
        select p;
News n = q.First();
if(n.Title.Contains(strKeyword) || n.Content.Contains(strKeyword)) {
    return true;
}

說明:你每次執行 q.First() 他都會進資料庫做一次資料查詢,你判斷五次就會執行五次,是很沒效率的作法。直接呼叫 q.First() 也有風險,請看第 3 點的說明。

3. 若要取得單筆資料,要判斷是否有從資料庫中取到資料時不要用 q.Count() 方法

錯誤的程式碼

var q = from p in db.News
        where p.ID.CompareTo(new Guid(Request.QueryString["id"])) == 0
        select p;

if (q.Count() == 1)
{
    m = q.First();
}
else
{   
    Response.Redirect("/", true);
}

建議的程式碼

var q = from p in db.News
        where p.ID.CompareTo(new Guid(Request.QueryString["id"])) == 0
        select p;

News n = q.FirstOrDefault(); // 如果用 q.First() 在沒資料時會發生 Exception

if (n == null)
{
    Response.Redirect("/", true);
    return;
}

說明:你每執行一次 q.Count() 方法,程式都會進資料庫執行一遍 SELECT COUNT(*) 的動作,如果你只需要取出一筆資料的話,這個動作其實是多餘的。如果你用的是 q.First() 來取得第一筆資料的話,當資料庫沒資料時是會發生 Exception 的!

4. 不要在 LINQ 語法中轉型(Casting)或執行太多的 .NET 方法

錯誤的程式碼

var q = from p in db.News
        where p.ID.CompareTo(new Guid(Request.QueryString["id"])) == 0
        select p;

建議的程式碼

try {
    Guid id = new Guid(Request.QueryString["id"]);
} catch {
    Response.Redirect("/", true);
    return;
}
var q = from p in db.News
        where p.ID.CompareTo(id) == 0
        select p;

說明:以本範例為例,如果你傳入的 id 不是有效的 Guid 字串,就會發生例外事件。這個例外事件會在 LINQ to SQL 在執行的過程中發生失敗 (Inner Exception),他會給你類似這樣的錯誤訊息:

System.Web.HttpUnhandledException: Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: g
   at System.Data.Linq.SqlClient.QueryConverter.VisitInvocation(InvocationExpression invoke)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.VisitExpression(Expression exp)
   at System.Data.Linq.SqlClient.QueryConverter.VisitBinary(BinaryExpression b)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.VisitExpression(Expression exp)
   at System.Data.Linq.SqlClient.QueryConverter.VisitBinary(BinaryExpression b)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.VisitExpression(Expression exp)
   at System.Data.Linq.SqlClient.QueryConverter.VisitWhere(Expression sequence, LambdaExpression predicate)
   at System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.VisitAggregate(Expression sequence, LambdaExpression lambda, SqlNodeType aggType, Type returnType)
   at System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
   at System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
   at System.Data.Linq.SqlClient.QueryConverter.ConvertOuter(Expression node)
   at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
   at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
   at System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
   at System.Linq.Queryable.Count[TSource](IQueryable`1 source)
   at ASP.masterpage_master.Page_Load(Object sender, EventArgs e) in c:\XXX\AAA\BBB\MasterPage.master:line 20
   at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
   at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
   at System.Web.UI.Control.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   --- End of inner exception stack trace ---
   at System.Web.UI.Page.HandleError(Exception e)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest()
   at System.Web.UI.Page.ProcessRequest(HttpContext context)
   at ASP.productmodel_aspx.ProcessRequest(HttpContext context) in c:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files\root\52723026\465b3099\App_Web_xglot_f2.3.cs:line 0
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

這樣的錯誤訊息將會讓你比較不容易除錯。

5. 使用具型別的語法時發生無法 Compile 的情形

例如說你有個 LINQ 語法如下:

var q = from p in db.News
        select new { p.ID, p.Title, p.Summary };

var myList = q.ToList();

上面這斷的 LINQ 語法選出來的資料在執行 ToList() 方法之後變成了一組「匿名型別(Anonymous Type)集合」,但是匿名型別是無法當成參數傳遞的,也無法序列化(Serialization),也就是說你沒辦法把這些資料存在 ViewState、Session 或 Cache 物件裡!

通常這種情況我們會自訂一個類別 ,用來儲存透過 LINQ to SQL 所 select 出來的欄位,例如說以下程式:

public class MyNews
{
    public Guid ID;
    public string Title;
    public string Summary;
}

而你原本的 LINQ 語法要改成這樣:

var q = from p in db.News
        select new MyNews { p.ID, p.Title, p.Summary };

如果你這樣寫的話,那你就錯了!因為這樣的程式碼在編譯的時候會出現以下錯誤訊息:

Cannot initialize type 'MyNews' with a collection initializer because it does not implement 'System.Collections.IEnumerable'

當你看著這個錯誤訊息,你可能會想趕快在 MyNews 類別上實做 System.Collections.IEnumerable 介面,但是問題根本不在這裡!

你必須「明確指定」該類別的欄位名稱(Field Name)才可以正常編譯,如下程式範例:

var q = from p in db.News
        select new MyNews { ID=p.ID, Title=p.Title, Summary=p.Summary };

有了明確的型別,我們透過 ToList() 方法取得的資料就可以當成參數傳遞了,也可以將取得的資料 Cache 起來,雖然是很小的地方,但第一次遇到的人可能會弄很久才解決!

以上這 5 點是我最近發現的小狀況,如果日後有發現新的狀況我還會補充上來。

  

此文章由 will 發表於 2008/5/16 上午 09:00:26

永久連結 | 評論 (4) | 此文章的RSSRSS comment feed |

分類: ASP.NET | C# | LINQ

標籤: , , , ,

收藏:

好用的 Linq Samples and the Sample Query Explorer

上次在微軟 Windows Server 2008 與 Visual Studio 2008聯合上市發表會看到有講師執行一個 LINQ Project Sample Query Explorer,覺得很好用,因為他把所有 LINQ 的範例放在程式裡,點選之後就會出現語法,還可以執行,真是學習 LINQ 的絕佳教材。

LINQ Project Sample Query Explorer

不過我找了好久才找到,原來這個程式是附在 Visual Studio 2008 裡面的,你只要到 C:\Program Files\Microsoft Visual Studio 9.0\Samples\1033 目錄下就可以看到  CSharpSamples.zip 這個檔案,解壓縮之後進入 LinqSamples 目錄開啟 LinqSamples.sln 方案,再按下 F5 就可以執行了。

而這個 CSharpSample 範例也是一個 MSDN Code Gallery 的專案,專案的網站在此:http://code.msdn.microsoft.com/csharpsamples,你可以下載最新版的範例後,將下載的原始碼將原本的解壓縮的目錄覆蓋過去即可。

不過,在範例 XLinq55 中的程式有 Bug,執行的結果都查不到任何資料!Output 都會是 <CustomerOrders />

請開啟 LinqSamples\SampleQueries\LinqToXmlSamples.cs 檔案,並找到 XLinq55() 方法(應該在第1056行),整個換成以下的程式碼即可:

[Category("Grouping")]
[Title("Group orders by customer")]
[Description("Group orders by customer and return all customers (+ orders) for customers who have more than 25 orders ")]
public void XLinq55() {
    XDocument customers = XDocument.Load(dataPath + "nw_customers.xml");
    XDocument orders = XDocument.Load(dataPath + "nw_orders.xml");
    XElement custOrder = new XElement("CustomerOrders",
        from
            order in orders.Descendants("Orders")
        group order by
            order.Element("CustomerID").Value into cust_orders
        where
            cust_orders.Count() > 25
        select
            new XElement("Customer",
                new XAttribute("CustomerID", cust_orders.Key),
                from
                    customer in customers.Descendants("Customers")
                where
                    (string)customer.Attribute("CustomerID") == (string)cust_orders.Key
                select
                    customer.Nodes(),
                cust_orders));

    Console.WriteLine(custOrder);
}

相關連結

  

此文章由 will 發表於 2008/5/5 下午 11:50:55

永久連結 | 評論 (2) | 此文章的RSSRSS comment feed |

分類: .Net | C# | LINQ

標籤: , ,

收藏:

SQL 2000 的 Uniqueidentifier 欄位在 LINQ to SQL 的問題

我有個專案原本都在 SQL Server 2005 上面開發的,到最後才將資料庫轉換到 SQL Server 2000 上,結果在執行 Insert 的時候卻發生 "The primary key column of type 'UniqueIdentifier' cannot be generated by the server. " 錯誤訊息,如下圖:

The primary key column of type 'UniqueIdentifier' cannot be generated by the server.

不過我在「建立時間」欄位上設定 Auto Generated Value 為 True 是可以正常運作的!

看這個樣子應該是 LINQ to SQL 在 SQL Server 2000 資料庫中的 Primary Key 不支援 Auto Generated Value,也就是說我的 LINQ to SQL Class 裡面每一個表格的 Primary Key 定義都必須將 Auto Generated Value 設定為 False ( 也是預設的狀態 ),但為了減少現有程式的修改量,我在每一個表格加上一個 OnCreated() Partial Method,並在這裡指定預設的 Guid 就解決所有問題了。

public partial class Role
{
    partial void OnCreated()
    {
        if (this.ID.CompareTo(new Guid()) == 0)
        {
            this.ID = Guid.NewGuid();
        }
    }
}

LINQ to SQL 的架構真的很直覺,有很多以前要寫很多 Code 的地方現在都簡化了,我也自從開始用 LINQ to SQL 之後,就一直不斷發現一些小地方讓我十分滿意。

  

此文章由 will 發表於 2008/4/28 上午 12:00:44

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: .Net | ASP.NET | LINQ | SQL Server

標籤: , , ,

收藏:

好用的 LINQ to CSV library

在做「匯出資料」功能的時候常會使用到 CSV ( Comma Separated Value ) 這種檔案格式,雖然格式並不複雜,但有好用的 Library 當然也能夠更省事啦。

我之前在 PHP 的時候也寫過一個 CSV Parser 也是可以處理許多 CSV 檔案格式不一的問題,例如說用 Outlook 匯出的連絡人 CSV 檔,跟 Excel 另存的 CSV 檔格式就不太一樣,第一次做 CSV Parser 的時候其實還蠻累人的,但是換到 .NET 就很少用 CSV 格式了,我大多用 Excel XML 格式做輸出,因為客戶最終目的還是要用 Excel 開啟匯出的資料。

Matt Perdeck 這位仁兄寫了個 LINQ to CSV library 可以讓你用 LINQ 語法方便的讀取 CSV 格式的檔案,也提供方法寫入 CSV 檔,除了方便好用之外,這個函數庫也十分的有彈性,功能也很強大,有興趣的人可以下載回來玩看看,也許以後會用到。

這套 LINQ to CSV Library 的特色有:

  • 依循最常見的 CSV 檔案格式,且可以正確的處理資料中包括逗號(,)與斷行(\r\n)的問題。
  • 欄位分隔符號可不限於使用逗號(,)或 Tab 符號,你也可以指定自己要用的分隔符號。
    例如:錢字符號 ( $ )、井號 ( # )、... etc.
  • 支援延遲讀取。
    這是 LINQ 的一個特色,可以讓你在定義 LINQ 語法時還不會進行資料的存取
  • 支援存取檔案中的日期與數字格式。
  • 可自動辨識各種不同的日期與數字格式。
  • 可彈性的控制日期與數字的輸出格式。
  • 支援不同的字集編碼 ( Encoding )。
    例如說在 Excel 中你要正確的開啟 CSV 檔你一定要用 Big5 編碼,否則中文會變成亂碼
  • 完善的錯誤處理機制,可讓你快速的找到 CSV 檔案中的錯誤格式。
    這功能很棒,不然檔案很大的 csv 檔要是格式有誤是很難查的!

相關連結

  

此文章由 will 發表於 2008/4/17 上午 12:01:00

永久連結 | 評論 (0) | 此文章的RSSRSS comment feed |

分類: C# | LINQ

標籤: , , ,

收藏: