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

標籤: ,

收藏:

以 Convert.ToString(Eval("ID")) 取代 Eval("ID").ToString()

在 ASP.NET 頁面套版時,我常看到有工程師是這樣套版的:

<a href="<%# Server.UrlPathEncode(Eval("Attachment").ToString()) %>">XXXX</a>

看出問題了嗎?

是的,當 Attachment 這個欄位的內容是 null 的時候,這裡就會發生例外狀況(Exception),所以這樣的寫法一旦養成習慣,那就等著常常被電啦!

我認為比較好的方式應該是改用以下的寫法:

<a href="<%# Server.UrlPathEncode(Convert.ToString(Eval("Attachment"))) %>">XXXX</a>

其實只是換個角度來想而已!原本是把 Eval("Attachment") 當成「主角」來看待,讓這位物件的主角呼叫 ToString() 回應字串;現在改變成「配角」,將 Eval("Attachment") 變成參數傳入 Convert 類別提供的 ToString() 方法,如果傳入的 Eval("Attachment") 是 null 的話,Convert.ToString(Eval("Attachment")) 傳回的值就是 String.Empty (也就是空字串啦),這樣一來就不會發生 Exception 了。

這是一個很小很小的技巧,但是請銘記在心。

2008-06-20 補充:

  • 我最近發現原來使用 Eval("ID", "") 也可以轉成字串喔,簡短多了,供大家參考!
  

此文章由 will 發表於 2008/6/6 下午 10:04:09

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

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

標籤:

收藏:

鬼打牆事件之『ASP.NET 無法刪除 Cookie 的問題』

先將我的執行環境說明一下:

  • 我有兩個網站,網域分別為 www1.domain.com 與 www2.domain.com
  • 自己實做單一簽入(Single Sign On, SSO)的機制,並透過 Web Service 進行網站會員登入、登出
  • 兩個網站共用同一組 Cookie 用以儲存 SSO 的 Token,且明訂 Cookie 的 Domain 為 .domain.com

我今天聚焦在「登出」這個簡單的功能就好,實作登出是在簡單不過的機制了,就是把 Cookie 清除掉就好啦!不過要是跟我一樣遇到 ASP.NET 怎樣也清除不掉 Cookie 的情況,那就真的是「鬼打牆」了,而且若要問人家:「請問 Cookie 要怎麼樣才能刪除掉?」這種問題問別人真的會被笑,也不知道怎麼開口。

因為我要共用 Cookie 在兩個網域,所以我的 Cookie Domain 是 .domain.com,所以照理說清除 Cookie 的方法應該是:

Response.Cookies["Token"].Expires = DateTime.Now.AddYears(-1);

或者是

Response.Cookies["Token"].Expires = DateTime.Now.AddYears(-1);
Response.Cookies["Token"].Domain = ".domain.com";

或者是

Response.Cookies["Token"].HttpOnly = true;
Response.Cookies["Token"].Expires = DateTime.Now.AddYears(-1);
Response.Cookies["Token"].Domain = ".domain.com";

反正能試的都試了,而且以我對 HTTP 底層協定的了解,幾乎在 Web 領域沒有什麼問題是無法解決的,但今天遇到 Cookie 清不掉這個問題,真的讓我滿面愁容,程式越寫越氣,用 Fiddler2 看 HTTP 封包看了幾十遍,就是看不出有任何問題,但 Cookie 就是殺不掉。

但是這問題非得要研究出來不可,就因為這個「無法登出」的「小問題」搞了我快 5 個小時才弄清楚所有來龍去脈,所幸問題有被我追根究柢的解決了,以下是要解決此問題的完整解法:

  • 若要清除跨 Domain 的 Cookie 必須清除兩次,例如說使用者在 www1.domain.com 要執行登出動作,必須要先將 Domain 為 www1.domain.com 的 Cookie 給清除掉,在接著將 Domain 為 .domain.com 的這個 Cookie 清除掉。
  • 因為這兩個 Cookie 為「同名」,全部都叫做 Token,所以無法在一個 HTTP Request 中清除掉兩個同名的 Cookie,所以必須要在不同的兩個 HTTP Request 中個別刪除不同 Domain 的 Cookie。

例如說:你必須先連到 Logout.aspx 頁面,在此頁面先將第一組 Cookie 清除:

HttpCookie cookie = new HttpCookie("Token", ""); 

cookie.HttpOnly = true;
cookie.Expires = DateTime.Now.AddYears(-1); 
cookie.Domain = Request.Url.Host;
Response.SetCookie(cookie);

Response.Redirect("Logout2.aspx", true);

然後再轉址到 Logout2.aspx 將 Parent Domain 的 Cookie 給清除掉:

HttpCookie cookie = new HttpCookie("Token", ""); 

cookie.HttpOnly = true;
cookie.Expires = DateTime.Now.AddYears(-1); 
cookie.Domain = ".domain.com";
Response.SetCookie(cookie);

Response.Redirect("index.aspx", true);

這樣就可以徹底將 Cookie 給清乾淨了,這真是難得的經驗,從沒想到有這種解法,不知道網路上有沒有其他人遇過跟我同樣的問題?

而我的登出程式最後是改成以下這段 Code,濃縮再一支程式裡:

protected void Page_Init(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache); 

    HttpCookie cookie = new HttpCookie("Token", ""); 

    cookie.HttpOnly = true;
    cookie.Expires = DateTime.Now.AddYears(-1); 

    if (Request.QueryString["domain"] == null)
    {
        cookie.Domain = Request.Url.Host;
        Response.SetCookie(cookie);
        Response.Redirect("Logout.aspx?domain=1"), true);
    }
    else
    {
        cookie.Domain = ".domain.com";
        Response.SetCookie(cookie);
        Response.Redirect("index.aspx", true);
    } 
}
  

此文章由 will 發表於 2008/6/4 下午 08:44:02

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

分類: .Net | ASP.NET | C# | Web | 心得分享

標籤: , , ,

收藏:

解決 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

標籤: , ,

收藏:

推薦:Microsoft Visual Studio International Pack 1.0 版

Microsoft Visual Studio International Pack 是一套國產的類別庫喔,是由台灣微軟的工程師所開發的,主要是幫助 .NET 程式開發人員建立全球化的應用程式,其中包括許多功能,其中有一套我們很可能會用到的「繁簡轉換」功能,也就是「中文繁簡轉換類別庫」。

在你下載回 vsintlpack1.zip 檔案並解壓縮後,會有個 CHTCHSConv.msi 安裝檔,這個就是「中文繁簡轉換類別庫及 Add-In 工具」,安裝好之後相關檔案會放在以下目錄(好長的目錄名稱啊):

C:\Program Files\Microsoft Visual Studio International Pack\Traditional Chinese to Simplified Chinese Conversion Library and Add-In Tool

你只要將該目錄中的 ChineseConverter.dll 組件複製到你網站的 Bin 目錄下就能用了,要使用之前必須先引用其命名空間:

using Microsoft.International.Converters.TraditionalChineseToSimplifiedConverter;

其中只有一個 ChineseConverter 靜態類別與一個 ChineseConversionDirection 列舉(enum),使用方式很簡單,如果要將繁體字轉成簡體字可參考以下程式碼:

string TWS = "領導有策略";
string CHS = ChineseConverter.Convert(TWS, ChineseConversionDirection.TraditionalToSimplified);

如果要將簡體字轉成繁體字可參考以下程式碼:

string CHS = "领导有策略";
string TWS = ChineseConverter.Convert(CHS, ChineseConversionDirection.SimplifiedToTraditional);

夠簡單吧!

除了單純的「字對字」轉譯之外,如果使用者電腦有安裝 Microsoft Office 2007 的話(使用這個組件的主機),他還會利用 Office 2007 內建的一些「詞彙轉譯」功能,發現雖然文件說有安裝 Microsoft Office 2007 可以讓中文繁簡體轉換出來的品質更好,不過我測試了一下發現其實沒差,不知道品質好在哪裡?不過不失為一個好用的工具啦! ^_^

  

此文章由 will 發表於 2008/5/29 下午 08:20:24

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

分類: .Net | C# | Office | Visual Studio

標籤: , , ,

收藏:

C# 3.0 初始設定 Hashtable 的方式

C# 3.0 有個特性叫做「物件和集合初始設定式」(Object and Collection Initializers),一般的用法是:

Customer c1 = new Customer
    {
        Name = "Will Huang", 
        Age = 30
    };

不過今天在寫 Code 的時候因為寫到 Hashtable 時卡住了,突然忘記要怎麼寫 Hashtable 的初始值的方式,上網查了一下寫法,原來很簡單,用大括弧框起每一筆即可。

Hashtable GenderMapping = new Hashtable()
    {
        {"男", true},
        {"女", false}
    }; 

相關連結

  

此文章由 will 發表於 2008/5/27 下午 11:59:01

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

分類: .Net | C#

標籤: , ,

收藏:

解決 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

標籤: , ,

收藏:

好用的 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

標籤: , , , , , ,

收藏:

今天成功的利用 WinDbg 分析與解決程式 Hang 住的狀況

我們之前有一支 Console Program 程式是用來收取 POP3 伺服器中的郵件,並在解析郵件內容後進行分析與後續處理,不過三不五時程式都會 Hang 住不動,導致分析作業無法進行下去。又因為我們在開發環境一直都很正常的執行,程式是到正式主機才 Hang 住的,所以一直很苦惱到底要怎麼解決。

我們之前的解決方法就是在程式中寫了大量的 Log 訊息,讓程式在正式主機執行的時候多紀錄一些執行的動作,最後是查出抓取某一封郵件的時候 Hang 住的,由於我們是採用從 CodeProject 下載回來的 .NET POP3 MIME Client 元件,元件本身包的還不錯用,只是寫的有點複雜,我們在追蹤錯誤的時候很難標出可能發生錯誤的地方,曾經研究了 6 個小時找不到 Hang 住的地方,這樣一直猜程式到底 Hang 在哪裡真的很累人。

今天我就利用之前去「ASP.NET 應用程式偵錯實戰專班」上課所學的知識進行程式的傾印(dump)與分析(analysis),還真的給我找到問題點了,不出 30 分鐘問題就解決,真的太棒了!不過說實在的,這課雖然是5月6~7日才剛上過的,到了今天還真的忘了一大半,主要是 WinDbg 的指令太難記了,不常用真的會忘記,我今天就大致分享一下除錯的過程與心得。

我主要會分成兩大塊來講,第一個是「將程式執行中的狀態留下來」(又稱 dump 或 Userdump),接下來是透過 WinDbg 程式分析 dump 檔。

首先,你必須先去下載 Debug Diagnostic Tool v1.1 工具(抓 dump 的工具),以及到  Debugging Tools for Windows 網站下載 WinDbg 程式(目前的最新版是6.9.3.113),並安裝完成。

第一步,將程式執行中的狀態留下來

開啟 Debug Diagnostic Tool 後,可以直接進入 Processes 頁籤,選取你 Hang 住的程式,按滑鼠右鍵選【Create Full Userdump】(如下圖)

Debug Diagnostic Tool

Debug Diagnostic Tool 預設手動產生的 Dump 檔會放在 C:\Program Files\DebugDiag\Logs\Misc 目錄下:

Debug Diagnostic Tool 預設手動產生的 Dump 檔會放在 C:\Program Files\DebugDiag\Logs\Misc 目錄下

這時你就會取得一個完整的程式執行狀態了,其中綠色圖示的 ConsoleProgram1.exe__PID__5276__Date__05_18_2008__Time_03_36_54PM__659__Manual Dump.dmp 檔案就是一個完整的 Dump File。

第二步:透過 WinDbg 程式分析 *.dmp 檔案

你開啟 WinDbg 程式後,第一步是先設定 Symbol 位址,開啟 File -> Symbol File Path。

接著輸入以下位址就設定完成了,預設動態下載的 Symbol 檔會儲存在底下這個設定的 c:\websymbols 目錄,你可以自己修改成其他目錄:

SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols

在來就直接載入剛剛的 ConsoleProgram1.exe__PID__5276__Date__05_18_2008__Time_03_36_54PM__659__Manual Dump.dmp 檔案。

選完檔案後會出現一個小視窗:

接下來就是打指令的時間啦,只要輸入 3 個指令就可以完成分析作業:

1. 在畫面最下面 0:000> 的輸入框輸入 kbn 顯示目前程式的 Call stack 與前三個參數、Frame number,如果你有看到 mscorwks 字樣的話(如下圖框紅色的地方),就代表這支程式是 .NET 寫的應用程式。

2. 接著載入 .NET 的 SOS 組件,輸入 .loadby sos mscorwks 指令。

3. 接著輸入 !clrstack 指令,輸出目前 .NET 程式的 Call stack,這你就應該很熟悉了。

0:000> !clrstack
OS Thread Id: 0x140c (0)
ESP       EIP     
0012f190 7c9585ec [HelperMethodFrame_1OBJ: 0012f190] System.Threading.WaitHandle.WaitOneNative(Microsoft.Win32.SafeHandles.SafeWaitHandle, UInt32, Boolean, Boolean)
0012f23c 793b03fe System.Threading.WaitHandle.WaitOne(Int64, Boolean)
0012f254 793b0c1b System.Threading.WaitHandle.WaitOne(Int32, Boolean)
0012f264 793b044e System.Threading.WaitHandle.WaitOne()
0012f268 00e92204 Net.Mail.Pop3Command`1[[System.__Canon, mscorlib]].GetResponse()
0012f2a4 00e9207d Net.Mail.Pop3Command`1[[System.__Canon, mscorlib]].Execute(Net.Mail.Pop3State)
0012f2cc 00e92d36 Net.Mail.Pop3Client.ExecuteCommand[[System.__Canon, mscorlib],[System.__Canon, mscorlib]](System.__Canon)
0012f2f0 00e94301 Net.Mail.Pop3Client.RetrMimeEntity(Net.Mail.Pop3ListItem)
0012f334 00e941c3 Net.Mail.Pop3Client.RetrMailMessageEx(Net.Mail.Pop3ListItem)
0012f348 00e9059c Pop3.Program.Main(System.String[])
0012f69c 79e7c74b [GCFrame: 0012f69c]

由這個 .NET 的 Call stack 訊息得知我的程式一直停在 System.Threading.WaitHandle.WaitOne() 這個方法,且透過 Call stack 也很容易找到這一行到底在哪裡。

找到後原本的程式是這樣:

_manualResetEvent.WaitOne();

最後我改成有 timeout 的版本,不樣讓程式一直癡癡的等:

_manualResetEvent.WaitOne(20 * 1000, false);

就這樣解決了一個惱人的問題。

相關連結

  

此文章由 will 發表於 2008/5/18 下午 05:31:10

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

分類: .Net | C#

標籤: , , , ,

收藏:

在撰寫 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

標籤: , , , ,

收藏: