首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

C# 存取 SQL Server 圖片欄位完整範例:參數化寫入與串流讀取

     編輯:關於SqlServer

資料表結構

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+ errorImage.FromStream 後 MemoryStream 過早 DisposeMemoryStream 生命週期需長於 Image 使用期間
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved