資料表結構
CREATE TABLE UserPhoto (
UserNo VARCHAR(20) PRIMARY KEY,
Photo VARBINARY(MAX) -- SQL Server 2005+ 推薦,IMAGE 類型已棄用
);
核心類別實作
using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
public class PhotoRepository
{
private readonly string _connStr = "Server=.;Database=TestPhoto;User ID=sa;Password=***;";
/// 寫入圖片:檔案 → 位元組陣列 → 參數化 SQL
public void SavePhoto(string userNo, string filePath)
{
byte[] imageBytes = File.ReadAllBytes(filePath);
const string sql = "UPDATE UserPhoto SET Photo = @img WHERE UserNo = @userNo; " +
"IF @@ROWCOUNT = 0 INSERT INTO UserPhoto (UserNo, Photo) VALUES (@userNo, @img);";
using (var conn = new SqlConnection(_connStr))
using (var cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.Add("@img", SqlDbType.VarBinary, -1).Value = imageBytes;
cmd.Parameters.Add("@userNo", SqlDbType.VarChar, 20).Value = userNo;
conn.Open();
cmd.ExecuteNonQuery();
}
}
/// 讀取圖片:位元組陣列 → MemoryStream → Bitmap
public Image LoadPhoto(string userNo)
{
const string sql = "SELECT Photo FROM UserPhoto WHERE UserNo = @userNo;";
using (var conn = new SqlConnection(_connStr))
using (var cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.Add("@userNo", SqlDbType.VarChar, 20).Value = userNo;
conn.Open();
var bytes = cmd.ExecuteScalar() as byte[];
if (bytes == null || bytes.Length == 0) return null;
using (var ms = new MemoryStream(bytes, false)) // false = 不複製,零拷貝
{
return Image.FromStream(ms); // 回傳 Bitmap,由呼叫端負責 Dispose
}
}
}
}
WinForms 呼叫範例
private readonly PhotoRepository _repo = new PhotoRepository();
private void btnUpload_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
_repo.SavePhoto(txtUserNo.Text, openFileDialog1.FileName);
MessageBox.Show("上傳成功");
}
catch (Exception ex)
{
MessageBox.Show("上傳失敗:" + ex.Message);
}
}
}
private void btnLoad_Click(object sender, EventArgs e)
{
try
{
var img = _repo.LoadPhoto(txtUserNo.Text);
if (img != null)
{
pictureBox1.Image?.Dispose(); // 釋放舊圖
pictureBox1.Image = img;
}
else
{
MessageBox.Show("無圖片資料");
}
}
catch (Exception ex)
{
MessageBox.Show("讀取失敗:" + ex.Message);
}
}
關鍵最佳實踐
- 參數化查詢: 防 SQL 注入、避免二進位資料轉義問題、計畫快取重用
- VARBINARY(MAX) 取代 IMAGE: IMAGE 為舊類型,未來版本將移除
- using 區塊管理連線/命令/串流: 確保非托管資源及時釋放
- MemoryStream(false): 避免位元組陣列二次複製,降低大物件堆(LOH)壓力
- 大圖片考慮: 超過 10MB 建議檔案儲存 + 資料庫存路徑,或使用 FILESTREAM/FileTable
- 非同步: 高併發場景改用
ExecuteNonQueryAsync/ExecuteScalarAsync 配合 await
常見錯誤排查
| 錯誤 | 原因 | 修正 |
| Parameter '@img' not found | 參數名稱拼寫不一致 | 核對 @img 大小寫與 Parameters.Add |
| Invalid parameter size | 未指定 Size = -1 (MAX) | SqlDbType.VarBinary, -1 |
| Out of memory | 單次載入超大圖片 | 分塊讀寫 / 檔案儲存 / 壓縮 |
| Generic GDI+ error | Image.FromStream 後 MemoryStream 過早 Dispose | MemoryStream 生命週期需長於 Image 使用期間 |