Table of Contents

SQL - Query

進階查詢用法 — 手動連線、SqlDataReader 控制、與 QuickQuery<T> / QuickDynamicQuery 的選擇時機。

Namespace

using ZapLib;

Quick vs Manual

ZapLib 的 SQL 查詢有「自動」與「手動」兩條路:

方法 連線 適用情境
QuickQuery<T> 自動開關 95% 的情境 — 一次性查詢
QuickDynamicQuery 自動開關 不想宣告 model 的一次性查詢
Query 手動 同一連線連續多次查詢、需 stream 大量資料

QuickQuery 詳解

完整簽章:

public T[] QuickQuery<T>(string sql, object param = null, bool isfetchall = true)

isfetchall 參數

預設 true — 取出所有資料。設為 false 時只取第一筆

// 只想知道某 user 是否存在
ModelUser[] u = db.QuickQuery<ModelUser>(
    "SELECT * FROM Users WHERE email = @email",
    new { email = "alice@example.com" },
    isfetchall: false
);

bool exists = u != null && u.Length > 0;

對大表查單筆時,isfetchall: false 可以省下記憶體與時間 — SqlDataReader 一拿到第一筆就 break,不會繼續讀。

欄位與屬性對應

  • 不分大小寫EMAILemailEmail 都會對到 Email 屬性
  • 沒對到的欄位忽略 — SQL 多出來的欄位不影響
  • 沒對到的屬性保留預設值
  • DBNull 自動轉成 null(Nullable 型別友善)
public class ModelUser
{
    public int Id { get; set; }
    public string Email { get; set; }
    public DateTime? LastLogin { get; set; }   // Nullable,DBNull 會變 null
    public string DisplayName { get; set; }    // SQL 沒這欄,保持 null
}

ModelUser[] users = db.QuickQuery<ModelUser>("SELECT id, email, last_login FROM Users");

QuickDynamicQuery 詳解

不想宣告 model 時的快速通道。回傳 dynamic[],每個元素背後是 ExpandoObject

dynamic[] rows = db.QuickDynamicQuery(
    "SELECT TOP 5 id, name, price FROM Products ORDER BY price DESC"
);

foreach (dynamic row in rows)
{
    Console.WriteLine($"{row.id} {row.name} ${row.price}");
}

⚠️ dynamic 沒有 IDE 補全、沒有編譯期檢查。錯字到 runtime 才會炸。建議只在腳本 / 探索期使用。

Manual Query

需要更精細控制?走手動三部曲:Connect()Query()Close()

SQL db = new SQL("DefaultConn");
db.Connect();

// > 💡 v2.5.0 起,原本的 db.Connet() 仍可使用,但已標記為 [Obsolete],
// > 編譯時會出現 warning 提示遷移到新名字 db.Connect()。下一個 major
// > 版本將完全移除 Connet。同樣的還有 SQL.BuildconnString() →
// > BuildConnectionString()、Fetch.SetRequestContnet() →
// > SetRequestContent()。

if (db.IsConn)
{
    try
    {
        using (SqlDataReader reader = db.Query(
            "SELECT * FROM BigTable WHERE created_at >= @from",
            new { from = DateTime.Today.AddDays(-30) }))
        {
            while (reader.Read())
            {
                // 一次處理一筆,不全部載入記憶體
                int id = reader.GetInt32(0);
                string name = reader.GetString(1);
                ProcessRow(id, name);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("查詢失敗:" + ex.Message);
    }
    finally
    {
        db.Close();
    }
}

手動模式下錯誤會 throw,不像 QuickXxx 自動吞掉。需自己 try/catch

Reuse Connection for Multiple Queries

同一個 SQL 物件、同一個連線跑多次查詢:

SQL db = new SQL("DefaultConn");
db.Connect();

if (db.IsConn)
{
    try
    {
        // 第一次查詢
        SqlDataReader r1 = db.Query("SELECT COUNT(*) FROM Users");
        r1.Read();
        int userCount = r1.GetInt32(0);
        r1.Close();

        // 第二次查詢(同一連線)
        SqlDataReader r2 = db.Query("SELECT COUNT(*) FROM Products");
        r2.Read();
        int productCount = r2.GetInt32(0);
        r2.Close();

        Console.WriteLine($"Users: {userCount}, Products: {productCount}");
    }
    finally
    {
        db.Close();
    }
}

Manual Fetch to Model

拿到 SqlDataReader 後,可以用 fetch<T>() 一鍵綁定 model:

db.Connect();
SqlDataReader reader = db.Query("SELECT * FROM Users");

ModelUser[] users = db.fetch<ModelUser>(reader);   // 全部
ModelUser firstUser = db.fetch<ModelUser>(reader, fetchAll: false)[0];   // 第一筆

reader.Close();
db.Close();

或用 dynamicFetch() 拿 dynamic:

dynamic[] rows = db.dynamicFetch(reader);

DB Always-On Read-Only Routing

如果你的環境啟用了 SQL Server AlwaysOn 高可用性架構,且想讓唯讀查詢自動走 ReadOnly secondary node:

<appSettings>
  <add key="EnableDBAlwaysOn" value="true" />
</appSettings>
SQL db = new SQL("DefaultConn");
db.SQLReadOnly = true;

// 這個查詢會自動加上 ApplicationIntent=ReadOnly,路由到 secondary
ModelUser[] users = db.QuickQuery<ModelUser>("SELECT * FROM Users");

Fallback 機制:如果 secondary 回傳空陣列(例如 secondary 還沒同步到資料),ZapLib 會自動切回 ReadWrite primary 再試一次。

Trace Code

每個 SQL 物件有一組 TraceCode(GUID)— 寫進 MyLog 的所有訊息都會帶這個 code,方便追蹤同一個物件的所有操作:

SQL db = new SQL("DefaultConn");
Console.WriteLine($"This SQL trace code: {db.TraceCode}");

db.QuickQuery<ModelUser>("SELECT * FROM Users");
// 日誌會出現:Exec sql: SELECT * FROM Users ... TraceCode: abc-123-...

See Also