需從資料庫查詢使用者清單,以結構體陣列 TUSER[] 形式經由 out 參數回傳給呼叫端 (Web Form / API Controller),避免建立額外 DTO 類別或 Tuple 封裝。
CREATE TABLE Users (
UserID INT IDENTITY PRIMARY KEY,
LoginName NVARCHAR(50) NOT NULL,
LoginPass NVARCHAR(100) NOT NULL, -- 實務應存雜湊
TrueName NVARCHAR(50),
Email NVARCHAR(100),
Level INT NOT NULL DEFAULT 0
);
public struct TUSER
{
public string LoginName;
public string LoginPass;
public string TrueName;
public string Email;
public int Level;
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
public class UserRepository
{
private readonly string _connectionString;
public UserRepository(string connectionString)
{
_connectionString = connectionString;
}
///
/// 取得所有使用者,以 out 參數回傳結構體陣列
///
/// 輸出:使用者結構體陣列
/// true=成功/有資料;false=失敗/無資料
public bool GetAllUsers(out TUSER[] users)
{
users = Array.Empty(); // 預設空陣列,避免 null
const string sql = "SELECT LoginName, LoginPass, TrueName, Email, Level FROM Users ORDER BY UserID";
// 使用 List 暫存,最後 ToArray()
var tempList = new List();
using (var conn = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand(sql, conn))
{
try
{
conn.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
tempList.Add(new TUSER
{
LoginName = reader.GetString(0),
LoginPass = reader.GetString(1),
TrueName = reader.IsDBNull(2) ? "" : reader.GetString(2),
Email = reader.IsDBNull(3) ? "" : reader.GetString(3),
Level = reader.GetInt32(4)
});
}
}
if (tempList.Count == 0) return false; // 無資料
users = tempList.ToArray();
return true;
}
catch (Exception ex)
{
// 實務:記錄 Log (ex.ToString())
return false;
}
}
}
}
// Web Forms Button Click
protected void btnLoadUsers_Click(object sender, EventArgs e)
{
var repo = new UserRepository(ConfigurationManager.ConnectionStrings["Default"].ConnectionString);
if (repo.GetAllUsers(out TUSER[] users))
{
rptUsers.DataSource = users;
rptUsers.DataBind();
lblMessage.Text = $"載入 {users.Length} 筆資料";
}
else
{
lblMessage.Text = "查無資料或發生錯誤";
}
}
// ASP.NET Core Controller
[HttpGet]
public IActionResult GetUsers()
{
var repo = new UserRepository(_config.GetConnectionString("Default"));
if (repo.GetAllUsers(out TUSER[] users))
return Ok(users);
return NotFound("No users found");
}
out TUSER[] 而非 ref 或回傳值?out 語意明確:「此方法產出此陣列」,呼叫端無需預先初始化。TUSER[] 同樣可行,但 out 允許同時回傳 bool 狀態碼,避免 null 語意模糊 (失敗 vs 空陣列)。Result<TUSER[]> 或拋出異常,而非 bool+out。| 特性 | struct TUSER | class TUSER |
|---|---|---|
| 記憶體 | 值型別,陣列元素連續內嵌資料 | 參考型別,陣列存參考,物件在堆疊 |
| 複製行為 | 指派即複製所有欄位 | 指派複製參考 |
| null 可能性 | 不可為 null (陣列元素永遠存在) | 可為 null |
| 適用場景 | 小型、不可變、高頻配置 (如 DTO) | 大型、可變、需繼承/多型 |
本案例資料單純、唯讀傳輸,struct 效能更佳 (減少 GC 壓力、記憶體局部性好)。
原始片段先 SELECT COUNT(*) 再查資料,**完全多餘**。
List<T> 動態擴容機制已能高效處理未知筆數,單次 ExecuteReader 即可。
using 區塊包裹 SqlConnection, SqlCommand, SqlDataReader → 確保釋放連線池資源。Open()/Close() 交錯,極易導致連線洩漏。public bool GetUsersPaged(int pageIndex, int pageSize, out TUSER[] users, out int totalCount)
{
users = Array.Empty();
totalCount = 0;
// 1. 取總筆數
string countSql = "SELECT COUNT(*) FROM Users";
// 2. 分頁查詢 (SQL Server 2012+ OFFSET/FETCH)
string pageSql = @"
SELECT LoginName, LoginPass, TrueName, Email, Level
FROM Users
ORDER BY UserID
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
// 總筆數
using (var countCmd = new SqlCommand(countSql, conn))
{
totalCount = (int)countCmd.ExecuteScalar();
}
// 分頁資料
if (totalCount == 0) return false;
using (var cmd = new SqlCommand(pageSql, conn))
{
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
using (var reader = cmd.ExecuteReader())
{
var list = new List();
while (reader.Read())
{
list.Add(new TUSER { /* ...同前... */ });
}
users = list.ToArray();
}
}
}
return users.Length > 0;
}
out T[] 模式在舊版 ASP.NET / WinForms 專案中常見,能以最小改動回傳陣列資料。現代開發建議改用 泛型集合回傳值 (List<TUSER> / IEnumerable<TUSER>) 或 Result 模式,語意更清晰、支援 LINQ 鏈式操作、配合 Dependency Injection 更易測試。