ASP.NET 動態載入的控制項一定要注意的事

我只是想再特別提醒一下各位,如果你一定要動態載入控制項的話,一定要注意幾件事:

第一:熟悉 ASP.NET Page Lifecycle 很重要(事件執行的順序)

控制項的初始化(Initialize)的動作一定要放在 Page_Init 事件中!

控制項的初始化也包括設定控制項的預設值(Default Value)。

千萬不能將設定控制項初始值的程式碼寫在 Page_Load 事件裡,否則當 PostBack 回來的值會被 Page_Load 事件中的程式碼蓋掉。

動態將控制項加入到 ASP.NET 頁面中「最好」在 Page_Load 事件中執行,以確保在後續事件中可以取得頁面中所有的控制項。

第二:要特別留心控制項加入的順序

動態將控制項加入到 ASP.NET 頁面中時,要特別注意「控制項加入的順序」,順序很重要,只要加入的順序不對就很有可能會在 PostBack 的時候發生 ViewState 異常的狀況,各位可以參考我之前寫的一篇文章:釐清 ViewState 與動態加入的控制項可能發生的錯誤

第三:ViewSate 是個低調的怪獸,能不用就不用

你知道光是談 ViewState 就可以出一本書嗎!

老實說,ViewState 真的很好用,用的人也很多,誤用的人也很多,敗在 ViewState 上面的人也很多。所以一件事情所帶來的好處,背後通常也潛藏著某些不為人知的壞處。我剛學寫 ASP.NET 的時候,曾經寫過一個線上預約系統,因為頁面超複雜,一大堆控制項都是動態加入的,每次 PostBack 大概都要送出 1MB 左右的 ViewState 資料,當初剛學 ASP.NET 也不知道為什麼會這樣,只是覺得為什麼 ASP.NET 執行速度這麼慢,等要結案的時候才發現原來是 ViewState 搞的鬼,光是改另一種寫法就寫死我了。

所以我對 ViewState 一直沒什麼好感,後來我的態度是「我一定要徹底瞭解 ViewState,並且在未來的日子裡能不用就不用它」。既然不用又為何要瞭解的?因為如果你不深入瞭解一項技術背後設計的動機與嘗試解決的問題,你就無法從這項技術得到啟發,並發展出另一套更適合自己的開發架構。

短短的三點提醒,一定不足以應用所有的開發情境,所以各位若能貢獻一些動態載入控制項開發時的注意事項,十分歡迎在此留下些評論,互相交流交流。

  

此文章由 will 發表於 2008/6/24 下午 08:05:42

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

分類: ASP.NET

標籤:

收藏:

完美解決網頁文字太長而導致破版的問題

基本上,網頁遇到「中文字」超過一行時都可以正常的斷字,只是若是英文字寫了一大串沒有空白的字,就會導致網頁版型被撐開,如下圖例就是因為網址過長而導致網頁被撐版,進而影響頁面的呈現。

image

IE 從 5.5 ~ 8.0 版的 CSS 都有支援一個 word-wrap 屬性,當你指定屬性值為 break-word 時就可以強迫瀏覽器進行斷字的動作,這樣就可以避免文字被斷行了。

image

不過要在 Firefox 中使用 word-wrap 就不可行了,一直到前天才正式發佈的 Firefox 3.0 也還是不支援 word-wrap 屬性,不過 word-wrap 屬性已經被編進 CSS 3.0 的規格中了,相信遲早有一天可以支援的。

不過在 Firefox 中也不是完全沒辦法,網路上有篇文章就有寫到如何在 Firefox 中實現自動斷字的方式,有興趣的可以上去看看。

我這裡摘要一下要達到目的必須的步驟:

1. 在 CSS 中定義一個 wordwrap 類別

.wordwrap
{
    word-wrap: break-word;
-moz-binding: url('wordwrap.xml#wordwrap');

display: block; overflow: auto; }

這幾行 CSS 定義都是有意義的,內容這四行我大致解釋一下:

第一行:給 IE 看的,讓斷字產生。

第二行:給 Firefox/Mozilla 看的,透過 binding 的方式執行一段 JavaScript,當 Element 套用此 wordwrap 類別時讀取 wordwrap.xml 檔案,裡面有定義一組JavaScript程式可動態執行。

第三行、第四行:wordwrap.xml 裡面定義當 overflow 事件發生時執行一段程式讓文字斷行,所以 display 屬性一定要設定成 block 才有可能引發 overflow 事件(使用 inline 是沒辦法的),而最後的 overflow 就設定成 auto 即可。

2. 新增一個 wordwrap.xml 檔案

<?xml version = "1.0"?>
<bindings xmlns = "http://www.mozilla.org/xbl" xmlns:html = "http://www.w3.org/1999/xhtml">
  <binding id="wordwrap" applyauthorstyles="false">
    <implementation>
      <constructor>
        //<![CDATA[
            
            var elem = this;

            elem.addEventListener('overflow',
                function()
                {
                    var exp = /<&#8203;\/*[&#8203;_\s="'\w]+>/g;
                    
                    var txt = elem.innerHTML;
                    var chars = txt.split('');
                    var newTxt = chars.join('&#8203;');                    
                    newTxt = newTxt.replace(exp, reconstructTag);                    
                    elem.innerHTML = newTxt;
                },false);
                
                function reconstructTag(_tag)
                {
                    return _tag.replace(/&#8203;/g, '');
                }

            //]]>
      </constructor>
    </implementation>
  </binding>
</bindings>

內容我就不詳述,請自行到 Emulating CSS word-wrap for Mozilla/Firefox 閱讀相關說明。

3. 最後,到你的 HTML 中會破版的那個標籤套上 wordwap 類別即可。

<span class="url wordwrap">一個非常長的網址.....</span>

在我的例子裡,套用之後在 Firefox 中的顯示效果如下:

image

就這樣三個步驟就可以達成完美、跨瀏覽器的自動斷字功能。這裡有個線上的 DEMO,你們可以用 Firefox 去看看執行的效果如何。

相關連結

  

此文章由 will 發表於 2008/6/21 上午 10:00:59

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

分類: ASP.NET | CSS | JavaScript | Tips | Web

標籤: , , , , ,

收藏:

開發 ASP.NET 使用 SQL Server 2000 要注意權限是否足夠

我今天在使用 Visual Studio 2008 開發一個 ASP.NET 的專案,資料庫是用 SQL Server 2000 SP4,但是當我想進入除錯模式(F5)進行測試的時候,卻發生【EXECUTE 使用權限在物件 'sp_sdidebug',資料庫 'master',擁有者 'dbo' 上被拒絕。】的錯誤,如下圖:

EXECUTE 使用權限在物件 'sp_sdidebug',資料庫 'master',擁有者 'dbo' 上被拒絕。

英文的錯誤訊息是:

EXECUTE permission denied on object 'sp_sdidebug', database 'master', owner 'dbo'.

上網查了一下發現原來 SQL Server 2000 從 SP3 之後就改成「如果不是資料庫的擁有者,就無法除錯預儲程序(Stored Procedure)」,進而導致 VS2008 在進入除錯模式的時候會發生例外狀況(Exception)。

而我們通常在「開發時期」都是用 sa 帳戶或信任連線與 SQL Server 連接,所以之前從未遇過此問題,而這個案子是用「最小權限」的方式在開發,也就是在 web.config 中所設定的連線參數只能連接至專案的那個資料庫而已,所以才會出現這個錯誤。

相關連結

  

此文章由 will 發表於 2008/6/18 下午 08:52:14

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

分類: ASP.NET | SQL Server

標籤: , ,

收藏:

善用 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

標籤: ,

收藏:

使用 VS2008 開發 ReportViewer 在部署時的注意事項

我最近用 VS2008 開發了一組報表,但在部署到測試機的時後發生了【無法載入檔案或組件 'Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 或其相依性的其中之一。 系統找不到指定的檔案。】的錯誤:

'/' 應用程式中發生伺服器錯誤。
--------------------------------------------------------------------------------

組態錯誤 
描述: 處理服務此要求所需的組態檔時發生錯誤。請檢視下列的特定錯誤詳細資訊,並適當修改您的組態檔。 

剖析器錯誤訊息: 無法載入檔案或組件 'Microsoft.ReportViewer.WebForms, Version=9.0.0.0, 
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 或其相依性的其中之一。 系統找不到指定的檔案。

image

因為我們在安裝 Visual Studio 2008 時都已經把所有該安裝的元件都安裝好了,所以部署到沒有 Visual Studio 2008 的主機需要額外安裝 ReportViewer 元件才可以使用。

若您是用 VS2008 開發報表的話,安裝檔可到以下網址下載:

若您是用 VS2005 開發報表的話,安裝檔可到以下網址下載:

  

此文章由 will 發表於 2008/6/16 上午 09:47:40

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

分類: .Net | ASP.NET | IIS | 系統管理

標籤: , ,

收藏:

如何讓同一個網域設置兩個不同的ASP.NET應用程式

我們最近開發了一個 ASP.NET 2.0 的網站,在開發的時候沒問題,上測試機的時候沒問題,上正式機的時候卻頻頻出狀況,大致的執行環境如下:

  • 開發機(用 ASP.NET Development Server 執行)
    http://localhost:2938/
  • 測試機(用 IIS 6 執行)
    http://mysite.test.com/
  • 正式機(用 IIS 6 執行)
    http://www.myofficalsite.com/app1/

看出差異了吧!因為我們開發的 ASP.NET 網站被安裝在「虛擬目錄」下,而該網站(http://www.myofficalsite.com/)本身就是一個 ASP.NET 2.0 的網站,所以「根目錄的網站(Root Website)」的 Web.config 影響了「虛擬目錄」下的「子網站」的運作,而導致有些控制項被重複載入,或是因為根目錄的網站要載入某些控制項而到了「子網站」時找不到檔案的問題。

我研究了一下找到了解決方案,其實只要加兩行到根目錄網站的 Web.config 即可取消子網站繼承根目錄網站設定,如下:

<location path="." inheritInChildApplications="false">
    <system.web>
    </system.web>
</location>

也就是先開啟根目錄網站的 Web.config 檔案,找到現有的 <system.web> 區段(Section),將整段的最前面加上一行 <location path="." inheritInChildApplications="false"> 標籤,在整段的最後面新增一行 </location> 標籤就可以了,這等於是宣告 ASP.NET 不要將根目錄網站的設定繼承(Inherit)到子網站應用程式(Child Applications)。

這是個很實用的技巧,不過這真的很難找,感覺很少人會討論到這個問題,而我也是之前在看國外 Blog 的時候無意間發現了,最近終於給我用上了。

相關連結

  

此文章由 will 發表於 2008/6/15 下午 12:46:03

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

分類: ASP.NET | IIS | 系統管理

標籤: ,

收藏:

ASP.NET 自訂角色的方式(不用實做 Role Provider)

我之前講過一篇文章叫做概略解釋 Forms Authentication 的運作,但若要使用 Forms Authentication 來驗證使用者又要自行指派使用者的角色時那就麻煩了,通常要實做角色提供者(Role Provider) 才行,不過通常很麻煩,我今天介紹一個更簡單、方便的自訂角色方法。

若使用 Forms Authentication 我常用的登入程式碼如下:

/// <summary>
/// 設定要存在 FormsAuthenticationTicket 中的資料,這裡用來儲存角色資訊
/// </summary>
string userData = "";

protected void Login_Button_Click(object sender, EventArgs e)
{
    // 登入時清空所有 Session 資料
    Session.RemoveAll();

    // 登入的密碼(以 SHA1 加密)
    string strPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, "SHA1");

    if (ValidateLogin(txtAccount.Text, strPassword))
    {
        // 將管理者登入的 Cookie 設定成 Session Cookie
        bool isPersistent = false;

        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
          txtAccount.Text,
          DateTime.Now,
          DateTime.Now.AddMinutes(30),
          isPersistent,
          userData,
          FormsAuthentication.FormsCookiePath);

        string encTicket = FormsAuthentication.Encrypt(ticket);

        // Create the cookie.
        Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

        Response.Redirect("~/Members/", true);
    }
    else
    {
        Page.ClientScript.RegisterStartupScript(Page.GetType(), "LoginFail", "alert('登入失敗,請重新登入!');", true);
    }
}

/// <summary>
/// 驗證使用者是否登入成功
/// </summary>
/// <param name="strUsername">登入帳號</param>
/// <param name="strPassword">登入密碼</param>
/// <returns></returns>
private bool ValidateLogin(string strUsername, string strPassword)
{
    // 驗證
    
        // 請自行寫 Code 檢查 Username, Password 是否正確
    
    // 授權:設定角色到 userData 
    userData = "gold_member,board_admin";
    
    return true;
}

而這段程式的重點在於我將 userData 儲存在 FormsAuthenticationTicket 中,讓使用者登入資訊包括「角色」的定義,但是如果你要在程式中使用 User.IsInRole 或要在 SiteMap 檔中啟用 securityTrimmingEnabled 的話,還需要在 Global.asax 中新增 Application_AuthenticateRequest 事件,才能讓 ASP.NET 知道該使用者擁有什麼角色,如下:

void Application_AuthenticateRequest(object sender, EventArgs e){
    if (Request.IsAuthenticated) {
        // 先取得該使用者的 FormsIdentity
        FormsIdentity id = (FormsIdentity)User.Identity;
        // 再取出使用者的 FormsAuthenticationTicket
        FormsAuthenticationTicket ticket = id.Ticket;
        // 將儲存在 FormsAuthenticationTicket 中的角色定義取出,並轉成字串陣列
        string[] roles = ticket.UserData.Split(new char[] { ',' });
        // 指派角色到目前這個 HttpContext 的 User 物件去
        Context.User = new GenericPrincipal(Context.User.Identity, roles);
    }
}

這段程式碼我可是研究好久才整理出來的,但我覺得透過這種方式自訂「角色」感覺簡單多了,而且也不需要自己實做 Role Provider 或在資料庫中多建立一個表格,整體的效能也應該不差才是。

P.S. 若要啟用 SiteMap 安全限制的功能必須要在 web.config 中設定該 sitemap 定義的 securityTrimmingEnabled 屬性為 True 才行,如下:

<siteMap defaultProvider="SiteMapProvider" enabled="true">
    <providers>
        <add name="SiteMapProvider" description="設的 SiteMap 定義檔"
             type="System.Web.XmlSiteMapProvider " siteMapFile="~/Web.sitemap" 
             securityTrimmingEnabled="true"/>
    </providers>
</siteMap>
  

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

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

分類: .Net | ASP.NET

標籤: , ,

收藏:

以 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

標籤: , ,

收藏: