Quantcast
Channel: 黑暗執行緒
Viewing all 2433 articles
Browse latest View live

StringDictionary無法JSON反序列化

$
0
0

接獲報案,使用Json.NET將WebService傳回物件序列化為JSON字串,過程順利,但反序列化發生錯誤:Cannot create and populate list type System.Collections.Specialized.StringDictionary. Path '', line 1, position 1.

問題物件包含StringDictionary型別,StringDictionary經JSON轉換後變成[{"Key":"…", "Value":"…"}, {"Key":"…","Value":"…"}…],以JavaScript角度相當於一堆具有Key及Value屬性物件所組成的陣列,故Json.NET解析時解析視為陣列,無法轉型為單一StringDictionary物件。

想起以前學過自訂Json.NET轉換邏輯,再次派上用場。我寫了一個StringDictionaryConverter搭配JsonConvet.DeserializeObject使用,就能成功將[{"Key":"…", "Value":"…"}, {"Key":"…","Value":"…"}…]轉為StringDictionary。

另外,順手測了BinaryFormatter(二進位序列化),發現「二進位序列化不一定比JSON序列化省空間」!

程式範例如下:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication3
{
class Program
    {
class StringDictionaryConverter : CustomCreationConverter<StringDictionary> {
 
publicoverride StringDictionary Create(Type objectType)
            {
thrownew NotImplementedException();
            }
 
publicoverrideobject ReadJson(JsonReader reader, Type objectType, 
object existingValue, JsonSerializer serializer)
            {
                JArray array = JArray.Load(reader);
                StringDictionary dict = new StringDictionary();
foreach (var element in array)
                {
                    dict.Add(element.Value<string>("Key"),
                        element.Value<string>("Value"));
                }
return dict;
            }
        }
 
 
 
staticvoid Main(string[] args)
        {
            StringDictionary sd = new StringDictionary();
            sd.Add("Jeffrey", "32767");
            sd.Add("Darkthread", "65535");
 
//印出StringDicionary內容
            Func<StringDictionary, string> dumpStringDictionary = (d) =>
            {
                List<string> list = new List<string>();
foreach (string k in d.Keys)
                    list.Add(string.Format("{0}:\"{1}\"", k, d[k]));
return"{" + string.Join(",", list.ToArray()) + "}";
            };
 
//JSON序列化沒問題
            var json = JsonConvert.SerializeObject(sd);
            Console.WriteLine("JSON({1}bytes)={0}", json, json.Length);
//反序列化會出現錯誤
try
            {
                var test = JsonConvert.DeserializeObject<StringDictionary>(json);
            }
catch (Exception ex)
            {
                Console.WriteLine("Error: {0}", ex.Message);
            }
//加上自訂StringDictionary轉換器後可成功反序列化
            var restored = JsonConvert.DeserializeObject<StringDictionary>(json, 
new StringDictionaryConverter());
 
            Console.WriteLine("Restored={0}", dumpStringDictionary(restored));
//測試二進位序列化
            BinaryFormatter bf = new BinaryFormatter();
byte[] data;
using (MemoryStream ms = new MemoryStream())
            {
                bf.Serialize(ms, sd);
                data = ms.ToArray();
                Console.WriteLine("Binary({1}bytes)={0}",
                    Encoding.UTF8.GetString(data), data.Length);
            }
using (MemoryStream msRestore = new MemoryStream(data))
            {
                restored = bf.Deserialize(msRestore) as StringDictionary;
                Console.WriteLine("Restored={0}", dumpStringDictionary(restored));
            }
            Console.Read();
        }
    }
}

測試結果:

在這個案例中,二進位序列化由於包含了物件型別宣告資訊(上圖看到的那堆"System, Version=4.0.0.0…"、"System.Collections.Specialized.StringDictionary…"),其資料量(475bytes)反而是JSON(72bytes)的六倍有餘。但型別資訊屬固定Overhead,二進位序列化時資料部分不像JSON必須逐筆標示屬性名稱"Key"、"Value",才開始展現節省效果。由此推估,當資料筆數多到一定數量,省略屬性名稱的優勢將會超過標頭區的額外耗損,才會比JSON更省空間。

例如:我們將資料筆數提高到100筆。

            StringDictionary sd = new StringDictionary();
for (int i = 0; i < 100; i++)
            {
                sd.Add("K" + i, "V" + i);
            }

此時JSON字串長度為2,781bytes,而二進位序列化資料只有2,204bytes,情勢逆轉。

順便記錄此一特性提供參考。


NG筆記25-如何將Directive Isolated Scope宣告為TypeScript強型別?

$
0
0

NG範例23-使用Directive建立自訂網頁元素介紹過好用的自訂元素。除了資料互動不高的單純DOM操作Directive,一般我們都會另建獨立Scope(Isolated Scope)避免彼此干擾。而Isolated Scope宣告時需指定{ propA: "=", propB: "@" }形式的scope物件,供NG參照建立參數對應,因此不像Controller、Service可以寫成獨立類別,而這衍生的副作用則是-scope參數在link函式裡被視為任意型別(any),難以享受TypeScript的強型別優勢。

舉個例子,以下模擬一個整合KendoGrid建立的Directive,其中宣告獨立Scope,對應到data、timestamp、columns、editable四個聯外參數,而在link函式中還在scope增加了options屬性,方便後續繫結到k-options。遇到這種狀況,我們直覺上會將scope宣告為any型別,否則TypeScript會抱怨scope不支援data、colums等屬性。但怎麼一來,scope與scope的屬性也都變成any型別,Visual Studio甚至連scope.$watch也認不出來,強型別優勢盡失。

幾經嘗試,我找到一個不錯的解決方案。

做法是多宣告一個ICustScope,依一般interface的做法宣告scope會用到的屬性或方法名稱及其型別,並記得extends ng.IScope。接著在link函式中,我們將scope宣告為ICustScope型別,讓scope成為不折不扣的強型別變數,後續開發才能保有Intellisense的便利及型別檢查的保護,充分享受使用TypeScript的優勢。同時,因為ICustScope繼承了ng.IScope,link函式裡的scope.$watch也能被正確識別。

由於ICustScope為此Directive專屬,不需export到module之外,可設為私有interface,有效範圍只在此段module directives的 「{」與「}」之間,而且interface只存在TypeScript世界,不實際產生JavaScript程式,故每個Directive用的Scope定義都取名ICustScope也不是問題,不需要擔心各Directive的自訂Scope介面名稱相衝。

註:舊版TypeScript不允許在對外函式link函式的參數型別使用私有Interface ICustScope,升級TypeScript 1.4後已無此限制。

題外話:TypeScript已經成為Angular 2.0的奧林匹克指定開發語言,又多了一項強而有力的學習理由!以我自己為例,在習慣TypeScript強型別的好處後,開始覺得過去一天到晚因打錯字被JavaScript陰,函式變數名稱沒取好也沒勇氣改的日子,真不是人過的…(謎之聲:接手維護你那些JavaScript的日子才不是人過的吧!)

【延伸閱讀】

 

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

Web API自訂AuthorizeAttribute注意事項

$
0
0

打算用Attribute限定ASP.NET Web API只接受本機存取,搬來先前寫的ASP.NET MVC版本卻沒效果。爬文得知ASP.NET Web API的AuthorizeAttribute Namespace與ASP.NET MVC不同,MVC版放在System.Web.Mvc下,Web API版則位於System.Web.Http。(此差異的關鍵在於System.Web.Mvc需依賴IIS,Web API考量跨平台另起爐灶,而ASP.NET 5也會更朝向不綁作業環境,可獨立運作的架構發展。)

由於我的AuthorizeAttribute放在獨立DLL,原本未參照Web API相關程式庫,心想只用到System.Web.Http,殺雞何需動牛刀,那就別從NuGet下載整套Web API,直接參照.NET Framework的System.Web.Http就好。

手腳俐落地將繼承來源由System.Web.Mvc.AuthorizeAttribute改成System.Web.Http.AuthorizeAttribute,HttpContextBase再改由actionContext.Request.Properties["MS_HttpContext"]取得,馬上就改好重新編譯無誤。

實測發現,改寫後的自訂AuthorizeAttribute還是沒生效,開始一頭埋進去射茶包,花了很久時間反覆測試,最後發現將自訂AuthorizeAttribute搬到ASP.NET Web API專案就正常,案情出現一絲曙光。

比較DLL專案與WebAPI專案,發現同樣是System.Web.Http,.NET Framework所附的是4.0.0版。而透過NuGet下載Web API套件:

專案所參照的System.Web.Http會變成來自NuGet下載的5.2.3版。

誤用4.0.0版的下場,編譯正常,執行也不會有錯誤訊息,但就是不會動… orz

我學到的教訓是:要在獨立DLL開發ASP.NET MVC或Web API支援元件,也請乖乖使用NuGet下載及更新相關程式庫。

僅以此文紀念白白消逝的三小時。

重點整理:

  1. Web API Controller用的AuthorizeAttribute位於System.Web.Http命名空間,不要跟System.Web.Mvc的MVC Controller專用版搞混,二者不相容。
  2. 撰寫自訂Authorization Filter時請由下列三者擇一繼承:
  • AuthorizeAttribute. 若授權邏輯與使用者身分或角色有關。
  • AuthorizationFilterAttribute. 授權檢查邏輯以同步方式執行(呼叫後可立刻得到結果)且與使用者身分或角色無關。 (以我的限定本機存取案例來說,最適合AuthorizationFilterAttribute)
  • IAuthorizationFilter. 授權檢查邏輯以非同步方式執行,例如:涉及網路查詢或大量CPU運算,繼承IAuthorizationFilter可以省去自己寫非同步機制的功夫。
  • 請確認自訂AuthorizeAttribute參照的System.Web.Http與其他程式庫與Web API所參照的版本一致。
  • 參考:Authentication and Authorization in ASP.NET Web API - The ASP.NET Site

    jQuery TypeScript定義檔2.2.3無法編譯

    $
    0
    0

    使用NuGet更新全部套件後,jQuery定義檔出現TypeScript編譯錯誤:

    Error    545    Interface 'JQueryPromise<T>' incorrectly extends interface 'JQueryGenericPromise<T>'.
      Types of property 'then' are incompatible.
        Type '(doneCallbacks: any, failCallbacks: any, progressCallbacks?: any) => JQueryPromise<T>' is not assignable to type '{ <U>(doneFilter: (value?: T, ...values: any[]) => U | JQueryPromise<U>, failFilter?: (...reasons...'.
          Type 'JQueryPromise<T>' is not assignable to type 'JQueryPromise<void>'.
            Type 'T' is not assignable to type 'void'. 

    檢查發現jQuery TypeScript定義檔被更新到2.2.3版,是這幾天(3/24)才推出的,莫非如上次事件與TypeScript新版有關?檢查TypeScript 1.4 for VS2013在1/16之後並無更新,先排除TypeScript套件未更新因素。

    懷疑新版定義檔有Bug,但爬文未發現有人反應類似狀況(2.2.3截至目前下載量不到800次,可能發現問題的人還不多)。

    使用Package Manager Console下指令Install-Package jquery.TypeScript.DefinitelyTyped -Version 2.2.2強制裝回2.2.2版(套件名稱及版號不用強記,按Tab會有提示),問題消失!順手將問題回報到Github,先以降版做為暫時解決方案。

    Managed ODP.NET簡介

    $
    0
    0

    Oracle Client版本問題困擾過很多次,之前由網友回饋得知Managed ODP.NET,一直沒深入研究過,直到今天完成評估,新武器一枚入手。

    過去我們常用的Oracle.DataAccess(ODP.NET),骨子裡其實是走Unmanaged,得靠oci.dll那堆程式庫才能連上資料庫,因此安裝時必須一併安裝Oracle Client,而Unmanaged Oracle Client有32位元跟64位元之分,許多開發者還因此學會IIS Application Pool啟用32位元模式的技巧呢!加上Oracle.DataAccess.dll執行時需存取Oracle Client檔案,我還因此學會排除NTFS權限問題PATH環境變數等技巧,ODP.NET真的讓我成長好多… orz

    由以上的血淚經驗,已足以說明Managed ODP.NET的長處:

    1. 不需要安裝龐大的Oracle Client,只用一個Oracle.ManagedDataAccess.dll就可搞定連線。
    2. 不必再為32位元跟64位元版本問題困擾,Oracle.ManagedDataAccess.dll的目標平台是Any CPU!(灑花)
      註:若程式啟用分散式交易,還需動用另一顆區分32及64版本的Oracle.ManagedDataAccessDTC.dll,但專案不需加入參照,Managed ODP.NET會自動載入正確版本

    補充:除了Oracle.ManagedDataAccess.dll,若使用EF6+或Code First,專案需再多參照Oracle.ManagedDataAccess.EntityFramework.dll。

    Managed ODP.NET可由Oracle官網下載取得,它被歸類為64-bit ODAC:

    注意:Managed ODP.NET支援Entity Framework,但不提供設計階段支援,如果想在Visual Studio使用Entity Framework專案項目,仍須安裝32位元版ODAC with Oracle Developer Tools for Visual Studio,另一方面,開發者通常會用到SQL Plus或第三方Oracle資料庫工具,故開發環境仍會以ODAC 32位元為主,伺服器或大量部署的客戶端才是Managed ODP.NET展現威力的主戰場。

    Managed版ODP.NET很小,只有2.53M,ZIP檔解壓縮可得到odp.net、network資料夾,readme.html安裝說明以及install_odpm.bat、uninstall_odpm.bat,為安裝及解除安裝批次檔。

    一般會用install_odpm.bat c:\oracle both true安裝及註冊,install_odpm.bat有三個參數,第一個參數為安裝資料夾(將解壓縮內容複製到指定路徑再註冊),第二個參數為x86、x64、both三者擇一,第三個參數則決定是否要將元件加入GAC及在machine.config加入設定,如果要執行Entity Framework,請選true。

    安裝動作很單純,下完指令瞬間完成,跟Unmanaged ODP.NET的安裝相比,是高鐵 vs 牛車的差別。

    安裝完成後,下一步是換上作業環境專用的資料庫名稱設定檔,在本例中,TNSNAMES.ORA預設放在c:\oracle\network\admin\目錄。

    要使用Managed ODP.NET時,專案必須做些修改,將Oracle.DataAccess參照改成Oracle.ManagedDataAccess,但OracleConnection、OracleCommand等用法都相同,程式碼幾乎[註]不需更動。(註:Managed ODP.NET與Unmanaged ODP.NET仍存在些許差異,不過依文件所列項目,我手上的程式應該都不用修改)

    使用Visual Studio開發的專案,改用Managed ODP.NET的捷徑是透過NuGet下載:(該程式套件由Oracle官方提供,可安心服用)

    前面提到,實務上Visual Studio開發機器多已安裝32位元ODAC(否則無法在專案中設計及修改Entity Framework模型),改由NuGet取得Managed ODP.NET後,取得TNS名稱的方式跟Unmanaged ODP.NET有所不同,傳統ODP.NET主要依賴Registry設定,而Managed ODP.NET則有自己解析TNS名稱的順序:參考

    1. .NET config裡dataSources區塊裡的設定(資料庫別名設定可以直接寫在config裡,如此連TNSNAMES.ORA都不需要)
    2. 由.NET config檔TNS_ADMIN設定所指路徑找到TNSNAMES.ORA
    3. EXE檔所在目錄下的TNSNAMES.ORA
    4. 環境變數%TNS_ADMIN%路徑下的TNSNAME.ORA
    5. 環境變數%ORACLE_HOME%\network\admin路徑下的TNSNAME.ORA

    就這樣,只要一個Oracle.ManagedDataAccess.dll,就能連上Oracle,很棒吧?

    【結論】

    不需要安裝笨重的Oracle Client,不用擔心32位元跟64位元版本打架,.NET程式就能輕鬆連上Oracle資料庫,是Managed ODP.NET最大亮點。在伺服器只運行.NET程式或是要大量部署到客戶端的場合,改用Managed ODP.NET可大幅簡化Oracle Client部署作業,是很不錯的選擇!

    【2015-04-01更新】

    感謝同事Jean補充目前改用Managed ODP.NET可能欠缺的功能:(但未來應會陸續補上)

    1. XML相關功能
    2. 不支援的OracleDbType:XmlType、Array、Boolean、Object、Ref
    3. OracleAdvanceQueue(罕用)
    4. OracleBulkCopy

    Font Awesome,果然厲害!

    $
    0
    0

    在同事專案發現好物-FontAwesome

    用字型檔配合CSS顯示圖示已非新鮮事(例如:Kendo UIBootstrap),但看過Font Awesome的威力展示,還是不禁讚嘆,真的好厲害!

    除了圖示數量多(完整圖示清單可參考官網),Font Awesome最強大之處,在於只用CSS樣式就實現尺寸放大、旋轉、鏡像、360度旋轉動畫、疊圖組合新圖示等神奇效果。(所以,Font Awesome的CSS設定檔也是偷學CSS技巧的好地方呢!)

    先來個簡單的威力展示:(完整程式碼及線上展示附於文末)

    怎麼安裝到專案裡呢?到官網下載解壓縮再自行複製到專案裡?不好不好,這年頭還鑽木取火會被人笑…

    打開NuGet,輸入Font Awesome,類似的Id有多個,最新版下載數較少排名較後,記得比一下發佈時間及版號。

    按下Install,專案會多出fonts資料夾,Content多出font-awesome.css及font-awesome.min.css。在網頁加入CSS連結,寫行<i calss="fa fa-icon-name"></i>就可以開始使用Font Awesome囉!

    如果策略允許使用CDN,也可引用Bootstrap CDN上的font-awesome.min.css,讓瀏覽器直接從CDN取得字型檔,如此簡化安裝部署、提升瀏覽速度又能節省頻寬,值得考慮。

    附上先前展示的Online Demo及完整程式碼:

    <!DOCTYPEhtml>
    <html>
    <head>
    <title>Font Awesome Test</title>
    <linkrel="stylesheet"
    href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
    <style>
            div { font-size: 10pt; margin: 6px; }
    </style>
    </head>
    <body>
    <div><iclass="fa fa-camera-retro"></i>標準</div>
    <div><iclass="fa fa-camera-retro fa-lg"></i> fa-lg 放大</div>
    <div><iclass="fa fa-camera-retro fa-2x"></i> fa-2x 兩倍大</div>
    <div><iclass="fa fa-camera-retro fa-3x"></i> fa-3x 三倍大</div>
    <div><iclass="fa fa-camera-retro fa-4x"></i> fa-4x 四倍大</div>
    <div><iclass="fa fa-camera-retro fa-5x"></i> fa-5x 五倍大</div>
    <div>
    <iclass="fa fa-android fa-2x fa-spin"style="color: #1d6000"></i>
            fa-spin 轉圈圈
    </div>
    <div>
    <iclass="fa fa-area-chart fa-2x"></i>
    <iclass="fa fa-area-chart fa-2x fa-rotate-90"></i>
    <iclass="fa fa-area-chart fa-2x fa-rotate-180"></i>
    <iclass="fa fa-area-chart fa-2x fa-rotate-270"></i>
            fa-rotate-90/180/270 旋轉
    </div>
    <div>
    <iclass="fa fa-git fa-2x fa-flip-horizontal"></i>
    <iclass="fa fa-git fa-2x fa-flip-vertical"></i>
            fa-flip-horizontal fa-flip-vertical 水平/垂直鏡像
    </div>
    <div>
    <iclass="fa fa-arrow-up fa-2x "></i>
    <iclass="fa fa-arrow-left fa-2x"></i>可變寬度
    </div>
    <div>
    <iclass="fa fa-arrow-up fa-2x fa-fw"></i>
    <iclass="fa fa-arrow-left fa-2x fa-fw"></i> fa-fw 固定寬度
    </div>
    <div>
    <spanclass="fa-stack fa-lg">
    <iclass="fa fa-square-o fa-stack-2x"></i>
    <iclass="fa fa-html5 fa-stack-1x"></i>
    </span>
            fa-stack 疊圖 fa-square-o 圓角框
    </div>
    <div>
    <spanclass="fa-stack fa-lg">
    <iclass="fa fa-circle fa-stack-2x"></i>
    <iclass="fa fa-html5 fa-stack-1x fa-inverse"></i>
    </span>
            fa-circle 圓形 + fa-inverse 反白
    </div>
    <div>
    <spanclass="fa-stack fa-lg">
    <iclass="fa fa-html5 fa-stack-1x"></i>
    <iclass="fa fa-ban fa-stack-2x"style="color: red"></i>
    </span>
            fa-ban 禁止符號
    </div>
    </body>
    </html>

    EF7將變成Code First Only?

    $
    0
    0

    在ASP.NET5文章看到留言,david提到EF7 Code Only。想起之前隱約讀到EF7將有所變革,會只剩下Code First(程式碼先行),當時看了也沒放心上。一經提醒,不對,Code First Only? 花惹發?事情大條了!

    Code First的點子很酷,Schema不用預先規劃討論,程式設計師依需求打造資料物件類別,第一次執行時來個初始化程序,對應的資料表就在DB自動建好,寫DB就算寫D槽一樣簡單,好不爽快。

    只可惜,實務上事情並不像想像這麼美好。除非是小型系統,資料表侷限少數程式使用,不涉及複雜系統整合且沒有太多報表需求,才可能交由程式設計師全權做主。否則,Schema常是多方協調下的產物。在撰寫程式之前,系統分析人員就得提出Schema規劃供各資料使用單位審閱,檢查是否短缺欄位導致其他系統難以運作?能否滿足轉檔及報表需求?正規化反正規化加不加鍵值索引,要在嚴謹與效能間取得平衡,哪些邏輯要由Procedure、Trigger、View實現,現有設計能否配合?待各界回饋折衝取得共識,訂好Schema像憲法一樣供在那裡,大家不准也不敢改(改了其他系統壞掉,你要出來坦嗎?),各系統的程式設計師乖乖照著寫程式,世界和平~

    必須說,在我工作接觸的專案應用,Database First(資料庫先行)才是常態,Code First或許是程式宅宅心中的AKB48,Database First才是柴米油鹽醬醋茶~

    那,EF7說將來只有Code First Only是什麼鬼話?(冰斗啦)

    查了資料,幸好,是我了解不夠,一切只是誤會一場!

    EF 4.1時代推出Code First,標榜可以未建立資料表前先寫資料庫程式,系統可依類別自動建立資料表,藉以與Database First(寫程式前資料表已經建好)區別。有趣的是,EF 6.1起又納入一項新功能,可以依據現成資料表產生原本要手寫的Code First類別(Code first from database),而不像過去只能產生EDMX!

    這個新功能讓Code First這個命名變得無比矛盾-依據「現有資料表」建立一個「在建立資料表前就先寫好」的類別? 最後讓Code First變成一個爛名字,First名不符實,一整個尷尬。因此Code-Based Model(程式碼模型,與EDMX這類XML模型相對比)才是較適切的名稱。

    換句話說,所謂EF7 Code First Only應該要正名為Code-Based Model Only!最大的意義在於EF7不再支援EDMX,你仍然可為現有資料庫在專案建立對應資料類別,但不再有編輯修改EDMX的圖形化介面、依EDMX再自動產生類別程式碼的步驟。EF開發Team的考量在於:

    1. XML檔不像程式那麼好被合併、解決衝突以及檢閱
    2. 規格異動時可減少同時修改EDMX及程式碼的重複工作
    3. 針對特定資料提供者的特殊需求,EDMX不像程式碼可透過條件化編譯(#if #else)加工
    4. 程式碼比EDMX更容易提供設計階段的錯誤資訊細節
    5. Migration功能-Code-Based Model將可依程式修改產生自動更新Schema的SQL Script(EDMX只能產生建立資料表Script,不能產生異動Schema Script)

    餘下一個重要疑問,資料庫Shema異動時要怎麼同步回到程式端?EDMX有「Update model from databse」功能(雖然有時有點兩光,需要手動介入校正),EF6.1雖然能由現資料庫產生Code-Based Model,但遇到Schema更新時採取的對策只有重新產生覆寫掉類別,若類別裡寫了自訂邏輯就慘了。EF7有計劃要加入「不覆寫原有程式就能套有Schema異動」的功能,但實做方式仍在研究中。

    我的專案架構很少將Model邏輯直接寫在EF產生的Entity類別,多半會將邏輯抽取出來,另外寫一顆Model類別(或用自製程式自動產生器產生),讀取時傳入Entity轉為Model,新增/修改前一刻轉成Entity。這個設計哲學是將Entity純粹當成資料庫的對應物件,不要沾染程式端的邏輯,而這麼做最大的好處也在於Entity類別不放自訂邏輯,遇到變動時可以隨時刪掉重建再修改Model配合即可。在此一架構下,倒還能接受用砍掉重建的方式處理Schema更新。

    補充:上述提到的「Model邏輯」主要指與Entity屬性密切相關的額外運算或檢核,例如:PropC = PropA + PropB、檢核所有欄位是否都有效的bool IsValid()。有時一個Model對一個Entity(Article Model -> Article Entity),也有可能一個Model涉及多個Entity(例如:Order Model -> OrderMaster Entity + OrderItem Entity)。我的想法是從物件導向概念出發,將跟Model有關的邏輯都該封裝在它自己裡面,以追求SoC(關注點分離)。

    【結論】

    Code First Only是項誤解,EF7真正的大改變是Code-Based Model Only!

    拿掉EDMX是一項重大更動,連EF Team都坦誠自己做好被譙的心理準備 XD,也不鼓勵大家急著升級。依我的看法,只能靠砍掉重建實現「Update model from database」功能是Code-Based Model目前較嚴重的缺陷,EF7也尚未提出妥善解決方案。而我習慣另建Model類別加入自訂邏輯,純粹把Entity當成資料表的100%重現,砍掉重建Entity的做法也OK,預期轉用EF7也不會有太大問題,暫且不用擔心日後不得不升級時吐血一升。

    【延伸閱讀】

    2015鳳梨馬

    $
    0
    0

    第24馬,2015八卦山台地馬拉松,適逢鳳梨產季,跑在鳳梨田間,補給站吃鳳梨喝鳳梨,伴手禮有鳳梨汁,連完賽獎牌上都有鳳梨,就叫它鳳梨馬吧!

    去年清明掃墓兼跑馬一舉兩得感覺不賴,今年照舊。誰知行前一週一家子展開感冒接力,小閃光跟小木頭陸續中鏢,喉痛發燒請了幾天假,最後波及他們老木,全家四分之三成員淪陷,眼看接力棒就要傳到我手上,把早早訂好的計劃攪亂,連是否能成行都產生變數。

    週六凌晨出現輕微喉痛,但起床後感覺還好,最後仍依計劃出團。週六跑完祭祖訪親行程早早休息,只求隔天別輪我發難。今年大會提供接駁車,早上五點10分到南投家樂福時已有兩台遊覽車等侯,我才剛登車就比預訂時間提早10分鐘出發。車上坐了好大一團香港跑友,對他們而言今天是「海外馬」格外興奮,廣東話此起彼落,讓我意外享受猶如到香港跑馬的海外馬氣氛,哈!

    5:40抵達會場,西嶺國小操場上好多帳篷,還看到幾台公路車,莫非是自行車騎到會場露營隔天跑馬,好強大的戶外活動健身魂!

    會場佈署跟去年很像,頒獎台的巨幅印刷帆布,圖案相同但依然精美搶眼。本來期待能再看到去年由四位同學舉旗進場的陣勢,今年會旗改由兩位同學「抬」進場,氣勢有差,害我小小失落了一下。XD

    去年的「攻其不備默默開跑」讓我印象深刻,今年主持人特別到起跑線跟大家一起同樂,除了介紹來賓,還玩起「台北來的朋友揮揮手」、「東部來的朋友揮揮手」、「香港來的朋友揮揮手」遊戲,到最後不管問哪裡來的大家都揮手,哈!開心就好~6:30準時起跑!

    第二次跑,路線又完全相同,新鮮感不如上回,但鳳梨馬的風景在我參加的賽事之中仍屬前段班。

    不意外地,會看到很多鳳梨!(看到鳳梨葉束起來包住果實的密技,原來是為了防曬

    揹了水袋,帶了RX100M3相機,今天抱定主意來踏青的,就慢慢跑囉。

    除了鳳梨,今年賽道上的另一主角是開花到無法無天的九重葛!另外,路過一段去年沒印象的「獅子櫻花大道」,仍留著一些櫻花,是意外收獲。

    補給維持水準,質跟量都挺好。但我跑後段班,跟半馬重疊的水站經過時東西都喀得差不多,還被水站的大哥大姐虧「誰叫你們愛報全馬,剛才被半馬的吃光了啦」,哈!所幸最後10K為全馬專屬路段,如願怒吃鳳梨,狂飲鳳梨汁,開心!

    最後10K,氣溫約28溫,但太陽好大,去年勉強SUB5,今年跑完25K便心理有數不可能保5,那就放水流吧,不急不急。

    最後,以05:25:33完成我的第24馬。雖然本屆不再現場列印成績單,但通過終點沒多久就收到成績簡訊,連總排名次跟分組名次都有,這樣也不賴。

    賽後補給照例是海鮮鹹粥(好吃)、礦泉水、鳳梨汁兩小瓶(去年為一大瓶)、長毛巾。

    獎牌很可愛!

          

    在會場看到三位跑友正巧湊齊10馬、30馬、60馬獎,三馬合體機不可失,上前借拍了一張留念!

    12點出頭就到對面消防隊等接駁車,等了好久。中間還目賭了6小時10分的大會關門。時間一到就移動交通錐調整路線,讓被關門的跑友提前右轉進入校園,連通過終點拱門的機會都沒有:「跑那麼慢,門都沒有!」

    等到近一點接駁車終於來了,再遇上來時的香港跑友團,就在帶著廣東話異國風的海外馬氛圍中,為本次賽事劃下句點。

    後記:

    不知是先前已有前兆早就中了感冒正要發難,還是跑完馬拉松後抵抗力驟降真有其事,總之隔日週一開始我就深陷半夜喉痛、卡車輾過等級全身酸痛、輕微發燒、鼻塞、流鼻水的感冒流程,整整一週才算全然脫身。但安然跑完才發作,也算是幸運囉~


    NG筆記26-再談Angular物件化寫法與JavaScript壓縮注意事項

    $
    0
    0

    專案再遇到Angular JavaScript原本執行正常啟用壓縮(Minification)後冒出找不到nPrivder、bProvider等錯誤訊息,這是Angular Dependency Injection機制因壓縮變數更名崩壞的典型案例,但訊息詭異(nProvider、bProvider是啥鬼?)常讓新手迷惑,特寫篇筆記補充。

    用一個簡單的ViewModel及Service範例來說明。

    開始前,先花一點時間說明Class化(物件化)的概念。雖然在許多Angular教學中,寫個function就能做出Controller、Service,實務上我都建議將ViewModel、Service寫成Class(JavaScript的Class寫法跟Function相似,Function本身可視為為建構式,內部用this.*定義屬性及方法),並將每個Class獨立成單一JavaScript方便管理。更進一步,Class還可改用TypeScript撰寫,享受編譯檢查、繼承、介面等強型別專屬的好處。(但在此先以純JavaScript為例,避免失焦)

    Class化還有另一項好處:未來在Angular 2.0,Directive、Service、ViewModel等將全面物件化(搭配TypeScript或ES6的Class語法),現階段避用Controller接入$scope加工掛屬性的傳統寫法,改成將ViewModel物件化並直接繫結物件屬性,較容易與Angular 2.0接軌。

    如下例,ng-controller寫成ctrl as vm,vm.writeLog()即對應到ViewModel物件的wirteLog()方法,而不是$scope.writeLog()。

    <!DOCTYPEhtml>
    <htmlng-app="app">
    <bodyng-controller="ctrl as vm">
    <buttonng-click="vm.writeLog('Test')">Test</button>
    <scriptsrc="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
    <script src="app.js"></script>
    </body>
    </html>

    在app.js宣告myViewModel類別,以controller("ctrl", myViewModel)將其註冊成Controller,注意動作邏輯被寫在this.writeLog而非$scope.writeLog,但ViewModel仍需取得$scope,以便應用$watch、$digest等Angular機制 。

    //將Service跟ViewModel寫成Class,實務上還會獨立一個Class一個js檔
    //而用TypeScript來寫Service及ViewModel類別是更好的選擇
    function myLoggerService($log) {
    this.log = function (msg) {
            $log.log(msg);
        }
    }
     
    function myViewModel($scope, myLogger) {
    this.writeLog = function (msg) {
            myLogger.log(msg);
        };
    }
     
    angular.module("app", [])
    //註冊服務或Controller時,直接給函式名稱當參數
    //Angular神奇的DI機制會自動找到$log、$scope、myLogger
    //傳給myLoggerService及myViewModel
        .service("myLogger", myLoggerService)
        .controller("ctrl", myViewModel);

    回到壓縮問題(跳一下)。myLoggerService建構時需要$log參數,myViewModel建構時需要$scope、myLogger(服務),但下方只寫了.service("myLogger", myLoggerService)及.controller("ctrl", myViewModel),建構函式卻能正確接收$logger或myLogger,這都歸功於Angular的依賴注入機制,它能解析參數名稱取得對應的物件、服務。然而,JavaScript壓縮機制會將區域變數更名為單一字母以節省空間,上述的程式碼在壓縮後,myLoggerService的$log被改成n,myViewModel的$scope跟myLogger被改成n及t:

    function myLoggerService(n){this.log=function(t){n.log(t)}}function myViewModel(n,t){this.writeLog=function(n){t.log(n)}}angular.module("app",[]).service("myLogger",myLoggerService).controller("ctrl",myViewModel);
    //# sourceMappingURL=app.min.js.map

    如此一來,Angular靠變數名稱取得對應物件的做法當場破功!

    針對這個問題,Angular提供兩種解法:

    function myLoggerService($log) {
    this.log = function (msg) {
            $log.log(msg);
        }
    }
     
    function myViewModel($scope, myLogger) {
    this.writeLog = function (msg) {
            myLogger.log(msg);
        };
    }
    myViewModel.$inject = ["$scope", "myLogger"];
     
    angular.module("app", [])
    //克服JS壓縮DI失效問題
    //方法一:使用陣列式宣告,依序放入參數名稱字串,函式殿後
        .service("myLogger", ["$log", myLoggerService])
    //方法二:使用$inject宣告參數名稱陣列
        .controller("ctrl", myViewModel);

    方法一,傳入函式的地方改寫成陣列,依序放入參數名稱字串,函式當成陣列最後一個元素。

    方法二,為函式加上$inject屬性,指定參數名稱字串陣列。(可讀性更高,推薦使用)

    由於參數名稱以字串常數存在,壓縮時會完整保留,而Angular能識別這兩種特殊寫法,DI機制就又活起來了!撰寫NG程式時,記得養成習慣。

    function myLoggerService(n){this.log=function(t){n.log(t)}}function myViewModel(n,t){this.writeLog=function(n){t.log(n)}}myViewModel.$inject=["$scope","myLogger"];angular.module("app",[]).service("myLogger",["$log",myLoggerService]).controller("ctrl",myViewModel);
    //# sourceMappingURL=appFixed.min.js.map

    [NG系列]

    http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

    NG筆記27-呼叫Directive內部函式

    $
    0
    0

    開始寫Directive之後,常面臨一個難題:Directive提供函式執行特定作業,當我們在DOM中引用Directive,如先前介紹透過Isolated Scope宣告{ callback: "&" },就可輕鬆由Directive呼叫外部Scope的Callback函式;但反過來,外部Scope要怎麼觸發Isolated Scope內部的函式呢?

    歷經一些研究、嘗試,我找到幾種做法,整理分享如下。

    先說明範例情境,我寫了一個無聊的Directive serverTime,它透過$http.get("/")向伺服器隨便發一個GET Request,再由Response headers["date"]偷出伺服器時間。Directive的Isolated Scope有個function refresh(),每次呼叫時重新由伺服器取得時間,藉以驗證Directive內部函式已執行。

    方法一是我認為最標準的做法,外部Scope需額外提供一個Trigger屬性,Directive使用$scope.$watch() Trigger屬性,每次Trigger值改變(可用Trigger = new Date()或指定為數字再Trigger++)就立刻執行refresh(),程式碼如下:(註:使用物件化形式寫ViewModel及Diretive,並配合$injector處理依賴注入,細節說明可參考前一篇文章Online Demo


    <!DOCTYPEhtml>
    <htmlng-app="app">
    <head>
    <metacharset="utf-8">
    <title>Directive Communication: $watch</title>
    </head>
    <bodyng-controller="ctrl as vm">
    <div>
    <spanng-bind="vm.Time"></span>
    <buttonng-click="vm.Refresh()">Refresh</button>
    </div>
    <hr/>
    <divserver-timetime="vm.Time"trigger="vm.Trigger"></div>
     
    <scriptsrc="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
    <script>
    function myViewModel($scope) {
    var self = this;
                self.Time = null;
                self.Trigger = 0;
                self.Refresh = function () {
                    self.Trigger++;
                }
            }
            myViewModel.$injector = ["$scope"];
    function serverTime($http) {
    return {
                    scope: {
                        time: "=",
                        trigger: "="
                    },
                    link: function (scope, element, attr) {
    function refresh() {
                            $http.get("/").success(function (data, status, headers) {
                                scope.time = headers("date");
                            });
                        }
                        scope.$watch("trigger", function () {
                            refresh();
                        });
                    },
                    template: "<pre>Server Time : {{time}}</pre>"
                };
            }
            serverTime.$inject = ["$http"];
     
            angular.module("app", [])
            .controller("ctrl", myViewModel)
            .directive("serverTime", serverTime);
    </script>
    </body>
    </html>

    copyText();

    這種做法很符合MVVM精神,由Directive自行訂閱指定屬性,在其改變時做出適當反應。ViewModel與Directive完全獨立,彼此不依賴。但為此額外要多設一個屬性(Trigger),而「改變Trigger值的目的是為了觸發指定函式」的概念有些迂迴,算是缺點。

    方法二,使用$broadcast()與$on()。Angular在Scope間可用$scope.$on註冊事件(想像成jQuery.bind()),並透過$broadcast()觸發所屬子Scope的指定事件、$emit()觸發所屬父Scope的指定事件(如同jQuery.trigger())。因此,只要在Directive $scope.$on("refresh-svr-time", refresh),在ViewModel中$scope.$broadcast("refressh-svr-time")即可達到由ViewModel觸發Directive refresh()的目的。Online Demo


    function myViewModel($scope) {
    var self = this;
                self.Time = null;
                self.Refresh = function () {
                    $scope.$broadcast("refresh-svr-time");
                }
            }
            myViewModel.$injector = ["$scope"];
    function serverTime($http) {
    return {
                    scope: {
                        time: "=",
                    },
                    link: function (scope, element, attr) {
    function refresh() {
                            $http.get("/").success(function (data, status, headers) {
                                scope.time = headers("date");
                            });
                        }
                        refresh();
                        scope.$on("refresh-svr-time", function () {
                            refresh();
                        });
                    },
                    template: "<pre>Server Time : {{time}}</pre>"
                };
            }
            serverTime.$inject = ["$http"];

    copyText();

    使用$broadcast()/$on()傳遞訊息,ViewModel與Directive仍維持不直接接觸,不用多設Trigger屬性,用$broadcast()觸發事件也比較直覺。但"refresh-svr-time"事件名稱的出現,意味著Directive邏輯滲入ViewModel端,二者的彼此獨立性略遜方式一。

    方法三,ViewModel增加供雙向繫結的物件屬性(我喜歡叫它ApiProxy),Directive建立時,動態在ApiProxy注入一個物件,其中可安插各式函式、屬性,成為ViewModel與Directive間溝通的橋樑,ViewModel即可輕易使用Directive主動外露的狀態屬性及方法。Online Demo


    function myViewModel($scope) {
    var self = this;
                self.Time = null;
                self.ApiProxy;
                self.Refresh = function () {
                    self.ApiProxy && self.ApiProxy.Refresh && self.ApiProxy.Refresh();
                }
            }
            myViewModel.$injector = ["$scope"];
    function serverTime($http) {
    return {
                    scope: {
                        time: "=",
                        apiProxy: "="
                    },
                    link: function (scope, element, attr) {
    function refresh() {
                            $http.get("/").success(function (data, status, headers) {
                                scope.time = headers("date");
                            });
                        }
                        refresh();
                        scope.apiProxy = {
                            Refresh: refresh
                        };
     
                    },
                    template: "<pre>Server Time : {{time}}</pre>"
                };
            }
            serverTime.$inject = ["$http"];

    copyText();

    由於ApiProxy物件可加入任意屬性、方法,是我認為最直覺最彈性的做法。實務上可用TypeScript定義專屬Class規範ApiProxy的型別,確保ViewModel正確存取ApiProxy的屬性及方法,減少程式出錯可能。但ApiProxy做法固然簡便,卻隱藏一項危機:ViewModel必須知道ApiProxy物件規格 ,在ViewModel摻雜Directive邏輯,使二者產生相依性,有違SoC準則。

    以上是我所知道三種ViewModel呼叫Directive內部函式的方法,各有優劣,大家偏好哪一種?歡迎回饋。

    [NG系列]

    http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

    2015三重全國馬拉松

    $
    0
    0

    第25馬,三重全國馬拉松。

    趁著初春天氣還沒熱起來要趕點業績,報名前爬過文,三重馬口碑普普,但念在剛好能串成雙週馬又離家不遠,在河濱道磨磨腳也沒什麼不好,便報了名。

    鳳梨馬完感冒一整週,比賽前又連下好幾天雨,練習、備戰什麼的就甭提了,GPS錶還雪上加霜按鈕失靈掛點… 就當LSD隨便跑跑囉~

    五點多到達會場,細雨綿綿不絕,放眼望去一片迷濛。大家全躲在橋下,不拖到最後一分鐘,沒人想站在起跑點淋雨。 XD

    GH625M送修,改戴Bryton C40,擔心重演去年5小時不到就沒電的慘劇,決定等要起跑再開機,不料大會居然提早兩分鐘起跑,驚險地在槍響前一刻GPS定位完成。

    不知是否定位時間過短加上開了省電模式,GPS軌跡開始奇幻漂流,兩度表演輕功水上飄,還數度上演一分速的神奇配速,距離也莫名灌水成45K變成超馬… orz

    雖然整場大半時間都在下雨,但地面積水不嚴重,小心腳步就能保住鞋襪不溼,比起前兩場25度多雲,17度微雨的天氣反而更舒適宜人,而天雨河濱冷清許多,還省去與自行車爭道的困擾,其實該說是天公作美。

    跑過不少河濱馬,相較之下三重馬的路線挺好,從重陽橋一路沿左岸向北跑到八里,再折回原點剛好21K,半馬一趟,全馬兩趟,路線單純好掌握。路面平整寬敞,參賽人少不必人擠人,補給簡單但充足(跟參賽人數少也有關,小而美的賽事是我的菜),加以天氣涼爽,天時地利之下,整體感受超乎預期,與網路評價的落差,反倒讓人驚豔。

    一路跑到關渡大橋,對岸望去一片白茫茫,也算詩情畫意。

    沿路在叉路口有都有大會人員導引,裁判騎機車巡邏救援的密度挺高,但沒有里程標示是美中不足。

    跑第二趟才發現,折返點就在有名的媽媽嘴咖啡。

    就這樣,輕踩油門滑完42K,以4:47:18再下一馬,SUB5+1。

    賽後沒有便當或餐點,但有大浴巾,蒸餾水及綠豆湯米苔目吃到飽,也不錯啦!

    最後一定要介紹大會完賽紀念品,3D雷射雕刻水晶球,裝電池會發光的「宇宙大跑者」~

    如展示影片,LED還會變色哩!只是從後面看,跑者好像沒穿衣服,為史上最歡樂的完賽獎座按個讚!

    影片

    TFS Build Service筆記

    $
    0
    0

    公司的專案版控由VSS換到TFS版已經很久了,自動組建(Build)專案的工作原本靠CruiseControl.NET搞定,一直沒研究如何改接TFS,但漸漸陷入危機,CCNET主機裝在Windows 2003,註定與.NET 4.5專案無緣,非升級不可。一不做二不休,索性棄守CCNET,另建TFS Build Service處理新專案。

    第一次玩TFS Build Service,一路不斷踩到水坑跌進洞裡,耗費多時,終於Build第一個專案! 記錄斑斑血淚於後:

    比較之下,安裝TFS Build Service反而是程序最單純的部分,細節可參考Franma的圖形化安裝手冊(連結為2012版,2013版在這裡),在此不多贅述,僅筆記我遇到的大小狀況及心得。

    TFS Build Service裝好,下一步是要在Visual Studio的Team Explorer中建立組建定義(Build Definition),做法安裝手冊有介紹,一般小專案應該都能一舉成功。但不巧我試作的解決方案專案結構複雜,人生歷練豐富(.sln搬過家),Build Service的環境特殊,簡單任務頓成天堂路~

    問題1:

    設好組建定義,第一次建置時冒出The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk. 訊息。檢查TFS Build Service主機,有找到\MSBuild\Microsoft\VisualStudio\v11.0\,但其下沒有WebApplications目錄,猜想是TFS Build Service安裝的MSBuild版本不若Visual Studio安裝的版本齊全,未支援WebApplication型別。從已安裝VS的機器複製補足檔案是一種做法,但我決定在TFS Build Service上安裝Visual Studio 2013以求省事。

    問題2:

    裝好Visual Studio 2013,在MSBuild資料夾裡看到WebApplications目錄,心想這下OK了。沒想到繼續冒出The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets" was not found.錯誤!

    定神一看,才發現安裝VS2013後,MSBuild一併升級,原本的v11.0目錄變成v12.0,而TFS 2010 Build Service仍傻傻去找v11.0撲了空。爬文沒找到強制Build Agent改用v12.0的做法,但組建定義有地方可以指定MSBuild參數,如下圖補上/p:VisualStudoVersion=12.0可以解決。猜想應該可以從Build Controller設定,但我沒找出方法,十方大德若有人知曉請再補充。

    補充一點,組建定義預設在每次組建失敗會建立一個新任務進行追蹤,在實務上很有用,理論上程式碼得通過編譯才能簽入版控,編譯失敗意味著有人犯錯,就該追蹤到問題解決為止。但像是第一次嘗試TFS組建,過程難免會有些問題需要排除,此設定會產生一堆無意義任務(就還搞不定咩!有什麼好追蹤的?)及一波Mail通知轟炸,建議可先關閉,正常運作後再打開。(上圖的Create WorkItem on Failure)

    問題3:

    TFS Build Service所處的網段被禁止存取網際網路,故連不上NuGet主機,NuGet Package Restore功能宣告破功,Log冒出一堆找不到參照元件錯誤。最簡單的解決方法是將整個packages資料夾也簽入TFS,但缺點是未來每次新増或更新NuGet Package得一併簽入packages資料夾的異動;但這樣也有優點,Build Service不需要每次建置都重新用NuGet取檔,節省時間也節省網路傳輸,感覺更務實。packages資料簽入後,參照錯誤問題排除,另一個專案卻爆出錯誤:This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is D:\Works\1\APP\MyWeb\src\\.nuget\NuGet.targets.

    比對發現出錯的是後期建立的新專案,csproj多了以下片段,應是NuGet升級後增加的檢查:

    <TargetName="EnsureNuGetPackageBuildImports"BeforeTargets="PrepareForBuild">
    <PropertyGroup>
    <ErrorText>This project references NuGet package(s) that are missing 
    on this computer. Enable NuGet Package Restore to download them. 
     For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. 
    The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <ErrorCondition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')"
    Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))"/>
    </Target>

    移除上述設定可避免問題,但既然是新版NuGet行為,後續再開新專案預期仍會有類似困擾,補上.nuget\NuGet.targets才是治本之道。將.nugets資料夾也簽入TFS,錯誤就消失了。關於packages與.nuget簽入TFS的注意事項,可參考前文:NuGet Packages資料夾該不該加入TFS版控?

    問題4:

    這回花最多時間的地方在這一段,原因是先前解決方案(.sln)曾搬過目錄,由Boo/Web/MyWeb.sln移到Boo/MyWeb.sln,而NuGet Packages目錄跟著.sln,因此部分專案csproj的參數<HintPath>仍指向Boo/Web/packages。Visual Studio有許多奇妙法則能找到替代DLL,例如:所參照其他專案帶來的同Package DLL其他軟體用過的其他版本… packages路徑不對的問題一直被掩蓋,直到改用MSBuild才一次爆出來,必須全部修正才能繼續~

    要排除前述packages路徑錯誤,可將解決方案由本機刪除再重新由TFS取回,或另找一台電腦由TFS取得原始碼,重新用Visual Studio編譯就可讓錯誤現形。這也是我推薦在TFS Build Service主機安裝Visual Studio的理由之一,面對這類問題時特別能展現優勢。Build Agent每次執行會由TFS抓取最新版程式碼寫入Build Service主機的工作目錄,用VS開啟工作目錄下的.sln檔,會與MSBuild面對環境一致,較容易重現錯誤狀況。Build Agent的工作目錄可以查這裡:

    就這樣,使用Visual Studio開啟D:\Works下的sln檔案,手動組置重現錯鋘再一一排除,總算闖關成功。

    另外還有幾個TypeScript編譯失敗的小問題,也在更新VS2013 Update 4、TypeScript 1.4 for VS後陸續排除,在Build Service安裝VS2013確實讓射茶包難度降低許多。

    心得5:

    常見的專案檔案架構會類似這樣:sln放在/src下,packages目錄也在/src下,src下再分成一個專案一個資料夾。/src可能有多個sln,A.sln包含專案A1、A2、Share,B.sln包含B1、B2、B3、Share,放在同一個src下可共用Share專案及packages。在組建定義設定Source時,若設定Source Control Folder為/src,則每次組建時Build Service將下載src下的所有資料夾,把packages、A1、A2、Share、B1、B2、B3全部都抓回來。當我們只想編譯A.sln,多抓B1、B2、B3只是浪費頻寬,還拖慢組建時間。此時可善用組建定義Source Settings可分別列舉應下載(Active)及不要下載(Cloaked)項目的特性,組合出建置所需的最少檔案,提升效率。

    心得6:

    第一次使用Build Service編譯MVC後楞了一下,輸出資料夾只有bin下的一堆DLL檔案,Scripts、Contents、Views都不見了。後來才發現可部署的網站內容另外放在輸出資料夾的_PublishedWebsites子目錄下,裡面的內容相當於Visual Studio發佈網站的產出結果。

    心得7:

    Build Service預設提供了幾種建置流程範本(Build Process Template),決定建置流程有幾個步驟,如何抓Source Code、怎麼執行測試… 等等,還可定義控制參數,顯示於下方供使用者調整,這些細節都定義在Windows Workflow Foundation(WWF)規格的XAML檔案,可用Visual Studio編譯修改,為專案量身打造專屬的建置流程,微軟很貼心地在UI 附上教學。呃… 這下連說「我不會」的機會都沒了,夠狠!(淚)

    不過,如果只是要在建置時加入執行外部程式、複製檔案等步驟,可考慮在csproj加入額外步驟,用Condition="'$(BuildingInsideVisualStudio)' == ''" 的技巧,設定成MSBuild建置時才觸發(使用Visual Studio建置時不執行),會比客製建置流程範本簡單。

    <TargetName="Copy Files"Condition="'$(BuildingInsideVisualStudio)' == ''"
    AfterTargets="Build">
    <Exec
    Command="xcopy &quot;$(SolutionDir)\Web\Boo&quot; &quot;$(DeployPath)\&quot;"
    Condition="'$(DeployPath)' != ''">
    </Exec>
    </Target>

    【延伸閱讀】

    使用管理者身分執行cmd.exe

    $
    0
    0

    UAC在我的心目中是好東西,雖然有不少人討厭它,但就像開車要繫安全帶,忍受一些不方便降低風險,永遠是值得的。

    Windows桌面操作或檔案總管在需要管理者權限的場合會主動彈出提示,倒不用特別費心去記什麼動作需要提升權限。

    但IT人員比較常遇到的困擾是命令提示字元視窗(cmd.exe,俗稱DOS視窗),若未使用管理者身分開啟cmd.exe,執行重啟IIS(iisreset)、停用啟動服務(net stop/start)等系統層級操作就得吃閉門羹。

    除了如上圖靠右鍵選單的「以系統管理員身分執行」,從Widows Vista起有一個小密技,在開始選單輸入cmd再按Ctrl-Shift-Enter,Windows也會以管理者身分啟動cmd.exe。

    到了Windows 8,我習慣裝Classic Shell找回開始選單,Ctrl-Shift-Enter小技巧仍然管用。

    最近遇到一個狀況,登入 Windows 2012 主機準備開啟cmd下指令,沒有Classic Shell可用,我的直覺是在左下角視窗圖示按滑鼠右鍵找到「執行」:

    輸入cmd按Ctrl-Shift-Enter:

    咦… 開啟的是一般使用者模式cmd.exe。反覆試了好幾次都不成功,懷疑鍵盤故障,還是Windows 2012已拿掉此快速鍵?直到用Windows 8交叉測試,才確認「執行」視窗不支援Ctrl-Shift-Enter。

    幾經嘗試,發現Ctrl-Shift-Enter要用在Windows 8/Windows 2012的搜尋常用鍵(Search Charm,可按Window+Q叫出)才有效。如下圖,在Search Charm輸入cmd,選擇命令提示字元(Command Prompt)再按Ctrl-Shift-Enter,就能以管理者身分執行cmd.exe。

    爬文過程學到另外一招,在PowerShell使用指令Start-Process "cmd.exe" –Verb RunAs,也能以管理者身分執行cmd.exe,筆記之。

    2015-04-17 補充:更簡便的做法來了!按Window+X,再按A,就能立即呼喚管理者身分cmd.exe,感謝Josch補充。

    電器耗電知多少?

    $
    0
    0

    聽過「家電不用拔插頭可省下可觀電費」的說法,對其成效半信半疑。依常理,具備待機功能可遙控開機的電視、音響,帶有時鐘的微波爐,即便沒開機肯定也要吃電,但耗電多寡成謎。而如寬頻數據機、無線基地台,幾乎都是24小時開機,我對一天要花多少電費卻毫無概念。另外,手機充電器在充飽後會不會繼續耗電?插在插座上不接手機會耗電嗎?筆電真的比個人電腦省電很多嗎?… 對於電器耗電量,我有一肚子的疑問。

    要解開久懸的疑惑,方法只有一個...

    薑薑薑薑~想了很久的插座式簡易瓦特計終於入手了。

    心想這玩意兒買來玩個幾次就會打入冷宮,差不多算耗材,網拍上300出頭的便宜貨應該堪用。

    兵器到手,發揮「一鎚握掌心,萬物皆成釘」的精神,測遍家中雙手可及的大小電器,一解多年疑惑。

    • 省電燈泡
      以前就知道省電燈泡要暖機一陣子才會達到最大亮度,這個特性也反應在耗電上。
      13W(瓦特)省電燈泡:通電13.6W,30秒後上升到14.8W左右
      20W省電燈泡:通電20.0W,2分鐘後上升至23.6W
    • 高耗能家電
      吹風機 700瓦、吸麈器1285瓦、烤箱1460瓦,關機0瓦
      微波爐開最強火力時耗電1577瓦,待機模式耗電1.3瓦
    • 37"液晶電視
      開機狀態193W,待機模式0W(非LED背光型,跟規格標示195W相近,支援遙控開機所需電力比想像低)
    • 筆電 vs PC
      VAIO SVT13 i5 13" SSD:正常使用 12W、螢幕關閉 8W、睡眠模式 0W
      PC E6400 + 2 HDD:開機 110W CPU滿載後上升至130W,但睡眠或「關機」時仍耗電2.1W。
      重要心得:PC的電源供應器即便在「電腦關機」下仍需供電給主機板(例如網路卡訊號燈會閃、USB插座仍有電),耗電約2瓦。PC的耗電量將近筆電的10倍,而且這數字還不包括液晶螢幕用電。
    • 電腦液晶螢幕
      23" LCD:開機19W,待機0W ,17" LCD:開機16W,待機0W
      結果液晶螢幕耗電比整台筆電還高
    • 電子防潮箱
      全力除濕時耗電31W
    • 35公分電風扇
      規格標示功率 60W,實測 弱風 48W / 中風 52W / 強風 55W (強弱風的差異比想像小)
    • 除濕機
      運轉時耗電206W
    • 空氣清淨機
      強中弱風分別耗 97W / 83W / 69W
    • 電動按摩棒
      強 40W / 弱 36W。令人驚奇的是,它的整流器(上方照片中的那一顆)插電後就算沒接按摩器,也要耗電3.1W。
    • 開飲機
      24小時運作的開飲機,據說是耗電大戶,經實測果然名不虛傳,保溫模式耗電 50W,加熱模式 670W。
    • 有線電視數位機上盒
      內部跑Linux的小黑盒子,開機模式耗電6W,待機模式也有5.2W。
    • 寬頻數據機(內建無線基地台)
      耗電4W,沒想像來得吃電
    • 平板手機充電器
      充電狀態:Nexus 7 7.4W、iPad 8W、Nokia Lumia 920 2.7W。充飽電後0W,插電不接手機也是0W。
    • 冰箱、冷氣、洗衣機
      插頭形狀不合或插座位置太刁鑽,放棄未測。

    胡亂測試一輪,歸納不負責心得以下:

    1. 桌上型電腦的耗電超過筆電(文書等級)10倍以上,甚至一台電腦液晶螢幕的耗電都比筆電高。而PC內450W電源供應器在電腦關機狀況下,仍會耗電2W。
    2. 大部分的整流器(例如手機、平板充電器,機上盒、基地台電源)未接負載時耗電趨近於0。換句話說,整流器留在插座上造成的電力損耗有限。
      但也有例外,例如照片中神奇的電動按摩棒14V 2A整流器,光插上插座就是3W起跳。
    3. 開飲機果然是耗電大戶,就算一直在保溫模式,也相當於24小時開著電風扇。
    4. 設備待機電力需求差異甚大,視其設計而定。例如:37"液晶電視為0瓦,有線電視機上盒則高達5瓦。
    5. 吸塵器、烤箱、微波爐等均為用電破千瓦的吃電怪物,請節約使用。
    6. 不同廠牌、型號的設備耗電有別,都要實測才知,但基本上跟規格標示接近。

    怕有人不知道,還是補充一下:1000W用一小時等於一度電,取得電器耗電瓦數,除以1000再乘上小時數,即可得用電度數,用這個公式就能估算家中電器要花多少電費。

    報告完畢!

    IIS更新通告:MS15-034

    $
    0
    0

    依據iThome的報導,資安研究機構SANS最近公告了一個IIS漏洞:微軟於4/14發佈的安全公告MS15-034,提及一個從Windows 7 SP1起存在於HTTP.SYS的安全漏洞,讓攻擊者有機會透過HTTP Request癱瘓系統,甚至有可能從遠端執行程式碼(聽起來又是緩衝區溢位漏洞)。安全公告發佈的同時,微軟也一併釋出了更新KB3042553,意思是:莫等待,莫依賴,快把IIS更新裝起來!(受影響範圍包含Windows 7/Windows 2008R2/Windows 8/Windows 8.1/Windows 2012/Windows 2012R2)

    報導提到要測試漏洞可模擬以下GET Request,看是否會得到Request Range Not Satisfiable回應:

    GET / HTTP/1.1
    Host: MS15034
    Range: bytes=0-18446744073709551615

    手癢想實證一下,便開啟一台久未更新的Windows 2008 VM,利用上回介紹過的Telent密技模擬GET Request。

    果真看到HTTP 416 Request Range Not Satisfiable,代表漏洞存在。

    接著執行Windows Update,特別挑出KB3042553安裝。

    安裝KB3042553後再做一次相同測試,HTTP 416狀態變成HTTP 400 Bad Request,象徵漏洞修復。

    好奇一點:那IIS6呢?Windows 2003未列於MS15-034公告,使用前述方法測試也不會回應HTTP 416,研判不在此波曝險範圍。

    所以結論是使用Windows 2003的朋友可以繼續安心使用?喂!不要亂教啦。

    事實是,Windows 2003將於2015/7/14終止支援,已是破百的待退老鳥,之後由於不再有安全修補及Hotfix,維運風險將大幅升高,大家手邊如果還有老而彌堅的Windows 2003,建議儘早安排升級以策安全。


    在TFS 2012 Build Service使用Robocopy實現自動部署

    $
    0
    0

    聲明:本文應用情境為TFS 2012,TFS 2013可在組建定義加掛PowerShell Script於建置前後執行,應比本文介紹的做法簡便。(請參考Franma的文章

    使用TFS Build Service成功建置專案後,我們希望做到建置後自動上傳測試台或UAT驗收主機的目標。考慮過幾種做法,包含自訂建置流程範本、撰寫派送服務,最後決定將部署作業編寫放進csproj,工程最小,依賴性最低,較符合KISS原則。

    之前對MSBuild的粗淺研究,知道csproj本身就是個MSBuild設定檔,我們可以在其中用Target包裝一連串動作,使用<Exec Command="">可以在其中執行XCOPY等DOS指令,並能指定執行條件Condition及時機。例如:AfterTargets="Build"代表在建置後執行、Condition="'$(ParamName)' == 'Y'"可以用參數控制執行與否。

    由於Build Service與測試台/UAT主機間網路頻寬有限,若每次部署都複製整個網站目錄,耗時甚久(傳輸整個網站需數十分鐘),只複製有更新檔案是較有效率的做法。好用的Robocopy可使用/XO參數限定只複製存檔時間較新的檔案,是過去常用的部署工具。但遇到一個問題,預設Build Service每次會清空Workspace重新由TFS下載,導致網站所有的css、js、html、jpg/png/gif的檔案時間都被改成建置時間,Robocopy的檔案時間比對特性當場破功。

    經過一番研究,找到一個組建定義 Clean Workspaces:它有三個選項,All/Outpus/None,選 All 會刪除輸出及原始碼資料夾,形同完全重新建置;選 Outputs 則刪除輸出資料夾保留原始碼資料夾,只下載上次建置後有修改的檔案(Incremental Get,遞增式下載);選擇 None 會只更新有修改檔案且保留輸出資料夾。

    透過Incremental Get,在第一次建置之後,每次建置只會更新修改過的檔案會,便能實現部署時只複製更過的目標。(補充:TypeScript、SCSS每次建置時會重新產生js, min.js, css, min.css,故這部分較難避免重複更新)

    在csproj中安裝Robocopy作業,會遇到幾項挑戰:

    1. Robocopy作業應限定Build Service建置時才執行
      關於這點,在<Target>加上Condition="'$(BuildingInsideVisualStudio)' == ''"可限定非使用Visual Studio建置時才執行。但更精準的做法可指定特定參數控制,例如:Condition="'$(DeployAction)' == 'Test'",配合MSBuild執行參數控制部署行為。
    2. Robocopy Exit Code問題
      Robocopy使用0以外的Exit Code傳回執行結果,與一般DOS程式Exit Code不等於0代表出錯的慣例不符,會被MSBuild誤判為執行失敗,針對此可加上IF %ERRORLEVEL% LSS 8邏輯,將Exit Code 0 - 7 轉為 Exit 0。
    3. <Exec Command="" >執行多行程式
      承2,Robocopy配合IF %ERRORLEVEL%需寫成兩行,MSBuild XML規則裡有項密技,在Command字串雙引號間直接換行就搞定了:(或使用&#xD;&#xA;代表換行符號也成)
      <Exec Command="Line1
      Line2
      Line3">
    4. 排除不想覆寫的檔案
      由於web.config、NLog.config在不同主機設定不盡相同,加上設定常包含敏感資料如連線字串、API網址等,不適合Check In TFS。故我們習慣部署時不覆寫config檔,視需求再手動修改。
      Robocopy有/X*參數可排除特定檔案,我選擇另一種做法,編譯後執行DEL將指定檔案刪除,但要記得加上/Q /F避開確認詢問及強制刪除唯讀檔。
    5. Build Controller執行身分
      由於Robocopy使用網路資料夾方式將檔案複製到遠端主機,最簡單的做法是以網域帳號當作Build Controller的執行身份,並授與該帳號存取遠端主機網路資料夾的權限。

    開啟csproj,加入以下內容,MSBuild會在DeployAction參數等於Test時,於建置完成後刪除*.config檔,並用robocopy將較新的檔案複製到DeployTargetPath所指定的UNC位址。

    </ProjectExtensions>
    <ImportProject="$(SolutionDir)\.nuget\NuGet.targets"
    Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')"/>
    <TargetName="DeployAction"Condition="'$(DeployAction)' == 'Test'"AfterTargets="Build">
    <MessageText="Delete config..."/>
    <ExecCommand="del $(WebProjectOutputDir)\*.config /Q /F"></Exec>
    <MessageText="Deploy to $(DeployTargetPath)..."/>
    <ExecCommand=
    "robocopy &quot;$(WebProjectOutputDir)&quot; &quot;$(DeployTargetPath)&quot; /S /E /copy:DAT /XO
    if %errorlevel% lss 8 exit 0 else exit %errorlevel%"Condition="'$(DeployTargetPath)' != ''">
    </Exec>
    </Target>

    要啟用上述作業,必須指定DeployAction及DeployTargetPath兩個MSBuild參數,可在組建定義 MSBuild Arguments屬性指定。

    每次Queue New Build時,也還有機會調整。

    就這樣,每次使用建置時,Build Service便會一併將更新旳檔案部署到遠端主機上,總算實現部署自動化的夢想。

    HTTPS網站被Chrome打臉?

    $
    0
    0

    接獲報案,某網站的SSL圖示忽然被Chrome打上紅叉叉,https字眼也被劃掉,有種駭客正站在你背後的驚悚感。檢視該網站SSL憑證尚未到期,改用Firefox、IE檢視並無異樣,只有Chrome在連線資訊提及沒有公開稽核記錄、安全性設定已過時、使用過舊密碼編譯法等缺失。

    以上提到的缺失並不算新鮮事。

    公開稽核記錄是指網站經第三方單位稽核無誤,跟花錢取得 CAS ISO認證差不多意思,目的在提升客戶信心。而具備公開稽核記錄的憑證,Chrome或IE還會在地址列標示組織名稱以顯尊榮,例如:

     

    不過,電子商務應用或強調資安的網站較有動機申請第三方稽核提升公信力(畢竟要成本),像是Google、Facebook、YouTube的SSL憑證也都沒有公開稽核記錄。嚴格來說,沒有公開稽核記錄不算缺失。

    至於「安全性設定過時」、「過舊的密碼編譯法」就比較嚴重…

    故事是這樣的(以下會扯到一些密碼學術語,我盡量說人話),現在大家廣泛使用的網頁加密技術(SSL),網站需要具備一張由憑證廠商或組織(CA)簽發的數位憑證(Certificate),數位憑證包含所謂的數位簽章(Signature)。數位簽章會用到一種稱為雜湊(Hash)的演算規則,過去數位憑證採用的雜湊演算法以SHA1為主。依照密碼學,數位簽章有可能被暴力破解、偽造(用電腦不斷嘗試各種組合試出答案),但只要雜湊或加密演算法的強度足夠,即便用全世界最快的電腦也得花上數千上萬年才能破解,就能有效阻擋駭客攻擊,稱得上安全。問題來了,依據摩爾定律,電腦每兩年的性能就會提高一倍,於2012年有研究指出,依電腦的演進速度,可以預期在幾年之後,SHA1的強度便不足以抵抗新一代電腦的攻擊。意思是只要大爺有錢,在雲端弄一堆機器組成電腦大軍,在短時間內破解數位簽章跟加密並非不可能的事,而花費成本每三年就會降到原來的1/3。很明顯地,SHA1被破解的風險在未來幾年內會逐年升愈高,安全性堪慮。(話雖如此,真要破解仍得花上幾百萬)。

    於是,微軟宣布從2016年起停用採SHA1演算法的數位憑證,接著Google也跟進,但Google的政策就讓大家有感多了。Chrome瀏覽器開始針對採用SHA1的SSL憑證發出警告,並逐步加重力道!

    Chrome 39版(2014/9)遇到網站使用SHA1憑證時,會在HTTPS鎖頭圖示加註黃三角,Chrome 40版(2014/11)將鎖頭圖示改成白紙,到了Chrome 41版(2015 Q1),鎖頭圖示再次出現,並加上令人驚心的紅叉叉,就是本案例所看到的樣子。[參考]

    這就是原本好好的網站,忽然在Chrome出現紅叉叉鎖頭的原因!隨著客戶端陸續更新Chrome版本,海水一退,就知道哪些網站穿著短褲…

    要解決這個問題很簡單,將SSL憑證換成SHA256版本就好了!而這是網站管理者要煩惱的問題,一般的使用者看到這裡就夠了。

    如果你是網站管理者,SSL憑證到期更新及申請新憑證時,記得要確認取得皂是SHA256版的(但應該也不會有CA再簽發SHA1版了吧?)。若現行的SHA1憑證仍有效,依到期日不同,會有不同的結果,不一定會出現紅叉叉。由於SHA1疑慮屬預防性的警示,較大的風險發生在未來,而且愈晚愈嚴重。故Chrome 41使用以下邏輯判斷憑證的安全程度:

    2016/1/1以前到期的憑證,反正很快要換新成SHA256版,被視為安全。

    2016/1/1-2016/12/31到期憑證,有點危險(因為2016電腦又變快了,攻擊難度下降),標為鎖頭加黃三角。

    2017/1/1以後到期的憑證,遇上的電腦就更凶狠了,故標示為鎖頭加紅叉叉。

    以上是網友佛心製作的SSL憑證檢查工具(http://sha1affected.com/),輸入網址就幫你分析憑證狀況並預估在各版Chrome下的顯示狀態,對網站SSL憑證有疑慮的朋友可多加利用。

    因此,若網站現有憑證到期日在2016/1/1以後,Chrome 40/41使用者用HTTPS連上網站時,將出現鎖頭加黃三角或鎖頭加紅叉叉的狀況。網站管理者可請CA廠商或組織重新簽發SHA256版本,一般不會收費,但得花功夫處理,基於避免使用者誤解的立場,更換掉還是比較好。

    不得不說,Google這一招對於撲殺SHA1憑證很有效果。雖然此舉為部分網站管理者多添一椿麻煩事,從提升全球資安的角度來看,還是要為Google按個讚!

    【延伸閱讀】

    行動瀏覽效果將影響Google搜尋排名

    $
    0
    0

    不久前接到Google Webmaster Tool通知,說我的網站有超過2000個網頁不利行動瀏覽,造成智慧型手機使用者閱讀困難。登入網站管理員工具一看,冒出一個行動裝置可用性報表。嘖,幾乎所有的網頁都不及格!問題包括字型過小、觸控元素距離太近不易點擊、未設定檢視點(ViewPort)、未依檢視點調整內容大小(不需橫向捲動就能看到完整內容)… 等。

    部落格的CSS Style是八年前定稿的,當初做夢也想不到,時代演變到人手一機成天滑。另一方面,許多因應行動裝置的HTML、CSS規格都是近幾年才制定的。在自動步槍已是基本配備的時代,還拿著抗戰時期的盒子炮跟人火拼,下場當然不是慘字可以形容的。嗯,這一年剛好工作上對行動瀏覽有點研究,找時間來改改吧… (謎:「找時間」是吧?我懂)

    備好拖字訣正要把頭埋進砂裡,Google在4/21發佈了一則重大消息,殺手鐧來了:

    Google搜尋將更新演算法,調降行動瀏覽不友善網頁在搜尋結果的排名。此一改變主要發生在行動裝置搜尋,影響範圍以個別網頁為單位,但會影響所有語言的搜尋結果。

    換句話說,不支援行動裝置正式成為拖累SEO的負面因素了!就像Chrome封殺SHA1憑證一樣,Google真的很知道怎麼逼迫大家駛向偉大的航道呀~(淚)

    Google很貼心地提供了檢測工具(且在缺失處都會附上說明及教學,算是佛心到底了),輸入網址後擷取網頁進行分析,診斷該網頁是否存在阻礙行動裝置瀏覽的缺失。輸入部落格網址一測:在手機上搞出如米雕般的細緻文字是不被允許的,零分!

    默默打開HTML及CSS,加上<meta name="viewport" … >,加入一段@media max-width整調字型大小,並透過float: left改成流動式(Fluid)式排版,所幸當初選用高人分享的CSS版型很有遠見,字型設定採百分比或em等相對比例,調整範圍不算大。

    修改後的檢視效果不怎麼稱頭,反正缺乏美感天分外加生性懶散,我的網站從來就跟精緻美觀方便使用扯不上邊(自暴自棄中)。至少,經過一番調整,已大幅消除行動瀏覽障礙,就算達成目標。

    無法逐一檢視所有網頁,大家如發現不妥的地方,請留言或在FB專頁回饋給我,感謝~

    讓ODP.NET查詢快10倍的小密技-FetchSize

    $
    0
    0

    同事報案,某專案使用ODP.NET+Dapper查詢一萬筆資料要耗時三分鐘,而同樣查詢丟到PL/SQL Developer跑只要15秒。為了洗刷.NET效能不佳的罪名,立刻出發調查。

    我實做一個簡單測試重現問題,在我的i7機器執行,查詢取回10,691筆耗時34.794秒;用PL/SQL Developer查詢測得11.453秒,足足慢了3倍。

    using Oracle.DataAccess.Client;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Test
    {
    class Program
        {
    staticvoid Main(string[] args)
            {
                var cs = "data source=blah;user id=foo;password=bar";
    using (var cn = new OracleConnection(cs))
                {
                    var sw = new Stopwatch();
                    sw.Start();
                    cn.Open();
                    var cmd = cn.CreateCommand();
                    cmd.CommandText = @"
                        SELECT A_Lot_Of_Columns 
                        FROM Some_Table 
                        WHERE Some_Column = 'A' 
                          AND Some_Flag <> 'F' 
                        ORDER BY Another_Column";
                    var dr = cmd.ExecuteReader();
                    var count = 0;
    while (dr.Read())
                        count++;
                    sw.Stop();
                    Console.WriteLine("{1:n0} rows in {0:n0} ms", 
                                      sw.ElapsedMilliseconds, count);
                }
                Console.ReadLine();
            }
        }
    }

    用ODP.NET+Slow關鍵字爬文,發現一椿天大的秘密。使用ODP.NET這麼多年,我竟不知有個參數-FetchSize會戲劇化地影響ODP.NET查詢效能。

    Oracle雜誌有篇文章ODP.NET: Improve ODP.NET Performance提到加速ODP.NET的三樣法寶:Connection Pooling、FetchSize以及Statement Cache。其中FetchSize指的是每次DataReader向DB讀取資料一次取回的資料量,預設FetchSize為131072(128KB)。換句話說,若查詢結果有128MB,則Reader要從DB讀取1024次才能把資料讀完。每次發動讀取都有收發封包及處理的額外成本(Overhead),增加每次讀取資料量可以降低讀取次數,即可提升效能(跟JavaScript、CSS打包、圖檔Sprite相同道理)。然而,增加每次讀取資料量將耗用較多記憶體(例如:只有1KB資料,ODP.NET也得向系統要求1MB記憶體,雖然用完可回收,但要求及回收大量記憶體會消耗CPU/IO資源),故開發人員要在效能與記憶體使用間拿捏。依我個人看法,在這記憶體成本日益下降的時代,處理速度與使用者滿意度、老闆的笑容與自己的荷包息息相關,拿記憶體換效能絕對划算。

    以上述程式為例,我們可修改程式,在dr.Read()之前調整FetchSize,讓資料區大小足夠每次讀取入2,000筆:

                    var dr = cmd.ExecuteReader();
    //指定FetchSize,每次讀取2000筆
                    dr.FetchSize = dr.RowSize * 2000;
                    var count = 0;
    while (dr.Read())
                        count++;

    原本讀完10,691筆要34秒,猜看看變幾秒?

    3.387秒!

    我驚呆了!快了10倍,這麼多年來,居然不知道有這招?而FetchSize在單筆資料量大、筆數龐大的場合,最能展現威力。在本例中,RowSize約1K,1萬筆資料量約10MB,原本128KB要讀78次,修改FetchSize後只需讀6次,產生10倍的速度差異。

    未來大家使用ODP.NET讀取大量資料如遇效能不佳,加入一行FetchSize設定就可能脫胎換骨,可多加利用。

    2015台北星光夜跑

    $
    0
    0

    第26馬,21-25度稍熱但一路涼風強勁,天氣絕佳,無奈跑前兩天又感冒惹,只能帶著不時發癢,永遠清不完的喉嚨上陣。

        

        

    賽前大會特定發了通知提醒有12台小巴士輪迴接駁,一台還沒坐滿開走,下一台已經停在後面待命,根本BRT,貼心。大佳河濱公園的會場好大,寄物區跟主舞台有數百公尺之遙,加上現場混雜不少隔天早上Nike女生半馬的設施,摸索了一陣子才搞清楚方位。像是胖卡旁有好大一排「21K半馬領物區」,我心裡還納悶,跑21K的人有這麼多嗎?那全馬又要去哪裡領?寄物領物幹嘛要分開?為什麼跟會場地圖標的不一樣?(偷偷說,跑完第一圈我才會意過來 XD)

             

    水站不提供紙杯,寄物時大會發送環保杯一只,杯蓋附吊繩可以掛手上綁腰包,比石碇馬版本更方便。

    四點整起跑,多雲偶有日頭,風透清涼,毫無燥熱感,好棒的天氣!(為什麼我要感冒呢? orz)

          

    路線從大佳河濱公園出發向東到百齡橋,過橋到右岸回頭往西,跑到麥帥橋回到左岸,再往東跑回大佳公園剛好21K,半馬一趟全馬兩趟。會場及沿路看到許多隔天Nike女生半馬的帳篷跟里程路標,由於終點相同,一大段的里程標示跟星光夜跑一致,但氣人的是尿急看到一整排流動廁所,卻被封起來明天才給用 XD…

    今年起,台北市政府嚴格規範路跑活動的場地與數量加上今天冒出路權要收費的消息,未來應該有很多機會跑這段賽道。

    揹了水袋,一兩公里就補幾口潤喉,降低脫水機率也避免在水站猛灌一肚子水難受,格外適合我的猛暴型飆汗體質。這場補給不賴,水站志工超熱情,一手礦泉水一手運動飲料吆喝著幫跑友倒水。大家都用環保杯,少了滿坑滿谷的廢棄紙杯,環保之餘也乾淨清爽,讚!不過,親切的「斟酒服務」恐怕仍是小而美賽事的專利,上萬人的場子很難這麼玩。另外,補給也挺精彩,梅子、檸檬、小萫茄、巧克力、梅粉、小餐包、可樂、沙士、鹹蛋… 還吃到令人驚豔的滷豆干以及脆甜到逆天的大西瓜,開心。

    感冒體能不佳,愈跑愈慢,在17公里處,我被三太子刷卡了 我被三太子刷卡了 我被三太子刷卡了…

    好不容易跑完第一圈,大會持續廣播「全馬選手請靠右通過不要過感應拱門」 望著終點拱門想到這才跑完一半,心裡偷偷罵了一句髒話 XD

          

    繼17公里被神明刷卡,在27公里處我又被一根香蕉(穿香蕉裝的跑友)刷卡… 嗯,有了這番經歷,想必我的跑馬人生又昇華到另一層境界惹。

          

    5:30:26回到終點,會場LED已亮,一片燈海。腳伸過去感應就能直接列印成績,發現關門前半小時回來,排名居然在前55%左右,大概三團浩浩蕩蕩的百馬團還在路上玩吧?XD

    星星形狀的獎牌,造型蠻別緻的。

          

    在涼爽的晚風中,連五場雙週馬的緊湊排程暫且劃上句點。下一場,復仇之戰即將登場~

    Viewing all 2433 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>