1. 難搞的 Entity Framework - 跨越 Context 的查詢

    咳,沒錯... 兩個月都沒寫什麼東西出來,就是都在研究 Entity Framework 跟 Enterprise Library... Enterprise Library 倒還好,看看範例,看看 Key Scenario 大概就能入門了,不過 Entity Framework 就沒這麼簡單...

    Microsoft 對 Entity Framework 的 Roadmap 規劃的很大,不過再怎麼樣附在 .NET 3.5 SP1 的也還只是第一版而已。背負著龐大的架構,而現在卻還不一定能拼過其它成熟的 ORM solution, 這就是辛苦的地方。

    先抱怨一下它的設計工具... 好用是蠻好用的 (跟直接寫 XML 檔相比),不過小問題還不少。像是 TABLE 拉進去,刪掉再拉進去,就有機會 build 失敗... VIEW 拉進去沒辦法明確的指定 KEY 是那個欄位,不是結果不對就是 build 失敗,最後忍不住還是得去手動改 .edmx .. 這些事件老實說這兩個月也碰了不少 @_@

    不過撇開工具的問題,Entity Framework 的設計還真是不錯。其它的心得就改天再講,先講困擾我最久的,也是大部份 ORM 的通病 - 大型 database 的問題。

    這裡指的 "大型" 不是指資料筆數很多,是指 schema 很複雜的情況。大型的 AP 用到上百個 table + view 是很常見的,尤其是隨著改版,舊 table 沒刪掉,新的 table 又一直加,那還真是恐佈。所有的 ORM 都需要某種型態的 O / R Mapping, 不是寫設定檔,就是有視覺化的設計工具。不過... 你能想像一張有 500 個 table 的 ER-MODEL 嗎? 

    要避免過大的單一 OR Mapping 設定,就只有做適度的切割了。在 Entity Framework / Visual Studio 2008 ,這點很容易做到,分成多個 .edmx 檔就可以了。不過問題會在後面,分開多個 .edmx 有幾個缺點:

    1. 會有多個 ObjectContext 產生,每邊都有物件要更新時,每個 ObjectContext 都得呼叫 SaveChange..
    2. 不論是 LINQ to Entities 或是 eSQL, 想要 join 橫跨在多個 .edmx 的資料時,會得到無情的錯誤... 不支援跨 context 的操作
    3. AssoicationSet 無法跨越 context 的範圍,意思是跨 .edmx 的 Entity, 不能靠 Navigation Property 來處理。

    解決的方式當然也有,也查了 ADO.NET Team Blog,這兩篇是所有 GOOGLE 到的相關文章裡,講這問題講的最深入的..

    有碰到這問題的,這兩篇一定要看一看。其實文章內探討的問題已經超過我的需求了,我只是要解決我面臨到的問題:

    1. 因應模組化需求,我需要把 .edmx 跟其相關的邏輯封裝在各別的 assembly
    2. 不同的模組間定義的 EntitySet 能夠用 eSQL 做 JOIN 的查詢
    3. 最基本的 LINQ 也不能少
    4. 新增新的模組時,其它模組不需要重新編譯

    老實說這兩篇沒解決到我的問題,只不過瞄到了 Part 2 有這麼一段話:

    9. At runtime, you could create either one Context that works with both the schema sets or two different contexts. To create a single context with both the schema sets, you would use the ObjectContext constructor that takes in an EntityConnectionString. In the Metadata parameter of the connection string, specify the paths to both sets of files.

    哈哈,運氣不錯,關鍵的一段話沒有漏掉... 就這段話解決了我一個多月以來的困擾。Entity Connection 用的連接字串又臭又長,包括了你的 .csdl, .ssdl, .msl 三份定義檔在那裡,還有底層用的 database connection string. 湊起來一大串,像這樣:

    medadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;............

    而從這兩篇文章,除了各種 split / reuse 這堆設定檔的方法及優缺點之外,就是學到原來 entity connect string 可以併入多組對應檔啊 :D,像這樣:

    medadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl|res://*/Model2.csdl|res://*/Model2.ssdl|res://*/Model2.msl;............

    沒錯!! 說穿了不值錢,就是把 entity connection string 這樣改一改就好了,eSQL 就可以透過單一 object context 就能存取兩份 .edmx 內定義的內容, Linq 則要自己用 CreateQuery< >() 方式來產生 EntitySet.. 其它就沒有什麼太大不同了。

    總算搞定這個大問題!! 收工! 其它的應用就改天有空再慢慢貼了 :D 感謝各位在這兩個月枯水期還沒取消訂閱我的 BLOG ... :D

    2009/01/20 .NET C# Entity Framework

  2. RUN!PC 精選文章 - 生產線模式的多執行緒應用

    http://www.runpc.com.tw/content/main_content.aspx?mgo=178&fid=E08

    無意間 search 我自己的名字,才發現這篇文章除了投稿到 RUN! PC 之外,原來還有刊在網站上的精選文章啊...

    哈哈,暗爽一下,順道貼一下 link, 讓沒看到雜誌的網友們也有機會看一看在下的作品...

    2009/01/16 RUN! PC 專欄文章 .NET C# RUN! PC 作品集 多執行緒 專欄 技術隨筆 有的沒的

  3. XmlWellFormedWriter.WriteRaw( ) 的 Bug 後續發展

    一時順手,就按下 Visual Studio 2008 上面的 [Report Bug] 回報上一篇發現的 Bug, 沒想到 Microsoft 真的有回應耶... :D

    反正 Microsoft 在 connect 裡的回應本來就公開的,我就順手貼一下:

     

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=386899&wa=wsignin1.0

     

    Hello,
    Originally the WriteRaw method was designed for XmlWriters that are formatting text XML into a file. In those cases the WriteRaw method can be used to write out an XML fragment that is already formatted and checked for well-formedness. It can also be used for writing text nodes with special character that have been already escaped and no further processing of the text is needed.
    However, when we introduced the XmlWriter over XmlDocument/XDocument (accessed via XPathNavigator editing methods), the use of the WriteRaw method on top of XmlDocument became controversial. We had two options:
    1.    Threat it as a text
    2.    Parse it into nodes


    The second option is very difficult (if not possible) to do. The XML fragment can we written out in multiple WriteRaw calls, so we could not assume that a single WriteRaw will contain a fully enclosed fragment. It can also be interleaved with other XmlWriter calls and nested many times – overall a very hard thing to implement properly. So that is why we have decided to treat the WriteRaw content as text, which is what you are seeing.
    If you have an XML fragment in a string and you want to append it to XmlDocument, you can do it like this:


                    XmlDocument doc = new XmlDocument();
                    XmlElement rootElement = doc.CreateElement("root");
                    rootElement.InnerXml = "<a/><a/><a/><a/><a/>";
                    doc.AppendChild(rootElement);


    Or if you really want to use the XmlWriter from XPathNavigator.AppendChild(), you can create an XmlReader over your fragment and use the WriteNode method on the XmlWriter:


                    XmlDocument doc = new XmlDocument();
                    XmlWriter writer = doc.CreateNavigator().AppendChild();
                    writer.WriteStartElement("root");
                    using (XmlReader r = XmlReader.Create(new StringReader("<a/><a/><a/><a/><a/>"), new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment } ) ) {
                        writer.WriteNode(r, false);
                    }
                    writer.WriteEndElement();
                    writer.Close();
                    doc.Save(Console.Out);


    I hope this helps.
    Thank you,
    -Helena Kotas, System.Xml Developer

     

    看來 Microsoft 認為這是權宜之計,不算 BUG,要 USER 就直接避掉了,只是他建議的解法剛好就是我上一篇用的,用 XmlReader, 搭上 ConformanceLevel.Fragment 的設定解決掉...

    只不過,這種情況,不是應該丟出 NotSupportException 比較好嘛? 幹嘛拿個不適當的實作填進來?

     

    提外話,現在 WEB 2.0 的時代,Microsoft 工程師除了寫 CODE 之外,也要負責回客戶的問題了? 真辛苦...

    2008/12/10 .NET C# MSDN Tips 技術隨筆 有的沒的

  4. 原來 System.Xml.XmlWellFormedWriter 有 Bug ..

    果然沒啥人知道的 code, bug 也會比較慢被抓出來 ... 兩個小時前我才貼了找到 XmlNodeWriter 的替代品,用了一下就被我挖到一個 BUG ... @_@

    先來看看我的 Sample Code:

    XmlTextWriter v.s. XmlWellFormedWriter[copy code]
       1:  // test xml text writer, correct result
       2:  // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
       3:  {
       4:      Console.WriteLine("Using XmlTextWriter:");
       5:      XmlWriter writer = XmlWriter.Create(Console.Out);
       6:      writer.WriteStartElement("root");
       7:      writer.WriteRaw("<a/><a/><a/><a/><a/>");
       8:      writer.WriteEndElement();
       9:      writer.Flush();
      10:      Console.WriteLine();
      11:      Console.WriteLine();
      12:  }
      13:  // test xml node writer, wrong result
      14:  // output: <?xml version="1.0" encoding="big5"?><root>&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;</root>
      15:  {
      16:      Console.WriteLine("Using XmlWellFormedWriter:");
      17:      XmlDocument xmldoc = new XmlDocument();
      18:      XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
      19:      writer.WriteStartElement("root");
      20:      writer.WriteRaw("<a/><a/><a/><a/><a/>");
      21:      writer.WriteEndElement();
      22:      writer.Close();
      23:      xmldoc.Save(Console.Out);
      24:      Console.WriteLine();
      25:      Console.WriteLine();
      26:  }

     

    而這是程式的輸出畫面:

    image

     

    兩段 code 除了拿到的 XmlWriter 來源不同之外,用它寫 XML DATA 的方式是一致的,不過寫出來的 XML 則完全不同。看來兩種 XmlWriter 對於 WriteRaw(...) 的實作不大相同。而照 MSDN 上的說明來說,XmlTextWriter的行為是對的,XmlWellFormedWriter 則太雞婆了,沒事多作一次編碼...

     

    該說運氣好嘛? 哈哈... 繼上次撈到一個 SmtpMail 的 Bug 之後,這次又撈到一個... 要用的人注意一下,不過即使有這個 Bug, 也不會影響它的地位啦,這 Writer 解決了我很大的困擾,動搖國本也要用下去... (咳... 不過是避開一個 API ...)

     

    最後我改了用法,一方面 API 有 BUG 是一回事,另一方面直接用這 API 也很危險,因為 MSDN 說它不會去做內容的驗證,也就是說透過 WriteRaw( ) 寫進不合法的資料,會讓你整份輸出都毀了... 第二個原因比較重要,因此我換了一個替代作法, 類似 Pipe 一樣,把 XmlReader 讀到的東西都寫到 XmlWriter:

    XmlCopyPipe 實作[copy code]
       1:  /// <summary>
       2:   /// 從 XmlReader 複製到 XmlWriter
       3:   /// </summary>
       4:   /// <param name="reader"></param>
       5:   /// <param name="writer"></param>
       6:   private static void XmlCopyPipe(XmlReader reader, XmlWriter writer)
       7:   {
       8:       if (reader == null)
       9:       {
      10:           throw new ArgumentNullException("reader");
      11:       }
      12:       if (writer == null)
      13:       {
      14:           throw new ArgumentNullException("writer");
      15:       }
      16:       while (reader.Read() == true)
      17:       {
      18:           switch (reader.NodeType)
      19:           {
      20:               case XmlNodeType.Element:
      21:                   writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
      22:                   writer.WriteAttributes(reader, true);
      23:                   if (reader.IsEmptyElement)
      24:                   {
      25:                       writer.WriteEndElement();
      26:                   }
      27:                   break;
      28:               case XmlNodeType.Text:
      29:                   writer.WriteString(reader.Value);
      30:                   break;
      31:               case XmlNodeType.Whitespace:
      32:               case XmlNodeType.SignificantWhitespace:
      33:                   writer.WriteWhitespace(reader.Value);
      34:                   break;
      35:               case XmlNodeType.CDATA:
      36:                   writer.WriteCData(reader.Value);
      37:                   break;
      38:               case XmlNodeType.EntityReference:
      39:                   writer.WriteEntityRef(reader.Name);
      40:                   break;
      41:               case XmlNodeType.XmlDeclaration:
      42:               case XmlNodeType.ProcessingInstruction:
      43:                   writer.WriteProcessingInstruction(reader.Name, reader.Value);
      44:                   break;
      45:               case XmlNodeType.DocumentType:
      46:                   writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
      47:                   break;
      48:               case XmlNodeType.Comment:
      49:                   writer.WriteComment(reader.Value);
      50:                   break;
      51:               case XmlNodeType.EndElement:
      52:                   writer.WriteFullEndElement();
      53:                   break;
      54:           }
      55:       }
      56:   }

     

    很好用的作法,就像過去需要 COPY XML 資料,最常見的就是把來源跟目的都用 XmlDocument 載入,直接用 ImportNode( ) 把 XML 片段資料搬到另一個 XmlDocument 再儲存。跟上一篇的原因一樣,看起來很蠢... 就想到這個作法,透過 XmlReader, 拿到的是已經 parsing 過的資料,直接寫到 XmlWriter。而我用的 Writer 正好又可避開重複作 parsing 動作的優點,正好這樣效能跟可用性都兼顧了... 經過 parsing, 至少寫出來的東西會安心一點...

     

    把最後我的程式搭配這個 XmlPipeCopy 改一改:

    用 XmlCopyPipe 取代 WriteRaw( )[copy code]
       1:  XmlDocument xmldoc = new XmlDocument();
       2:  XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
       3:  writer.WriteStartElement("root");
       4:  XmlReaderSettings settings = new XmlReaderSettings();
       5:  settings.ConformanceLevel = ConformanceLevel.Fragment;
       6:  XmlReader reader = XmlReader.Create(
       7:      new StringReader("<a/><a/><a/><a/><a/>"),
       8:      settings);
       9:  XmlCopyPipe(reader, writer);
      10:  writer.WriteEndElement();
      11:  writer.Close();
      12:  xmldoc.Save(Console.Out);

     

    試了一下,果然如預期的執行了 :D,結果也沒錯,還好 XmlWellFormedWriter 的 Bug 只存在於 WriteRaw... 閃開就沒事了:

    image

     

     

    其中有個陷阱,就是如何用 XmlReader 讀取 XmlFragment (可以有多個 ROOT 的 XML DATA)。其實這個解法跟程式碼,大部份都是這篇看來的,只不過在裡面加了個 LOOP 跟改了名字,各位覺的好用的話記得去謝原作者 Mark Fussell, 別謝錯人了 :D

    2008/12/08 .NET C# MSDN Tips 有的沒的

  5. 原來 .NET 早就內建 XmlNodeWriter 了...

    最近事情一堆,上班忙上班的事,下班還在忙著研究 Enterprise Library, Entity Framework, 還有一堆五四三的,文章寫的就少了... 先跟有訂閱我 BLOG 的朋友們說聲道歉...。 不過在寫新專案的過程中,意外的發現這東西,一定要提一下...

     

    不知道有多少人用過 XmlNodeWriter ? 我用這東西用很久了,當年 Microsoft 推出 .NET Framework 時,強調有很強的 XML 處理能力,其中 XmlReader / XmlWriter 就是以效能為考量,讓你避開處理大型 XML 資料效能很糟糕的 XmlDocument, 也不用去碰很難寫的 SAX 的替代方案...

    無奈 Microsoft 內建的 XmlWriter 少的可憐,只能寫到檔案或是 TextWriter ... 看看權威的 MSDN 告訴我們有那些 XmlWriter 可以用?

    image

    老實說除了 XmlTextWriter 之外,另外兩個很少用的到。XmlWriter 在輸出 XML 時很好用 (如果你只作輸出的話),複雜的 XML 輸出用 XmlWriter 比用 XmlDocument 簡單多了,不過最常碰到的情況是我還是想用 XmlDocument 來操作 XML,不過其中一部份的 NODE 想用 XmlWriter 來更新內容...

    古早有位好心的 MVP 寫了 XmlNodeWriter, 就可以讓我這樣用:

    XmlNodeWriter Sample Code:[copy code]
       1:  XmlDocument xdoc = new XmlDocument();
       2:  xdoc.LoadXml("<root><node1><data/><data/><data/></node1><node2/></root>");
       3:  XmlNodeWriter xnw = new XmlNodeWriter(xdoc.DocumentElement, true);
       4:  xnw.WriteStartElement("newNode");
       5:  xnw.WriteAttributeString("newatt", "123");
       6:  xnw.WriteCData("1234567890");
       7:  xnw.WriteEndElement();
       8:  xnw.Close();
       9:  xdoc.Save(Console.Out);

     

    第一個參數是 XmlNode, 第二個參數是要不要清掉原來 Node 下的內容。很棒,我可以直接拿 XmlNode 當作 XmlWriter 輸出的對象,透過 Writer 寫出去的東西就直接反映在 XmlNode 身上了,省掉輸出成 Text 然後再 PARSING 回 XML NODE 這種蠢事...

     

    前面只是緬懷 XmlNodeWriter 到底有多好用,現在找不到有多難過而已... 接下來才是正題...

     

    不過現在想再去找 XmlNodeWriter 官方網站已經找不到了 @_@,原本這 lib 是 hosting 在 gotdotnet.com 這網站上,不過 Microsoft 已經把它關了,改成 codeplex.com / MSDN Code Gallery 取代,只好求助 GOOGLE 大神,無意間又發現這 Microsoft XmlTeam 的 BLOGCOMMENTS 有這麼一段:

     

    # re: XML Features in the February CTP of Visual Studio “Orcas”
    Friday, February 02, 2007 8:27 PM by Stuart Ballard


    Is there going to be an XmlNodeWriter in Orcas? It's a fairly glaring hole, especially if you've ever wanted to apply an XSL transformation to an in-memory XmlDocument and get the result as another in-memory XmlDocument. You can pass the input to the transform via XmlNodeReader, but to get it back out again you just have to feed your XmlWriter to a StringBuilder and then parse it...

    Fortunately my use case wasn't performance-critical, but it's still ugly...

     

    # re: XML Features in the February CTP of Visual Studio “Orcas”
    Saturday, February 03, 2007 3:22 AM by Oleg Tkachenko


    Stuart, XmlNodeWriter in .NET 2.0 is hiding in

    xmlNode.createNavigator().AppendChild() method. It can be used to populate XmlNode via XmlWriter API and so you can

    XmlDocument doc = new XmlDocument();

    using (XmlWriter writer = doc.CreateNavigator().AppendChild()) {

       xslt.Transform(input, (XsltArgumentList)null, writer);

    }

    Mike, am I right that  Orcas January CTP includes none of these coolness?

     

    真是太機車了,這麼好用的東西藏在這種地方? @_@,枉我從 .NET 1.0 beta 就開始用 C# 處理 XML,連 XSLT Extension 都寫過一堆, Trace Code 也追到 XSLT 內抓過一堆問題... 竟然連這東西都沒發現? 可惡...

    於是手又癢了,拿來試用看看,發現只要動手寫幾行 Code, 我就能把 XmlNodeWriter 變回來了,像這樣:

     

    我的 XmlNodeWriter 實作[copy code]
       1:  public class XmlNodeWriter : XmlWriter
       2:  {
       3:      private XmlWriter _inner_writer = null;
       4:      public XmlNodeWriter(XmlNode node, bool clean)
       5:      {
       6:          if (clean == true)
       7:          {
       8:              node.RemoveAll();
       9:          }
      10:          this._inner_writer = node.CreateNavigator().AppendChild();
      11:      }
      12:      #region 無聊的 "延長線" 程式碼...
      13:      // 略! 共一百多行,補上廿幾個 abstract method / property, 把它接到 _inner_writer 上
      14:      #endregion
      15:  }

     

    這樣果真可以 WORK 了 :D  不過要真正變出一個新的 XmlNodeWriter 代價還不低,繼承 XmlWriter 的後果是有廿幾個 abstract method / property 得補上實作... 全都是很無聊的 code, 就是拿 _inner_writer 的直接套上去而已... 像這樣:

    "延長線" 型的程式碼[copy code]
       1:  public override void Close()
       2:  {
       3:      this._inner_writer.Close();
       4:  }
       5:  public override void Flush()
       6:  {
       7:      this._inner_writer.Flush();
       8:  }
       9:  public override string LookupPrefix(string ns)
      10:  {
      11:      return this._inner_writer.LookupPrefix(ns);
      12:  }

     

    這堆 Code 我就不貼了,總之可以 WORK :D

     

    image

     

    不過對 CODE 有點潔癖的我,越看越不是味道,就動起 Factory 的腦筋了。繼續改造一下... 原本 .NET 2.0 內建的 XmlWriter 就已經提供 Factory 的用法了,像這樣:

    XmlWriter my_writer = XmlWriter.Create( ... );

    不過沒辦法不改 .NET FX 原始碼的情況下 "加掛" 我自己的 Create(...) 實作,原本腦筋是動到 C# 3.0 開始支援的 Extension Method, 不過它只支援 instance method, 不支援 static method ... 只好改成這樣:

     

    XmlWriterFactory 實作[copy code]
       1:  public abstract class XmlWriterFactory : XmlWriter
       2:  {
       3:      public static XmlWriter Create(XmlNode node)
       4:      {
       5:          return Create(node, false, null);
       6:      }
       7:      public static XmlWriter Create(XmlNode node, bool clearnContent)
       8:      {
       9:          return Create(node, clearnContent, null);
      10:      }
      11:      public static XmlWriter Create(XmlNode node, bool cleanContent, XmlWriterSettings settings)
      12:      {
      13:          if (node == null) throw new ArgumentNullException("node");
      14:          if (cleanContent == true)
      15:          {
      16:              node.RemoveAll();
      17:          }
      18:          XmlWriter xw = node.CreateNavigator().AppendChild();
      19:          if (settings != null)
      20:          {
      21:              xw = XmlWriter.Create(xw, settings);
      22:          }
      23:          return xw;
      24:      }
      25:  }

     

    沒事還繼承原本的 XmlWriter 只有一個目的,就是要延用它原來的 10 種 Create method 啊... 貼張圖為証,繼承之後我就有 13 種不同的 Create method 可以用... 不用再兩頭跑 (只是不能加在原本的 XmlWriter 上真是殘念, C# 什麼時後會支援 static method extension ?):

    image

     

     

    當然,原程式只要改掉如何拿到 XmlWriter 那行而已,其它都照舊就可以執行了 :D

     

    有需要的就拿去用吧,CODE 才十幾行,還包成 DLL 實在太麻煩了,需要的直接貼到你自己的 CODE 裡就好! 要散怖都隨便,沒有什麼授權問題,唯一的要求就是讓我知道我的 CODE 你有在用就好 :D,想讚助我的也很簡單,BLOG 上該多點幾下的東西,沒事就點一點... 哈哈

    2008/12/07 .NET C# Tips 作品集 技術隨筆 物件導向