Problem Using INSTEAD OF Triggers with Entity Framework

0 comments
若你在使用識別資料行做為主索引鍵的資料表中,定義 INSTEAD OF INSERT 觸發程序,並透過 ObjectContext 的 SaveChanges 方法新增資料時,將會引發 OptimisticConcurrencyException,並導致整個交易復原。以下是發生例外狀況的錯誤訊息:
存放區更新、插入或刪除陳述式影響到非預期數目的資料列 (0)。這些實體載入之後可能被修改或刪除了。請重新整理 ObjectStateManager 實體。

Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

如以下範例,會依據 AdventureWorks 實體資料模型的定義,將新的實體物件附加到物件內容:
using (AdventureWorksEntities context = new AdventureWorksEntities()) 
{
context.AddToSalesReason(new SalesReason() {
Name = "Recommendation", ReasonType = "Other", ModifiedDate = DateTime.Now
});
context.SaveChanges();
}

當呼叫 SaveChanges 方法時,物件服務會產生及執行以下 SQL 命令:
exec sp_executesql N'insert [Sales].[SalesReason]([Name], [ReasonType], [ModifiedDate])
values (@0, @1, @2)
select [SalesReasonID]
from [Sales].[SalesReason]
where @@ROWCOUNT > 0 and [SalesReasonID] = scope_identity()
'
,N'@0 nvarchar(14),@1 nvarchar(5),@2 datetime2(7)'
,@0=N'Recommendation',@1=N'Other',@2='2009-11-02 15:14:54.4856524'

因為該實體物件包含識別插入的實體索引鍵,所以會在 INSERT 陳述式後,搭配使用 SCOPE_IDENTITY 函數以取得插入識別欄位中的最後一個識別值。然而,如果此資料表已定義 INSTEAD OF INSERT 觸發程序,則 SCOPE_IDENTITY 函數會因為分屬不同的範圍(Scope)而傳回 NULL 值,於是導致 SaveChanges 失敗,而復原該筆交易並擲回如前述的 OptimisticConcurrencyException 例外狀況。

要解決這個問題,你可以使用預儲程序取代 INSTEAD OF INSERT 觸發程序,然後利用實體資料模型設計工具(Entity Designer),把實體類型的修改函式對應至預存程序。或者,你也可以使用 ExecuteCommand 擴充方法來直接執行新增作業的預儲程序。

繼續閱讀...

How to Fix "Unable to update the EntitySet" Error

0 comments
如果你嘗試對資料表實體集進行新增、修改、刪除資料時,有可能會發生無法更新實體集的例外狀況。以下是更新資料時所引發的錯誤訊息:
System.Data.UpdateException: 無法更新 EntitySet '實體集名稱',因為它有 DefiningQuery,但是在 <ModificationFunctionMapping> 項目中沒有 <UpdateFunction> 項目來支援目前的作業。

Unable to update the EntitySet 'EntitySet Name' because it has a DefiningQuery and no <UpdateFunction> element exists in the <ModificationFunctionMapping> element to support the current operation.

當你將不含主索引鍵的資料表加入實體資料模型(Entity Data Model,EDM)時,Entity Framework 會因為找不到資料表主索引鍵,而將定義建立成唯讀的資料表,也就是在描述儲存結構之 SSDL 區段所定義的 EntitySet 項目中,加入 DefiningQuery 項目以對應到資料存放區的唯讀檢視。所以,當實體集使用定義查詢時,你就無法使用標準更新處理序將保存更新至資料來源。如果嘗試對唯讀的實體集進行更新動作,將會引發如上所述的例外狀況。

要解決這個問題,你可以為來源資料表加入主索引鍵,並透過更新資料庫模型來獲得解決。或者,你也可以利用實體資料模型支援使用預儲程序的功能,定義插入、更新、刪除函式對應來進行資料更新。

繼續閱讀...

在 .NET 應用程式中執行 T-SQL 指令碼檔

0 comments
當你在 .NET 應用程式,使用 ADO.NET 提交包含 GO 指令的 T-SQL 批次時,SQL Server 會引發的語法不正確的錯誤。這是因為 GO 指令是 sqlcmd 、osql 等公用程式和 SQL Server 指令碼編輯器所認識的指令,而非有效的 T-SQL 陳述式。

在執行含有一個以上 T-SQL 批次的指令碼時,SQL Server 公用程式會將 GO 視為 T-SQL 陳述式批次結束的信號,並將目前一個或多個 SQL 陳述式的集合傳送至 SQL Server,但並不包含 GO 指令。因此,當你使用 ADO.NET 執行多個 T-SQL 批次的指令碼時,你必須自行過濾 GO 指令,並分批執行 T-SQL 陳述式。

如以下範例使用 EmbeddedResourceTextReader 讀取內嵌資源的 SQL 指令碼檔內容,並透過 Regex 物件以 GO 關鍵字將指令碼分隔成批次陳述式,然後再逐一提交至 SQL Server 執行:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Text.RegularExpressions;

namespace RunSql
{
class Program
{
static void Main(string[] args)
{
string script = new EmbeddedResourceTextReader()
.GetFromResources("RunSql.Install.sql");

string[] stmts = Regex.Split(script, "\\sGO\\s", RegexOptions.IgnoreCase);

using (SqlConnection conn =
new SqlConnection(ConfigurationManager
.ConnectionStrings["DefaultConnection"]
.ConnectionString))
{
conn.Open();
using (SqlTransaction trans =
conn.BeginTransaction(IsolationLevel.ReadUncommitted))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
cmd.CommandType = CommandType.Text;

foreach (string stmt in stmts)
{
cmd.CommandText = stmt.Trim();
if (cmd.CommandText.Length > 0)
{
try
{
cmd.ExecuteNonQuery();
}
catch(SqlException)
{
trans.Rollback();
throw;
}
}
}
}
trans.Commit();
}
}
}
}
}

相較於 ADO.NET,使用 SQL Server Management Objects(SMO)就無須處理 GO 指令的問題,使用起來更為簡便。有關如何使用 SMO 執行 T-SQL 批次的範例程式碼,請參閱這裡

繼續閱讀...

Using DHTML Tooltips with AJAX

0 comments
DHTML Tooltips 是一個跨瀏覽器相容的程式庫,提供豐富的功能讓你可以輕鬆建立、自訂多樣的工具提示(Tooltip)或是彈跳式訊息(Pup-up Box)。

程式庫提供 Tip 函式接受訊息字串參數,而 TagToTip 函式則允許你提供元素的識別名稱以顯示其 HTML 的內容。這兩個函式的用法都相當簡單,在此就不再贅述,本文將會側重在兩者的併用,並透過範例逐步說明告訴你,如何結合 jQuery 的應用來動態載入伺服器所提供的提示訊息。

在以下的範例網頁片段中,你會看到座位平面圖,以及其所組成映射區域,並且繫結要實作動態提示訊息的 onmouseover 事件處理函式。
<img src="Images/seatingplan.png" usemap="#seatingPlan" />
<map name="seatingPlan">
<area shape="rect" id="Front Stalls" coords="22,100,493,236"
onmouseover="showSectionInfo(this, 61);" />
<area shape="rect" id="Rear Stalls" coords="22,252,492,388"
onmouseover="showSectionInfo(this, 62);" />
</map>

在定義 onmouseover 事件處理函式之前,我們先在 jQuery 的 DOM ready 事件中,預先建立一個供資料讀取的過程中顯示處理訊息的隱藏區塊,並將 Tooltips 程式庫中的 UnTip 函式繫結所有映射區域元素的 onmouseout 事件處理常式。
$(document).ready(function() {
$('<div></div>')
.attr('id', 'ajaxLoading')
.html('Loading...')
.hide()
.appendTo('body');

$("map[name='seatingPlan'] *")
.mouseout(UnTip);
});

當映射區域元素 onmouseover 事件被觸發時,將會執行以下函式並將所接收的傳遞參數做為請求參數,來進行遠端呼叫:
function showSectionInfo(element, sectionId) {
if (data(element) === undefined) {
tooltip.call(element, 'ajaxLoading');
$.ajax({
url: 'InfoHandler.ashx',
type: 'GET',
data: {
id: sectionId
},
dataType: 'html',
error: function(xhr) {
UnTip();
alert('The ajax request failed.');
},
success: function(response) {
data(element, response);
tooltip.call(element);
}
});
} else {
tooltip.call(element);
}
}

在叫用 jQuery 的 ajax 函式處理非同步請求前,會先透過以下所定義的 tooltip 函式叫用 TagToTip 函式將先前所建立的隱藏訊息顯示出來,以回應使用者的請求:
function tooltip(elementId) {
var options = [ABOVE, true, SHADOW, true];
if (elementId === undefined) {
Tip.apply(this, $.merge([data(this)], options));
} else {
TagToTip.apply(this, $.merge([elementId], options));
}
}

當 AJAX 請求成功後,會透過如下的 data 函式將回應結果暫存到元素物件中,提供給後續可能的相同觸發動作讀取之用,以避免冗餘的資料請求。
function data(element, value) {
return value === undefined ? $.data(element, 'tooltip') :
$.data(element, 'tooltip', value);
}

最後,再次呼叫 tooltip 函式將回應結果委由 Tip 函式顯示出來。

繼續閱讀...

jQuery Floating Layer Plugin

3 comments
在許多網站設計中,浮動圖層(Floating Layer)經常被應用在較長的網頁文件中,以呈現能隨著瀏覽器的捲軸移動的浮動廣告或選單。

Floating Layer Plugin 是以 jQuery 原型物件(Prototype Object)為基礎所擴充的外掛程式庫。相較於先前版本,這次的版本除了將程式碼最佳化外,也增添了若干的新功能。

makeFloating( [options] )
透過元素選取(Selector)將特定的 DOM 元素封裝為具有 jQuery 函式功能的外覆物件後,你可以呼叫此函式來建立自訂的浮動圖層。函式可以接受傳遞物件參數來提供選項設定屬性,如以下所示:
屬性型別說明
positionobject指定 x 和 y 屬性來控制浮動區塊的定位方式。除了絕對座標來定位外,你也可以在 x 軸指定 "left"、"right"、"center" 決定水平位置,在 y 軸指定 "top"、"bottom"、"center" 來決定垂直位置。
範例:
  • {x:0, y:250}
  • {x:'right', y:'top'}
durationnumber指定動畫的顯示速度,以毫秒為單位。
easingstring指定變速移動的特效。
fixedboolean指定是否要固定圖層位置而不隨捲軸移動。

應用範例
首先,除了必要的 jQuery 程式庫外,你還需要下載本程式庫並引入到你的網頁中。然後,在網頁的文件的主體中加入區塊圖層,如以下範例:
<div id="floatlayer" style="
width: 50px;
height: 50px;
border: solid 1px #cccccc;
background-color: #d0d0ff;
z-index: 100;
display: none">
<!-- Place your content here -->
</div>

接下來,你可以處理 jQuery 的 DOM ready 事件,在網頁載入完成後,選取如上的圖層元素並叫用 makeFloating 函式:
$(document).ready(function() {
$('#floatlayer').makeFloating();
});

在預設的情況下,浮動圖層會配合網頁的可見區域以水平置左、垂直置中的方式定位,且會隨瀏覽器的捲軸移動產生擺動(Swing)的動畫效果。如果要變更浮動圖層的預設行為,你可以透過物件參數提供自訂的選項設定。如以下範例會建立水平置右、垂直置中,並加入線性(Linear)動畫效果的浮動圖層:
$('#floatlayer').makeFloating({
position: { x: 'right', y: 'center' },
easing: 'linear'
});

因為程式所表現的動畫效果是透過 jQuery 的 animate 函式來實現,所以 easing 參數只能支援內建的 linearswing 兩種特效。如果內建的動畫效果不能符合你的期望,建議你可以搭配 Easing Plugin 來擴增多種慢入(Easing In)和慢出(Easing Out)的動畫效果,如以下範例就使用了更為順暢的慢出特效:
$('#floatlayer').makeFloating({
position: { x: 'right', y: 'center' },
easing: 'easeOutBounce'
});

當然,如果你不需要動態移動的效果,你也可以透過物件參數的 fixed 屬性來固定圖層的位置:
$('#floatlayer').makeFloating({
position: { x: 'right', y: 'center' },
fixed: true
});

另外,程式庫還提供 floatingPosition 函式,讓你可以在建立浮動圖層物件後,重新定位浮動的目標位置,如以下範例:
$('#floatlayer').floatingPosition({ x: 'center', y: 'center' });


Download jquery.floatinglayer.zip

繼續閱讀...

在 Chrome 2.0 中為 JavaScript 偵錯

0 comments
和大多數人一樣,瀏覽器本身對我來說並不重要,它只是瀏覽網頁、使用網路應用的工具而已。相較於瀏覽器的功能性,快速、穩定的基本要素更能貼近我的需求。因此,強調簡潔、快速的 Google Chrome 瀏覽器,很快就在我心中佔據了一個無可取代的地位。

Chrome 不僅滿足使用者對瀏覽器的根本訴求,也從開發人員需求實務上的觀點設計許多的功能,希望能藉以協助開發人員提供更好的使用者經驗。在開發人員的工具中,最令我感興趣的,卻也是對我極度不友善而讓我望之卻步的,莫過於基於命令提示介面的 JavaScript 偵錯工具(JavaScript Debugger)了。當然,這對於既自詡為 Chrome 愛用者,又身兼網頁開發人員的我來說,似乎令人難以悅服。於是,最近我又再度興起要深入研究這個好用的小工具的念頭,如今在此分享自己的學習心得及蒐集的相關資訊,希望能對有興趣的朋友有所幫助。

命令
在 JavaScript 偵錯工具中,可使用的命令會依目前是否處於執行中或是中斷狀態而有所不同。在執行狀態中,也就是網頁尚未執行暫止於中斷點時的狀態下,可以使用的命令如下:
命令說明
b[reak] <function | script:function | script:line | script:line:pos> [condition]在來源中的位置或函式設定中斷點。
break_info [breakpoint #]

bi [breakpoint #]
顯示目前所有中斷點資訊,或是已指定中斷點的資訊。
clear <breakpoint #>移除已指定的中斷點。
h[elp] [command]顯示所有命令的描述,或是已指定命令的詳細描述。
p[rint] <expression>評估運算式並輸出執行結果。
scripts顯示所有可偵錯的指令碼資訊。
註:[] 表示可省略或選擇性項目; <> 表示必須提供的項目。

當網頁暫止於中斷點的狀態時,可以使用的命令如下:
命令說明
args顯示目前函數的參數。
b[reak] [<function | script:function | script:line | script:line:pos> [condition]]在來源中的位置或函式設定中斷點,或是不使用任何參數停止偵錯並終止程式執行。
break_info [breakpoint #]

bi [breakpoint #]
顯示目前所有中斷點資訊,或是已指定中斷點的資訊。
backtrace [from frame #] [to frame #]

bt [from frame #] [to frame #]
顯示目前函式的呼叫堆疊(Call Stack)。
clear <breakpoint #>移除已指定的中斷點。
c[ontinue]繼續執行到下一個中斷點。
dir <expression>顯示物件的詳細資訊。
f[rame] <frame #>顯示目前堆疊框架(Stack Frame)的偵錯資訊,或是已指定堆疊框架的偵錯資訊。
h[elp] [command]

? [command]
顯示所有命令的描述,或是已指定命令的詳細描述。
locals顯示目前堆疊框架中所有變數的值。
n[ext]逐步執行每行指令碼,如果是執行函式呼叫,則會進入函式內的第一行指令碼。
p[rint] <expression>評估運算式並輸出執行結果。
scripts顯示所有可偵錯的指令碼資訊。
source [from line] | [<from line> <num lines>]

ls [from line] | [<from line> <num lines>]
顯示目前堆疊框架的原始碼,或從指定的行號開始顯示。
s[tep]逐步執行每行指令碼,如果是執行函式呼叫,則不會進入函式直接執行。
stepout

so
在目前的函式中,繼續執行指令碼直到函式返回,然後在呼叫函式的返回點中斷。

範例網頁
為了幫助你更快熟悉偵錯命令的應用,將會利用以下範例網頁,用逐步解說的的方式進行偵錯示範。
<html>
<head>
<title>Sample Page</title>
<script type="text/javascript" src="shape.js"></script>
<script type="text/javascript">
function Rectangle(x, y, w, h, cnv) {
Shape.call(this, x, y);
this.width = w;
this.height = h;
this.convas = cnv;
return true;
}

Rectangle.prototype = new Shape();
Rectangle.prototype.draw = function() {
if(this.convas) {
this.convas.innerHTML += "Drawing a Rectangle at:" + this.getCoordinates() +
", width " + this.width + ", height " + this.height + "<br/>";
}
};

function drawRectangle() {
var x = parseInt(document.getElementById("x").value);
var y = parseInt(document.getElementById("y").value);
var w = parseInt(document.getElementById("w").value);
var h = parseInt(document.getElementById("h").value);
var cnv = document.getElementById("cnv");
var rect = new Rectangle(x, y, w, h, cnv);
rect.draw();
}
</script>
</head>
<body>
x:<input id="x" type="text" size="3" /> y:<input id="y" type="text" size="3" />
width:<input id="w" type="text" size="3" /> height:<input id="h" type="text" size="3" />
<input type="button" value="Draw Rectangle" onclick="javascript:drawRectangle();" />
<div id="cnv"></div>
</body>
</html>

在範例網頁中引入一個外部的 js 檔案,其內容如下:
function Shape(x, y) {
this.x = x;
this.y = y;
return true;
}

Shape.prototype = {
getCoordinates : function() {
return "(" + this.x + " ," + this.y + ")";
}
};

當你在 Chrome 中開啟範例網頁後,請按一下 [網頁功能] 功能表,然後在 [開發人員選項] 點選 [為 JavaScript 偵錯] 或是使用鍵盤捷徑 [Ctrl+Shift+L],這時 JavaScript 偵錯工具便會開啟並附加在作用中的分頁。另外,你還需要 [檢視網頁原始碼],除了便於在偵錯期間檢視程式碼外,也可以利用原始碼的行號來設定中斷點。


偵錯逐步解說
在進行偵錯前,我們可以在命令列輸入 "scripts" 來檢視附加在範例網頁的指令碼資訊,如以下輸出結果:
$ scripts
file:///C:/shape.js (lines 11)
file:///C:/test.htm (lines 4-30)
[unnamed] (source:"javascript:void(0)")

如果你要將中斷點設在外部檔案中的 Shape 建構函式,你可以執行如下的命令:
$ break Shape
set breakpoint #1

除了指定函式中斷點外,你也可以指定原始碼的位置來設定中斷點。例如,若要將中斷點設在如下的指令碼行:
var rect = new Rectangle(x, y, w, h, cnv);

那麼,你可以先從 [檢視網頁原始碼] 中得知指令碼行所在行號,然後執行如下的命令:
$ break file:///C:/test.htm:28
set breakpoint #2

然而,上述設定中斷點的方式都屬於無條件中斷,事實上,你還可以選擇性地設定需同時符合特定條件的中斷點:
$ break file:///C:/test.htm:24 x<0
set breakpoint #3

這個命令將會在以下的原始碼位置設定中斷點,且變數 x 的值必須滿足特定條件才會暫止程式執行。
var y = parseInt(document.getElementById("y").value);

當你完成如上的中斷點設定後,便可以使用 "break_info" 檢視所有的中斷點資訊:
$ break_info
Num breakpoints: 3
id=1, hit_count=0, type=function, target=Shape
id=2, hit_count=0, type=script, target=file:///C:/test.htm, line=27
id=3, hit_count=0, type=script, target=file:///C:/test.htm, line=23, condition=x<0

現在,你可以回到範例網頁的頁籤,然後填入必要欄位並按下按紐執行指令碼。如果你在 x 欄位輸入的數值小於零的話,那麼程式就會暫止在第三個中斷點:
paused at breakpoint 3: drawRectangle(), file:///C:/test.htm
24: var y = parseInt(document.getElementById("y").value);

若要繼續執行指令碼,可以視情況選擇使用 "step" 或 "next" 命令來逐行執行指令碼:
$ next
25: var w = parseInt(document.getElementById("w").value);

或是,使用 "continue" 命令繼續執行到下一個中斷點,也就是之前所設定的第二個中斷點:
$ continue
paused at breakpoint 2: drawRectangle(), file:///C:/test.htm
28: var rect = new Rectangle(x, y, w, h, cnv);

當程式中斷執行時,可以輸入 "locals" 命令來檢視目前執行函數中的所有變數值:
$ locals
w = 400
cnv = #<an HTMLDivElement>
x = -10
y = 0
h = 600
rect = undefined

若程式繼續執行,將會暫止在第一個中斷點:
$ continue
paused at breakpoint 1: #.Shape(x=-10, y=0), file:///C:/shape.js
2: this.x = x;

在偵錯過程中,你可以選擇性地切換堆疊框架來進行偵錯。首先,你可以透過 "backtrace" 命令來檢視所有堆疊框架的資訊:
$ backtrace
Frames #0 to #4 of 5:
#00 #<an Object>.Shape(x=-10, y=0) file:///C:/shape.js line 2 column 12 (position 36)
#01 new Rectangle(x=-10, y=0, w=400, h=300, cnv=#<an HTMLDivElement>) file:///C:/test.htm line 7 column 15 (position 58)
#02 drawRectangle() file:///C:/test.htm line 28 column 20 (position 839)
#03 #<an HTMLInputElement>.[anonymous](evt=#<a MouseEvent>) file:///C:/test.htm line 37 column 12 (position 177)
#04 #<an HTMLInputElement>.onclick(evt=#<a MouseEvent>) file:///C:/test.htm line 38 column 4 (position 197)

然後,再使用 "frame" 的命令來切換到指定的堆疊框架,如以下所示:
$ frame 1
#01 Rectangle, undefined
7: Shape.call(this, x, y);

你可以視需要使用 "source" 命令來檢視目前堆疊框架的原始碼:
$ source
5:
6: function Rectangle(x, y, w, h, cnv) {
>>>> Shape.call(this, x, y);
8: this.width = w;
9: this.height = h;
10: this.convas = cnv;
11: return true;
12: }

如果不打算繼續執行指令碼,請使用 "break" 命令停止偵錯並終止程式執行:
$ break
JavaScript execution already stopped.


參考資料:
Sample debug session with Google Chrome JavaScript debuger
Basic information on Chrome's Debugger
Google Chrome JavaScriptデバッガ完全マニュアル

繼續閱讀...

CKIP Client for .NET

12 comments
CKIP 中文斷詞系統是由中文詞知識庫小組(Chinese Knowledge Information Processing Group,CKIP)所發展的線上斷詞服務。此服務採用 XML 資料交換模式,用戶端必須自行撰寫程式經由 TCP Scoket 傳送文本到斷詞系統,並剖析回傳包含斷詞及詞類標記的 XML 處理結果。

目前,CKIP Client 開放原始碼專案已實作 Java、PHP 兩種版本,可以用來簡化 CKIP 斷詞服務用戶端應用程式的開發,而不需自行撰寫 Socket 程式碼及處理 XML 資料。而本文的 CKIP Client for .NET 則是自己重新以 C# 實作 CLR 非同步程式撰寫模型(Asynchronous Programming Model)所設計的斷詞服務用戶端 APIs。

使用範例
下列範例會使用 CkipClient 在同步封鎖模式進行連接、傳送文本,並接收傳回結果,其中 username 及 password 為用戶端所申請之帳號及密碼。
using (CkipClient client = new CkipClient("username", "password"))
{
client.Send("菩提本無樹,明鏡亦非臺;本來無一物,何處惹塵埃?");
SegmentationResult result = client.GetResult();

if (result.StatusCode == StatusCode.Success)
{
StringBuilder sb = new StringBuilder();
foreach (Term term in result.GetTerms())
{
sb.Append(term.ToString() + " ");
}
Console.WriteLine(sb.ToString());
}
else
{
Console.WriteLine(string.Format("Error: {0}", result.StatusDescription));
}
}

輸出結果如下:
菩提(N) 本無樹(N) ,(COMMACATEGORY) 明鏡(N) 亦(ADV) 非(Vt) 臺(N) ;(SEMICOLONCATEGORY) 本來(ADV) 無(Vt) 一(DET) 物(N) ,(COMMACATEGORY) 何處(N) 惹(Vt) 塵埃(N) ?(QUESTIONCATEGORY)


非同步 API 使用範例
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using CKIP;

namespace AsyncCkipClient
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}

private void uxSend_Click(object sender, EventArgs e)
{
CkipClient client = new CkipClient();
client.BeginConnect(uxUsername.Text, uxPassword.Text,
new AsyncCallback(EndConnectCallback), client);
DisplayStatus("Connecting to server ...");
}

private void EndConnectCallback(IAsyncResult ar)
{
CkipClient client = (CkipClient)ar.AsyncState;

try
{
client.EndConnect(ar);

if (client.Connected)
{
client.BeginSend(uxRawText.Text,
new AsyncCallback(EndSendCallback), client);
DisplayStatus("Sending data to server ...");
}
else
{
DisplayStatus(string.Format("Ready (last error: {0})", "Connect Failed!"));
}
}
catch (Exception ex)
{
client.Close();
DisplayStatus(string.Format("Ready (last error: {0})", ex.Message));
}
}

private void EndSendCallback(IAsyncResult ar)
{
CkipClient client = (CkipClient)ar.AsyncState;

try
{
client.EndSend(ar);
client.BeginGetResult(new AsyncCallback(EndGetResultCallback), client);
DisplayStatus("Reading server response ...");
}
catch (Exception ex)
{
client.Close();
DisplayStatus(string.Format("Ready (last error: {0})", ex.Message));
}
}

private void EndGetResultCallback(IAsyncResult ar)
{
CkipClient client = (CkipClient)ar.AsyncState;
try
{
SegmentationResult result = client.EndGetResult(ar);

if (result.StatusCode == StatusCode.Success)
{
StringBuilder sb = new StringBuilder();
foreach (Term term in result.GetTerms())
{
sb.Append(term.ToString() + " ");
}
DisplayResults(sb.ToString());
}
else
{
DisplayStatus(string.Format("Ready (last error: {0})",
result.StatusDescription));
}
}
catch (Exception ex)
{
DisplayStatus(string.Format("Ready (last error: {0})", ex.Message));
}
finally
{
client.Close();
}
}

public void DisplayResults(string text)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(DisplayResults), text);
return;
}
uxResult.Text = text;
DisplayStatus("Done");
}

public void DisplayStatus(string text)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(DisplayStatus), text);
return;
}
uxStatus.Text = text;
}
}
}



Download source code

參考資料:
實作 CLR 非同步程式撰寫模型 (Programming Model)

繼續閱讀...

如何在資料庫存取組合旗標值

0 comments
在程式設計當中,如果要建立可以組合列舉值清單的位元旗標列舉型別時,我們會以 2 的乘冪來定義列舉常數。事實上,在資料庫設計中我們也可以沿用位元旗標列舉的概念,使用單一整數型別的欄位來儲存多個整數常值的組合旗標。

範例資料表
本文將藉以下是範例資料表來示範如何對數值欄位存取組合旗標:
CREATE TABLE [dbo].[User] ( 
[UserName] [varchar] (20) NOT NULL,
[Status] [int] NOT NULL DEFAULT 0
)

其中 Status 欄位可以儲存的整數常值以 2 的乘冪定義如下:
狀態整數二進位值
APPROVED10001
LOCKED_OUT20010
BANNED40100
DELETED81000

使用 2 的乘冪來定義整數常值,是為了確保在結合的常數中的個別旗標不會重疊。
DELETEDBANNEDLOCKED_OUTAPPROVED計算結果
00111+23
01011+45
11011+4+813

加入旗標值
這時我們所定義的常值便可透過位元 OR 運算來組合旗標值。
INSERT INTO [User] VALUES ('Nancy',1|2)
INSERT INTO [User] VALUES ('Robert',1|4)
INSERT INTO [User] VALUES ('Laura',1|2|4|8)
INSERT INTO [User] VALUES ('Andrew',0)
INSERT INTO [User] VALUES ('Janet',1)

當你要測試數值中是否包含特定旗標時,必須先將數值與特定的旗標值進行位元 AND 運算後,再將運算的結果與該旗標值進行相等比較。
SELECT * FROM [User] WHERE Status&2 = 2

UserNameStatus
Nancy3
Laura15

移除旗標值
若要在數值中移除特定的旗標值,可以將數值與旗標值進行位元互斥 OR 運算。
UPDATE [User]
SET Status = Status^4
WHERE UserName = 'Robert' AND Status&4 = 4

值得注意的是,在對數值與特定的旗標進行位元互斥 OR 運算前,務必先確認該旗標已設定在數值中再執行,否則若貿然對不存在的旗標進行位元互斥 OR 運算將會得到反效果。

參考資料:
Storing Multiple Statuses Using an Integer Column
SQL Server: Updating Integer Status Columns
Integer Based Bit Manipulation - SQL

繼續閱讀...

實作非同步模式的 TCP 用戶端

2 comments
TcpClient 類別是以 Socket 類別為基礎所建立的抽象層 TCP 服務,它提供簡單的方法以連接、傳送和接收網路間的資料,用來簡化 TCP 用戶端應用程式的開發。TcpClient 提供同步與非同步兩種通訊模式,本文將會著重探討如何使用非同步程式撰寫模型來處理網路服務要求。

同步模式
在一般的情況下我們都使用同步的封鎖模式來建立 Socket 應用程式,因為這種方式最簡單也最直接。以下範例將會以同步作業的方式發送 HTTP 請求:
using (TcpClient client = new TcpClient("www.msn.com", 80))
{
using (NetworkStream stream = client.GetStream())
{
byte[] send = Encoding.UTF8.GetBytes("GET HTTP/1.0 \r\n\r\n");
stream.Write(send, 0, send.Length);
byte[] bytes = new byte[client.ReceiveBufferSize];
int bytesRead = stream.Read(bytes, 0, (int)client.ReceiveBufferSize);
String data = Encoding.UTF8.GetString(bytes);
char[] unused = { (char)data[bytesRead] };
Console.WriteLine(data.TrimEnd(unused));
}
}

在上例中,我們使用 TcpClient 的建構式進行同步的連接嘗試,並透過 Stream 通訊端同步收發網路資料。在這段時間內,執行同步要求的執行緒會因為等候網路作業完成而無法再執行其他工作。如果該執行緒是 UI 執行緒,那麼應用程式就被封鎖並停止回應使用者的輸入。雖然使用同步封鎖的 Socket 從觀念上來說比較容易使用,然而如果你的應用程式需要保持快速回應、提升延展性及可靠性,那麼就不應該使用同步封鎖的作業方式。

非同步模式
非同步用戶端通訊端使用標準 .NET Framework 非同步程式撰寫模型,讓你的應用程式不會因為等候同步作業完成的執行緒而被封鎖。因為非同步作業(Asynchronous Operation)會在不同的執行緒上(在背景中)執行,所以應用程式可以在呼叫非同步方法(BeginOperationName)的執行緒上繼續執行指令。例如,在 Windows 應用程式中,非同步執行作業將會委派給背景執行緒,而使用者介面執行緒(在前景中)在作業執行時仍可以保持回應狀態。


接下來,我們將以 Windows Form 應用程式來逐步建構非同步傳輸的 HTTP 用戶端程式。首先,必須先建立用來在非同步呼叫間傳送狀態資訊的狀態物件。然後,呼叫 TcpClient 物件的 BeginConnect 方法開始遠端主機連接的非同步要求。
TcpClient client = new TcpClient();
StateObject state = new StateObject(client);
state.Data.AppendFormat("GET {0} HTTP/1.0\r\n", url);
state.Data.AppendFormat("Host:{0}\r\n\r\n", hostName);

client.BeginConnect(hostName, 0,
new AsyncCallback(EndConnectCallback), state);

當完成非同步連線要求時,系統會回呼實作 AsyncCallback 委派的 EndConnectCallback 方法。在這個方法中,我們必須呼叫 EndConnect 方法來結束擱置的非同步連接要求。下列程式碼實作了 EndConnectCallback 方法:
private void EndConnectCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
TcpClient client = state.Client;

try
{
client.EndConnect(ar);

if (client.Connected)
{
NetworkStream stream = client.GetStream();
if (stream.CanWrite)
{
byte[] send = Encoding.UTF8.GetBytes(state.Data.ToString());
stream.BeginWrite(send, 0, send.Length,
new AsyncCallback(EndWriteCallback), state);
}
}
else
{
DisplayStatus(string.Format("Ready (last error: {0})", "Connect Failed!"));
}
}
catch (Exception ex)
{
client.Close();
DisplayStatus(string.Format("Ready (last error: {0})", ex.Message));
}
}

在用戶端通訊端讀寫資料時,我們需要定義一個能儲存非同步作業相關資訊的狀態物件。
internal class StateObject
{
public readonly TcpClient Client;
public readonly byte[] Buffer;
public readonly StringBuilder Data;
public readonly int BufferSize = 1024;

public StateObject(TcpClient client)
{
this.Client = client;
this.Data = new StringBuilder();
this.Buffer = new byte[this.BufferSize];
}
}

當成功連接到遠端主機後,接著會透過 Stream 通訊端的 BeginWrite 方法以非同步方式將 HTTP 請求字串寫入網路資料流,並於作業完成後回呼實作 AsyncCallback 委派的 EndWriteCallback 方法,這個方法會在網路資料流做好接收準備時接收遠端主機回應的資料。
private void EndWriteCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
TcpClient client = state.Client;

try
{
NetworkStream stream = client.GetStream();
stream.EndWrite(ar);

state.Data.Length = 0;
if (stream.CanRead)
{
stream.BeginRead(state.Buffer, 0, state.BufferSize,
new AsyncCallback(EndReadCallback), state);
}
}
catch (Exception ex)
{
client.Close();
DisplayStatus(string.Format("Ready (last error: {0})", ex.Message));
}
}

接著會呼叫 Stream 通訊端的 BeginRead 方法,以非同步方式從用戶端通訊端讀取資料,並於作業完成後回呼實作 AsyncCallback 委派的 EndReadCallback 方法,將遠端主機回應的資料讀入資料緩衝區並建立訊息字串。然後再次呼叫 BeginRead 方法,直到用戶端接收資料完畢。
private void EndReadCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
TcpClient client = state.Client;
NetworkStream stream = client.GetStream();

int bytesRead = stream.EndRead(ar);

if (bytesRead > 0)
{
state.Data.Append(Encoding.UTF8.GetString(state.Buffer, 0, bytesRead));
stream.BeginRead(state.Buffer, 0, state.BufferSize,
new AsyncCallback(EndReadCallback), state);
}
else
{
client.Close();
DisplayResults(state.Data.ToString());
}
}

最後,呼叫 DisplayResults 方法將非同步讀取作業所建立訊息字串顯示在 TextBox 控制項。
public void DisplayResults(string text)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(DisplayResults), text);
return;
}

int hdrLen = 0;
if (-1 != (hdrLen = text.IndexOf("\r\n\r\n")))
{
uxHeader.Text = text.Substring(0, hdrLen);
char[] unused = { '\r', '\n', '\r', '\n' };
uxContent.Text = text.Substring(hdrLen).TrimStart(unused);
}
else
{
uxHeader.Text = string.Empty;
uxContent.Text = text;
}
}

因為 Windows Form 控制項的存取並沒有保證執行緒安全(Thread-safe),也就是說在當一個以上的執行緒同時存取共用資源時,就會發生競爭情形而導致狀態不一致或是死結的情況。所以,在多執行緒的表單中必須以安全執行緒的方式來存取控制項。因此,在 DisplayResults 方法中使用表單的 InvokeRequired 屬性來判斷呼叫端是否來自於建立控制項之執行緒以外的執行緒。如果是跨執行緒呼叫(Cross-Thread),則必須使用 Invoke 或是 BeginInvoke 方法來封送處理(Marshaling)至控制項的執行緒。當你在 Visual Studio 偵錯工具中執行應用程式,若有不安全的執行緒嘗試存取控制項時,偵錯工具會引發 InvalidOperationException 例外狀況:「跨執行緒作業無效: 存取控制項 control name 時所使用的執行緒與建立控制項的執行緒不同」。這也意味只有在偵錯期間才有可能發現這個例外狀況,如果應用程式完成建置與佈署,那麼即使有發生不安全的存取情況也不會引發錯誤,建議你在發現問題時加以修正。



Download sample code

參考資料:
Using an Asynchronous Client Socket
Asynchronous Client Socket Example
Asynchronous operations, pinning
以非同步的方式呼叫同步方法
使用非同步用戶端通訊端

繼續閱讀...

iTextSharp 中文字型解決方案

0 comments
iTextSharp 是由 iText(for Java)移植到 .NET 平台的開放原始碼專案。它是一個完全使用 C# 語言所撰寫的 PDF APIs,可以讓你即時建立 PDF 文件。然而,當你要使用 iTextSharp 來建立包含中文字型的文件時,就會面臨無法正常顯示中文的問題。這是因為 iTextSharp 只支援 14 個標準的 Type 1 英文字型,並沒有直接支援中文字型的顯示,取而代之的是你必須自行指定中文字型來源才能獲得解決,所以在此整理顯示中文字的幾種解決方案。

Windows 內建的中文字型
中文版 Windows 內建的中文字型檔可分為 True Type、True Type Collection 及 Open Type 三種字型格式。你可以在 %WINDIR%\Fonts 目錄下找到以 .ttf、.ttc 為延伸檔名的中文字型檔。以下範例將會使用標楷體字型來顯示文字:
Document document = new Document();
PdfWriter.GetInstance(
document,
new FileStream(@"cjk.pdf", FileMode.Create)
);
document.Open();

string fontPath = Environment.GetFolderPath(Environment.SpecialFolder.System) +
@"\..\Fonts\kaiu.ttf";
BaseFont bfChinese = BaseFont.CreateFont(
fontPath,
BaseFont.IDENTITY_H, //橫式中文
BaseFont.NOT_EMBEDDED
);
Font fontChinese = new Font(bfChinese, 16f, Font.NORMAL);
document.Add(new Paragraph(
"難得糊塗",
fontChinese
));
document.Close();

在上例中,你也可以使用 FontFactory 代替 BaseFont 來取得中文字型:
FontFactory.Register(fontPath);
Font fontChinese = FontFactory.GetFont("標楷體", BaseFont.IDENTITY_H, 16f);
document.Add(new Paragraph(
"吃虧是福",
fontChinese
));

CID 字型(Character Identity-keyed Fonts)
CID 字型格式通常被應用到中日韓(Chinese Japanese Korean, CJK)字元集。如果要使用 CJK 字型,你就必須搭配額外的亞洲語言包 iTextAsian-1.0.dll
Document document = new Document();
BaseFont.AddToResourceSearch("iTextAsian.dll");

PdfWriter.GetInstance(
document,
new FileStream(@"cjk.pdf", FileMode.Create)
);
document.Open();

BaseFont bfChinese = BaseFont.CreateFont(
"MHei-Medium",
"UniCNS-UCS2-H", // 橫式中文
BaseFont.NOT_EMBEDDED
);
Font fontChinese = new Font(bfChinese, 8);
document.Add(new Paragraph(
@"聰明難,糊塗難,
由聰明而轉入糊塗更難,
放一著,退一步,當下心安,非圖後來福報也。",
fontChinese
));
document.Close();

Download sample code

參考資料:
iText Tutorial: Getting fonts

繼續閱讀...

Printing PDF Documents using C#

0 comments
在報表應用中,PDF 是常被使用的文件格式,透過 iTextSharp 可以讓你即時(On-The-Fly)建立 PDF 文件。然而,如果你想自動將建立的文件內容輸出至印表機時,可能就會面臨苦無對策的窘境。因為,iTextSharp 並未提供實際的列印文件功能。所幸,這個問題可以藉由動態資料交換(Dynamic Data Exchange, DDE)技術獲得解決,也就是透過 DDE 從 Adobe Reader 來要求列印指定的文件檔案。在 .NET 中,我們可以使用 NDde 這個開放源碼專案,來輕鬆撰寫 DDE 連結的功能。

在接下來的程式碼範例中,除了參考「Printing PDF documents in C#」的做法外,同時亦採用 Clifton Nock 所著「Data Access Patterns」一書中的 Retryer Pattern。以下程式碼是實作 IRetryable 介面的類別,用來執行列印 PDF 文件的工作:
using System;
using System.Diagnostics;
using NDde;
using NDde.Client;

namespace PdfPrintExample
{
class PdfPrintOperation : IRetryable
{
private DdeClient _client;
private string _filePath;

public PdfPrintOperation(string filePath)
{
_filePath = filePath;
_client = new DdeClient("acroview", "control");
}

public bool Attemp()
{
try
{
_client.Connect();
return true;
}
catch (DdeException)
{
// ignore exception
}

return false;
}

public void Recover()
{
// try running Adobe Reader
Process p = new Process();
p.StartInfo.FileName = "AcroRd32.exe";
p.Start();
p.WaitForInputIdle();
}

public void Print()
{
_client.Execute("[DocOpen(\"" + _filePath + "\")]", 60000);
_client.Execute("[FilePrintSilent(\"" + _filePath + "\")]", 60000);
_client.Execute("[DocClose(\"" + _filePath + "\")]", 60000);
_client.Execute("[AppExit]", 60000);
}
}
}

以下是簡單的使用範例:
namespace PdfPrintExample
{
class Program
{
static void Main(string[] args)
{
PdfPrintOperation operation = new PdfPrintOperation(@"C:\sample.pdf");
Retryer retryer = new Retryer(operation);
retryer.Perform(10, 5000);
// print the file
operation.Print();
}
}
}

使用 Retryer Pattern 的好處在於連結 DDE Server 發生 DDEException 例外狀況時,可以自動嘗試重新連線,直到連接成功或是達到最大重試次數為止。

Download source code

參考資料:
Printing PDF documents in C#

繼續閱讀...