Tuesday, April 17, 2007 - 文章

KB-ADO.NET 2.0+SQL 2005分散式交易錯誤

今天發現正式主機上的ASP.NET 2.0程式傳回以下錯誤:
Stack Trace:
[SqlException (0x80131904): New request is not allowed to start because it should come with valid transaction descriptor.]
   System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) +857578
   System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) +735190
   System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) +188
   System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) +1838
   System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj) +600
   System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie) +38
   System.Data.SqlClient.SqlInternalConnection.EnlistNull() +61
   System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx) +736046
   System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction) +38
   System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction) +30
   System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject) +1209
   System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) +82
   System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) +105
   System.Data.SqlClient.SqlConnection.Open() +111
   Blah.SqlDataProvider.Query(String cmdText) in C:\Documents and Settings\jeffrey\My Documents\Visual Studio 2005\Projects\Blah\SqlDataProvider.cs:472

出錯的程式只是簡單的查詢,並沒有啟動TransationScope,但由StackTrace來看,出現TdsExecuteTransactionManagerRequest等Method,很像被包在TransactionScope中的Behavior。追蹤前因後果,發現這個錯誤發生在一個包含TransactionScope的ASP.NET因Exception Rollback之後,同樣的訊息隔了幾鐘出現兩次,但期間也有其他ASP.NET成功完成資料庫存取。感覺上很像Connection Pool中有一條壞掉的Connection(由TransactionScope Rollback那條?),誰用到誰倒楣。

Google了一下,很快找到MS的KB 916002。發生的條件為ADO.NET 2 + SQL 2005,再加上

• The connection is reused from the connection pool in a distributed transaction.
Connection Pool中的Connection用在分散式交易
•  A local transaction is committed or rolled back before the connection.
連線前有本地的交易Commit或Rollback(真是語簡意賅,看不懂)
• The local transaction ends before the connection.
連線前本地交易結束(一整個深奧,無法理解)

雖然不能完全了解KB裡的描述,但就Error Message及之前發生Rollback兩點的符合,我覺得是它的機率很高。MS承認這是Bug,很不幸地,它2006/6/26才公佈,沒趕上2006/4的SQL 2005 SP1,所以得裝Hotfix修正。[2007-04-20 更正,它是.NET 2.0的Hotfix才對] 由於是上線DB,想等到週末再來安裝Hotfix,到時再跟大家報告結果。

KB-難以理解的Disabled Span行為

   1:  <html><body>
   2:  <form method="POST">
   3:      <span disabled="disabled">
   4:          <input name="x" type="text">
   5:          <input type="button" onclick="alert('Yo!');" value="Yo">
   6:      </span>
   7:      <input type="submit" value="Submit">
   8:  </form>
   9:  </body></html>

以上這段HTML Code, 看來十分平常,但卻隱藏著殺機...

注意到span上的宣告disabled=disabled了嗎? 設定disabled屬性後,span內部的Input都會變成灰色,感覺上被disable了。但是!!! input type="text"可以繼續輸入,button的onclick也會照樣被觸發。

所以一切都是幻覺,嚇不倒我??

又錯了! 雖然input可輸入,button onclick會觸發,但是Submit時,被包在disabled span中的form element都會被忽略,後端是接不到的。

這兩個矛盾的行為,讓我搞不清楚被包在disabled span中的form element究竟算不算被disabled?從HTML client的角度來說不是(因為可以操作,只是變灰),從Server的角度來說是(因為不會被submit)。

結論是,大家如果發現資料沒被Submit時,不妨檢查一下是否外層的element被disabled了,例如這篇文章(回應中有人可是耗了好幾個小時在上面呢)。

PS: 另外做了個測試,span disbled在Firefox上完全不管用,不會產生灰色效果,也不會阻擋Submit,會有跨瀏覽器的問題。

TIPS-Invoke HttpRequest with ASP.NET AJAX client library

ASP.NET AJAX除了神奇地簡化了前端動態更新式網頁的開發工作化,還建立了不少Client-Side Script的基礎建設。神奇簡化的背後是靠複雜的ViewState、HTML部分更新堆砌起來的,ASP.NET AJAX所提供的UpdatePanel或ASP.NET 2.0的Script Callbacks,程式寫來超簡單,但每按一個鈕的代價是數十K資料在網路飛來飛(過陣子我會針對這部分做些探討),是否值得什麼東西都要AJAX化或放棄自己Coding HttpRequest改用ASP.NET AJAX? 非常值得商確。

基於以上的考量,我並不打算全部前端動態處理的東西都用UpdatePanel打死,除非是被呼叫的頻率不算高或以UpdatePanel可以讓程式簡化很多,有許多時候(尤其是得注意效能的場合),我還是會考慮採取HttpRequest Call另一隻ASPX程式的做法(連Web Service都不太想用,SOAP的Overhead也是挺可觀的)。不過,以前大家寫HttpRequest都是自己找Library、Code Sample,現在有了ASP.NET AJAX,理論上,就該借用它相關的Client-Side Function,避免各吹各的調。

以下示範,如何用ASP.NET AJAX的前端去Call另一隻ASPX傳回的XML:

function testXml() {
    var wRequest =  new Sys.Net.WebRequest();
    //指定URL
    wRequest.set_url("WebReqXml.aspx");  
    //非同步式呼叫,指定資料回傳的接收函數
    wRequest.add_completed(xmlCallback);
    //發射
    wRequest.invoke();        
}
function xmlCallback(executor, eventArgs) {
    if(executor.get_responseAvailable()) 
    {
        //由Response中取出XML
        var xd=executor.get_xml();
        //用ASP.NET AJAX新推出的StringBuilder來接字串
        var sb = new Sys.StringBuilder("User Info:");
        sb.append("<br />Name="+xd.selectSingleNode("/Player/Name").text);
        sb.append("<br />Skills="+xd.selectSingleNode("/Player/Skills").text);
        //FireFox時,要用getElementById代替docuemnt.all,索性用$get較省事
        $get("spnPlayerInfo").innerHTML=sb.toString();
    }
    else
    {
        if (executor.get_timedOut())
            alert("Timed Out");
        else if (executor.get_aborted())
            alert("Aborted");
    }        
}

WebReqXml.aspx的寫法如下:

protected void Page_Load(object sender, EventArgs e)
{
    XmlDocument xd = new XmlDocument();
    xd.LoadXml("<Player></Player>");
    XmlNode xn = xd.CreateElement("Name");
    xn.InnerText = "Jeffrey";
    xd.DocumentElement.AppendChild(xn);
    xn = xd.CreateElement("Skills");
    xn.InnerText = "Hoyucan";
    xd.DocumentElement.AppendChild(xn);
    Response.ContentType = "text/xml";
    Response.Write(xd.OuterXml);
    Response.End();
}

比較特別的地方都寫在註解裡了,只再補充三件事:

1) ASP.NET AJAX提供了$get, $addHandler這些Global Shortcuts函數,其實都只是很平常的找Element、設定Event之類的動作。有些人可能會懷疑直接寫document.all("...")不就得了,為什麼要大費周章只為了少打幾個字元? 你也許不知道,document.all("...")這種寫法遇到Firefox是會出局的。如果以跨瀏覽器為目標,使用這些捷徑函數,少打的可不只是幾個字而已,而是一大段依瀏覽器不同隨機應變的Code。
2) ASP.NET AJAX將Namespace的概念搬到Client端了。所以你會常看到Sys.Net.Blah... 這類的寫法。完整的Client Reference可以參考MS的官方參考文件
3) 有些好東西已被MS在Client實作出來了,例如: StringBuilder。而Javascript base type extension強化了Array, Boolean, Date, Error, Number, Object及String物件,值得多多利用,有份很不錯的小抄可以參考。

 除了以上傳統的XML式資料傳回法,這裡一定得提一下,ASP.NET AJAX引入的新概念---JSON(JavaScript Object Notation)!!

JSON有什麼神奇呢? 舉個例子來說,假設我們在.NET程式裡有個PlayerInfo物件(假設它有Name, Skills兩個Property),序列化成JSON字串後Response.Write回Client端,Client端可以把字串還原回後物件,接著在Javascript中一樣可以用player.Name, player.Skills去讀屬性值,酷吧?

先來看ASPX裡如何實作JSON的傳送端: 我們建立一個PlayerInfo Structure,直接將物件丟給System.Web.Script.Serialization.JavaScriptSerializer.Serialize(讓我想到變蠅人裡那台神奇的物質傳送器)後得到一個字串,將字串送給呼叫它的Javascript程式就完成了Server端的工作。

//一個簡單的玩家資料結構
public struct PlayerInfo
{
    public string Name;
    public string Skills;
    public PlayerInfo(string name, string skills)
    {
        this.Name = name;
        this.Skills = skills;
    }
}
 
protected void Page_Load(object sender, EventArgs e)
{
    PlayerInfo p = new PlayerInfo("Keroro", "Kero Ball");
    System.Web.Script.Serialization.JavaScriptSerializer jss 
        = new System.Web.Script.Serialization.JavaScriptSerializer();
    Response.Write(jss.Serialize(p));
    Response.End();
}

Client端呼叫跟接收的程式跟處理XML差不多,我們用Sys.Serialization.JavaScriptSerializer.deserialize這個神奇函數,取代了原本的XMLDOM,就可以讓.NET Object在Javascript裡復活。

    function testJson() {
        var wRequest =  new Sys.Net.WebRequest();
        wRequest.set_url("WebReqJson.aspx");  
        wRequest.add_completed(jsonCallback);
        wRequest.invoke();         
    }
    function jsonCallback(executor, eventArgs) {
        if(executor.get_responseAvailable()) 
        {
            //將其還原回Object, COOL!!
            var player=
        Sys.Serialization.JavaScriptSerializer.deserialize(
        executor.get_responseData());       
            var sb = new Sys.StringBuilder("User Info:");
            //可以直接引用Name屬性
              sb.append("<br />Name="+player.Name);
            sb.append("<br />Skills="+player.Skills);
            $get("spnPlayerInfo").innerHTML=sb.toString();
        }
        else
        {
            if (executor.get_timedOut())
                alert("Timed Out");
            else if (executor.get_aborted())
                alert("Aborted");
        }        
    }    

是不是很酷呀? 不過大家也別想太多,以為這樣就可以把SqlDataRreader傳到Javascript World把玩。JSON的目的主要是用來傳遞物件的資料結構,程式碼並不在其涵蓋範圍內。想要變成AJAX高手,還是乖乖學好JavaSript JavaScript(Update 2007-04-23 謝謝elleryq指正)吧!

搜尋

Go

<April 2007>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication