Spiga

ADO.NET 二進位資料存取 (SQL Server image 型態)

如果要使用 ADO.NET 存取 SQL Server 上的 image 欄位,其實只要使用 byte[] 陣列即可:



// 將檔案儲存到資料庫 image 欄位中
string filename = "c:\demo.jpg";
FileStream fs = new FileStream(filename, FileMode.Open);

// 用來儲存檔案的 byte 陣列,檔案有多大,陣列就有多大
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();

SqlConnection conn = new SqlConnection();
conn.ConnectionString = "";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "INSERT Table1(Body) VALUES(@Body)";
cmd.Parameters.Add("@Body", SqlDbType.Image);
cmd.Parameters["@Body"].Value = buffer;

conn.Open();
cmd.ExecuteNonQuery();
conn.Close();##ReadMore##

// 從資料表 image 欄位取得資料存檔
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT Body FROM Table1 WHERE FileID=1";

conn.Open();
object obj = cmd.ExecuteScalar();
conn.Close();

if (obj != null && obj != DBNull.Value)
{
// 用來儲存檔案的 byte 陣列,檔案有多大,陣列就有多大
byte[] buffer = (byte[])obj;
FileStream fs = new FileStream("c:\new.jpg");
fs.Write(buffer, 0, buffer.Length);
fs.Close();
}

可是大家有沒有發現一個問題,那就是,如果檔案非常大,例如 1.5GB,那麼 buffer 陣列就需要 1.5GB ?! 如果是 Web 應用程式,不用幾個使用者連線上來,伺服器大概就掛了吧 ^_^

所以最好將檔案分批次寫入或讀取,例如一次存取 1KB,這樣不管檔案有多大,記憶體的使用量都是固定的。

好了,如果要做到這個效果,我們可以使用 READTEXT 和 WRITETEXT 這兩個 T-SQL 指令,



-- 讀取 Employees 資料表中 Photo 欄位的第 16 個 byte 開始,
-- 共 1024 個 bytes
DECLARE @ptr varbinary(16)
SELECT @ptr = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = 1
READTEXT Employees.Photo @ptr 15 1024

-- 刪除 Employees 資料表中 Photo 欄位的第 16 個 byte 開始,
-- 共 50 個bytes
-- 並且在同一個位置插入二進位資料 0x112233445566778899AABBCCDDEEFF
-- (16 進位字串表示)
DECLARE @ptr varbinary(16)
SELECT @ptr = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = 1
WRITETEXT Employees.Photo @ptr 15 50 0x112233445566778899AABBCCDDEEFF

聖哥寫了兩個副程式,可以直接用來存取 image 欄位:




// 將 byte 陣列轉換為 16 進位字串,例如: 0x112233445566778899AABBCC
public string ByteArrayToHexString(byte[] bytearray, int length)
{
StringBuilder sb = new StringBuilder("0x");
for (int i = 0; i < length; i++)
{
sb.Append(string.Format("{0:x2}", bytearray[i]));
}
return sb.ToString();
}

// 將檔案寫入 image 欄位
public void SaveFileToSqlImage(SqlConnection connection,
string table_name, string column_name,
string filename, string where_condition,
int buffer_size)
{
SqlCommand cmdBody = new SqlCommand();
cmdBody.Connection = connection;
string sql = "DECLARE @ptrval varbinary(16) " +
" SELECT @ptrval = TEXTPTR(" + column_name +
") FROM " + table_name + " WHERE " + where_condition +
" UPDATETEXT " + table_name + "." + column_name +
" @ptrval {0} 0 {1}";


FileStream fs = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[buffer_size];
int count = fs.Read(buffer, 0, buffer.Length);
int loc = 0;
while (count > 0)
{
cmdBody.CommandText = string.Format(sql, loc,
ByteArrayToHexString(buffer, count));
connection.Open();
cmdBody.ExecuteNonQuery();
connection.Close();
loc += count;
count = fs.Read(buffer, 0, buffer.Length);
}
fs.Close();
}

// 從 image 欄位讀取資料並存檔
public void SaveFileFromSqlImage(SqlConnection connection,
string table_name, string column_name,
string filename, string where_condition,
int buffer_size)
{
SqlCommand cmdGetSize = new SqlCommand(
"SELECT DATALENGTH(" + column_name +
") FROM " + table_name + " WHERE " +
where_condition, connection);


connection.Open();
object obj = cmdGetSize.ExecuteScalar();
connection.Close();
if (obj != null && obj != DBNull.Value)
{
int filesize = (int)obj;
string sql = "DECLARE @ptrval varbinary(16) " +
"SELECT @ptrval = TEXTPTR(" + column_name +
") FROM " + table_name + " WHERE " + where_condition +
" READTEXT " + table_name + "." + column_name +
" @ptrval {0} {1}";


SqlCommand cmdBody = new SqlCommand();
cmdBody.Connection = connection;
int loc = 0;
FileStream fs = new FileStream(filename, FileMode.Create);
while (loc < filesize)
{
int count = (filesize - loc >= buffer_size)
? buffer_size : filesize - loc;
cmdBody.CommandText = string.Format(sql, loc, count);
connection.Open();
byte[] buffer = (byte[])cmdBody.ExecuteScalar();
connection.Close();
fs.Write(buffer, 0, buffer.Length);
loc += count;
}
fs.Close();
}
}

使用方法:



SaveFileToSqlImage(conn, "MyTable", "MyImageColumn",
"c:\filename.ext", "FileID=1", 1024);
SaveFileFromSqlImage(conn, "MyTable", "MyImageColumn",
"c:\filename.ext", "FileID=1", 1024);

ps. 附上 VB.NET 版本:



Function ByteArrayToHexString(ByVal bytearray() As Byte, _
ByVal Length As Integer) As String
Dim sb As New StringBuilder("0x")
For I As Integer = 0 To Length - 1
sb.Append(String.Format("{0:x2}", bytearray(I)))
Next
Return sb.ToString()
End Function

' 將檔案寫入 image 欄位
Sub SaveFileToSqlImage(ByVal Connection As SqlConnection, _
ByVal TableName As String, _
ByVal ColumnName As String, ByVal FileName As String, _
ByVal WhereCondition As String, _
ByVal BufferSize As Integer)

Dim cmdBody As New SqlCommand()
cmdBody.Connection = Connection
Dim sql As String = "DECLARE @ptrval varbinary(16) " +
"SELECT @ptrval = TEXTPTR(" + _
ColumnName + ") FROM " + TableName + " WHERE " + _
WhereCondition + " UPDATETEXT " + TableName + "." + _
ColumnName + " @ptrval {0} 0 {1}"
Dim fs As New FileStream(FileName, FileMode.Open)
Dim buffer(BufferSize - 1) As Byte
Dim count As Integer = fs.Read(buffer, 0, buffer.Length)
Dim loc As Integer = 0
Do While count > 0
cmdBody.CommandText = String.Format(sql, loc, _
ByteArrayToHexString(buffer, count))
Connection.Open()
cmdBody.ExecuteNonQuery()
Connection.Close()
loc += count
count = fs.Read(buffer, 0, buffer.Length)
Loop
fs.Close()
End Sub

' 從 image 欄位讀取資料並存檔
Sub SaveFileFromSqlImage(ByVal Connection As SqlConnection, _
ByVal TableName As String, _
ByVal ColumnName As String, ByVal FileName As String, _
ByVal WhereCondition As String, _
ByVal BufferSize As Integer)

Dim cmdGetSize As New SqlCommand("SELECT DATALENGTH(" + _
ColumnName + ") FROM " + _
TableName + " WHERE " + WhereCondition, Connection)
Connection.Open()
Dim obj As Object = cmdGetSize.ExecuteScalar()
Connection.Close()
If obj IsNot Nothing AndAlso obj IsNot DBNull.Value Then
Dim filesize As Integer = obj
Dim sql As String = "DECLARE @ptrval varbinary(16) " +
"SELECT @ptrval = TEXTPTR(" + _
ColumnName + ") FROM " + TableName + " WHERE " + _
WhereCondition + " READTEXT " + TableName + "." + _
ColumnName + " @ptrval {0} {1}"

Dim cmdBody As New SqlCommand()
cmdBody.Connection = Connection
Dim loc As Integer = 0
Dim fs As New FileStream(FileName, FileMode.Create)
Do While loc < filesize
Dim count As Integer = _
IIf(filesize - loc >= BufferSize, BufferSize, filesize - loc)
cmdBody.CommandText = String.Format(sql, loc, count)

Connection.Open()
Dim buffer() As Byte = cmdBody.ExecuteScalar()
Connection.Close()

fs.Write(buffer, 0, buffer.Length)
loc += count
Loop
fs.Close()
End If
End Sub

台北市河濱腳踏車道導覽圖

索取地點: 工務局水利工程處河川管理科(臺北市市府路 1 號 8 樓東南區)

RAYCH R2066-21 升級

聖哥在 2008/9/20 到松山老胡單車那邊買了一台 21 速版的 RAYCH R2066 (如下圖),

可是原本的齒比實在是騎不快,所以就一直想要升級一下整套傳動系統。

RAYCH R2066-21 未改裝 ##ReadMore##

原廠傳動系統配置:

  • 大齒盤: 鋁合金 TCRFE-ME 3/32" 42/32/24T (最大只有 42T,害我一直騎不快)
  • 飛輪: SHIMANO MF-TZ07 14T-28T 鎖牙飛輪 (就是這 14T,害我一直騎不快)
  • 前變速器: SHIMANO 上拉線Φ31.8 FD-TY10 (只支援到最大 42T,大小齒差 18 齒)
  • 後變速器: SHIMANO RD-TY22GSB-7 SIS 正勾 (只支援到 7 速)
  • 前變轉把微調變速 (轉兩格才變一速,不太方便)
  • 後變轉把定位變速
  • 齒比: 最快 = 42/14 = 3、最輕 = 24/28 = 0.857

聖哥預計要升級成(2008/10/13~10/14):

  • 大齒盤: 48/38/28T
  • 飛輪: 龍億 8 速 11-34T 鎖牙式定位飛輪 LY-1108MFZ
  • 鏈條: TAYA PR0 Velocity 大亞七八段銀色變速鍊條 (因為換了飛輪,所以鏈條也一併更換)
  • 前變速器: SHIMANO 上擺式 C-051 中變 (可支援 48/38/28T, ,大小齒差 20 齒)
  • 後變速器: SHIMANO TOURNEY 七/八段定位後變速器 RD-TX51 (附掛勾)
  • 前變/後變轉把定位變速: SHIMANO 24 速 RS41 轉把
  • 齒比: 最快 = 48/11 = 4.364、最輕 = 28/34 = 0.824

這樣速度就可以提升 4.364 / 3 = 1.455,也就是將近 46%,

聖哥用原廠的設定最快可以騎 25 公里/小時,

升級後就可以到 36 公里/小時,快很多呦 ^_^

而爬坡能力也小提升 4% 左右。

在經過一週多的網路詢價與購買零件之後,2008/10/14 進行改車動作囉 ~~~

首先,先把愛車反過來 (工作環境有點亂,請見諒 ^_^ ):

拆後輪囉 ~~ 先拆變速器那一邊的輪軸螺絲,要逆時針轉呦:

SANY2016 SANY2017

取下螺絲 (歹勢,焦距沒調好):

取下後變速器謢弓:

拆後輪另一邊螺絲,一樣是逆時針轉呦:

取下螺絲和腳架:

拆下來了:

打開後 V 煞車,只要拉開俗稱 "香蕉" 的地方就可以了:

將後輪組拆下來,後變速器要記得往後拉一下,然後後輪就可以往上提了:

後輪組拆下來之後的樣子 (後面那一台白色的,是我另外一台 26 吋的登山車啦):

拆下來的後輪組:

拆下飛輪那一邊的輪軸螺絲:

接下來要來拆飛輪了,首先要先準備一個拆鎖牙飛輪的套筒 (原廠是使用鎖牙飛輪):

將套筒套入飛輪那一邊的輪軸 (歹勢,那個輪軸上的螺絲要先拆下來呦):

套上去的樣子:

使用活動板手逆時針轉開:

不知道是聖哥力氣太小還是沒吃飽,一直轉不開,後來換一下姿勢將輪子一邊頂在床邊 (我是坐在床板上啦),左手握住輪子,右手握住活動板手,用上身的重量往下壓,就可以拆下來了呦:

拆下來的原廠 shimano 飛輪:

套上新的飛輪,順時針轉緊 (不用轉太緊沒關係,騎的時候就會自動轉緊了):

套上原本的套筒:

套好的樣子:

鎖上螺絲後 (螺絲鎖緊後,要比飛輪凸出來一點呦,要不然鎖上車架後,鏈條在最小齒會卡在後叉上,所以必要時可以加個墊片上去):

對了,如果要拆開另一邊的輪軸螺絲,可以先用板手套住花鼓上黑色那個螺絲 (我不知道該叫它什麼):

再用活動板手套住上方的螺絲,上面的逆時針,下面的順時針,就可以轉開了:

如果要轉開花鼓,則要用兩支板手,兩邊各套一支,逆時針時轉開,順時針是轉緊:

要小心,轉開後不要讓裡面的珠珠掉下來了呦,我是順便在這個時候上個黃油:

由於要換後變速器,所以要先將鏈條拆開,要先準備一支打鏈器 (手震,焦距又跑掉了):

將鏈條放入打鏈器中:

順時針轉動打鏈器,將連條連接處的那根金屬 (正確的名字叫什麼啊 ?) 推出來:

拆下來了 (要小心,不要推太出來了,要不然以後要再打過去就麻煩了):

拆下來的原廠鏈條 (這要準備換到我兒子的坐車上的):

接下來要來拆後變速器了,首先先將變速線拆下來:

拆下後變速器 (逆時針鬆開勾爪處螺絲):

拆掉後變速之後的車架:

這是要換上去的新變速器:

鎖上後變速器:

鎖好了:

聖哥順便要換一下變速線外管,所以先準備一下,外管按照原本黑色變速線外管的長度裁剪就可以了,兩端要套上銅頭呦:

接下來要將變速線穿入後變速器:

穿入並繞過小導輪:

再穿過這個地方:

鎖上變速器並套上線尾套頭:

再來一張後變速器完成圖:

SANY2080

後輪組與後變速器 (這一次不需要調整鋼絲輻條,後輪組就在正中央了,真是太幸運了 ^_^):

接下來要裝上新的鏈條 (鏈條上面是兩組鏈條快扣):

鏈線先套上最小齒飛輪,然後順勢套入後變速器導輪:

往下垂入導輪溝槽:

順著 S 形繞出導輪:

鏈條兩端都往大齒盤方向拉,對了,當鏈條在大盤最小盤、飛輪最少齒時,鏈條和後變速器 "腿" 的角度要像下面這張圖,大概呈 10~15 度角,所以可以用這樣的方式來決定最後鏈條的長度 (要打掉幾目的鏈條):

套上大盤的最小齒後,將鏈條兩端用快扣住:

SANY2092 SANY2093 SANY2094

拆踏板囉 ~~~ (左踏板順時針、右踏板逆時針)

SANY2096 SANY2097

接下來要拆左邊的曲柄 (就是和踏板連接在一起的那一根拉),首先將曲柄螺絲的防塵蓋打開:

SANY2098 SANY2099

使用內六角板手:

一手握住曲柄、手插握住板手,板手逆時針方向將螺絲拆下來:

SANY2103SANY2102

拆下來需要一個退曲柄 (退大盤) 的工具,上面那個套頭先拿掉:

旋開到底:

順時針轉入曲柄:

一手握住曲柄、一手握住板手,板手順時針方向轉緊,曲柄就會慢慢退出來了:

SANY2112 SANY2113

再將退盤工具逆時針轉出曲柄即可:

SANY2114

拆完左邊,來拆右邊的曲柄和大盤,一樣先將踏板拆下來,再拆曲柄螺絲的防塵蓋:

SANY2116

使用內六角螺絲板手:

SANY2118

一手握住曲柄、手插握住板手,板手逆時針方向將螺絲拆下來:

SANY2119 SANY2120

最後用手將螺絲旋出來:

SANY2121

套上退盤工具後,一手握住曲柄、一手握住板手,板手順時針方向轉緊,大盤就會慢慢退出來了:

SANY2123SANY2124

左邊是拆下來的原廠大盤,右邊是我要換上去的 48/38/28T 大盤:

SANY2126

接下來要換上新的大盤,直接將大盤裝上去,鏈條接到最小盤上:

SANY2127

將螺絲鎖上:

SANY2129 SANY2130

用內六角板手順時針將螺絲轉緊:

SANY2131

套上防塵蓋:

SANY2132

裝上左側曲柄並套上螺絲:

SANY2133

SANY2134

用內六角板手順時針將螺絲轉緊:

SANY2135

套上防塵蓋:

SANY2136

裝上並鎖緊踏板 (左踏板逆時針、右踏板順時針):

SANY2137

大盤安裝完成 (因為要裝前變速器,所以圖中的鏈條還沒上):

SANY2146

由於聖哥買的環抱式前變是 31.8mm 的,可是車車的管子是 28.6mm,所以只好去買了一個轉接環:

SANY2148

套上轉接環 (疑 ? 上面怎麼有貼紙 ? ... 因為聖哥這部車是在台北松山老胡那邊買的啦 ^_^ ):

SANY2151

鎖上前變速器,上下的位置很重要,鏈條在最小盤時不能打到變速器,又要能變上最大盤,要多試幾次:

SANY2152

由於這個變速器是在左側上拉,可是 R2066 預設是右側上拉,所以走線有點奇怪 (上點黃油,可以用就好了 ^_^),對了,前變速器要和大盤平行呦:

SANY2158 SANY2157

另外, 由於安裝 C051 中變的中管 (裝中變的那根) 最佳的角度是 66-69度,可是 R2066 的角度不太對 (不夠傾斜), 所以在安裝 C051 變速器時,要多花一點時間調校一下上下的位置呦。

正面來一張:

SANY2154

前後變來一張:

SANY2155

接下來換上 SHIMANO 24 速 RS41 轉把:

SANY2160 SANY2161

前面來一張 (煞車線與變速線外管全部換成纖維網狀外管):

SANY2163

全車照:

全車照

SANY2178

這次升級的花費約 2500 元 (不含工具):

  • 48/38/28T 大盤: 3xx
  • SHIMANO C-051 前變速器: 3xx
  • 龍億 8 速 11-34T 鎖牙式定位飛輪: 5xx
  • SHIMANO TOURNEY 七.八段定位後變速器 RD-TX51 (附掛勾): 3xx
  • TAYA PR0 Velocity 大亞七八段變速鍊條: 3xx
  • SHIMANO 24速 RS41 轉把: 4xx
  • 纖維網狀外管: 2xx

其實我發現,如果經費有限的話,

飛輪換 7 速 11-30T 的,就可以不用換後變速器,也不用換鏈條,也不用換變把,

不過鏈條要再多加幾目上去就是了 (可以找一下人家多的鏈條)

所以只要 1000 元多一點就可以了:

  • 48/38/28T 大盤: 3xx
  • SHIMANO C-051 前變速器: 3xx
  • 龍億 7 速 11-30T 鎖牙式定位飛輪: 4xx

速度一樣可以提升到跟我現在改一樣,齒比 = 48/11 = 4.364

只是爬坡能力會稍微差一點 (比原廠的差 9%)。

或者只改後飛輪,由原本的 14-28T 改成 11-30T (七速),

這樣就只要花不到 500 元,速度可以由 42/14 提升到 42/11,差不多 27%,

也就是時速可以由 25 公里/小時提升到 32 公里/小時,

實在是非常經濟的改法呦。

另外,升級下來的零件當然不要浪費了,所以聖哥就拿來升級寶貝大兒子的 20 吋童車,

原本是 12 速的非定位兒童登山車,升級後就變成前後 21 速 SHIMANO 定位變速高級童車了,呵呵,

不過由於後飛輪由 6 速改 7 速,所以還要調一下鋼絲輻條,大約是左右鬆緊各轉一圈就可以了。

21 速 SHIMANO 定位變速高級童車

21 速 SHIMANO 定位變速高級童車

21 速 SHIMANO 定位變速高級童車

21 速 SHIMANO 定位變速高級童車

21 速 SHIMANO 定位變速高級童車