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

LINQ to SQL 通用基類實作:以泛型封裝 CRUD 實現 N 層架構資料存取層復用

     編輯:關於SqlServer

前言:強類型優勢與抽象困境的矛盾

LINQ to SQL (Visual Studio 2008) 提供 **強類型實體類別、編譯期驗證、IntelliSense、延遲載入** 等現代 ORM 體驗。但每個資料表對應一個實體類,導致 **資料存取邏輯 (Select, Where, Add, Update, Delete) 在每個 Repository 中重複**,違反 DRY 原則,難以構建乾淨的 N 層架構。本文示範利用 **泛型基類 + DataContext.GetTable()** 實作通用資料存取層。

核心設計:泛型 Repository 基類


public abstract class RepositoryBase where TEntity : class
{
    protected DataContext Context { get; private set; }

    protected RepositoryBase(string connectionString)
    {
        Context = new DataContext(connectionString);
    }

    // 通用查詢:支援 Lambda 表達式
    public virtual IList Where(Func predicate)
    {
        return Context.GetTable().Where(predicate).ToList();
    }

    // 通用查詢:支援動態字串謂詞 (System.Linq.Dynamic)
    public virtual IList Where(string predicate, params object[] values)
    {
        return Context.GetTable().Where(predicate, values).ToList();
    }

    // 取得單一實體 (依主鍵)
    public virtual TEntity GetById(object id)
    {
        var table = Context.GetTable();
        var keyProp = table.GetType().GetProperties().First(p => p.Name == "Id"); // 簡化:假設主鍵名為 Id
        return table.FirstOrDefault(e => keyProp.GetValue(e).Equals(id));
    }

    // 新增
    public virtual void Add(TEntity entity)
    {
        Context.GetTable().InsertOnSubmit(entity);
    }

    // 更新 (LINQ to SQL 自動追蹤變更,呼叫 SubmitChanges 即可)
    public virtual void Update(TEntity entity)
    {
        // Attach 邏輯視情況實作
        Context.GetTable().Attach(entity, true); // true = 視為已修改
    }

    // 刪除
    public virtual void Delete(TEntity entity)
    {
        Context.GetTable().DeleteOnSubmit(entity);
    }

    // 批次儲存
    public virtual void SaveChanges()
    {
        Context.SubmitChanges();
    }
}

動態查詢擴充:System.Linq.Dynamic 整合

針對「執行階段決定篩選條件」 (如使用者自訂報表篩選、動態 Grid 排序分頁),整合 `System.Linq.Dynamic` 套件:


public static class DynamicQueryable
{
    public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (predicate == null) throw new ArgumentNullException(nameof(predicate));

        var lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);
        return source.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable), "Where",
                new Type[] { source.ElementType },
                source.Expression, Expression.Quote(lambda)));
    }
}

// Repository 中暴露
public IList WhereDynamic(string predicate, params object[] values)
{
    return Context.GetTable().Where(predicate, values).ToList();
}

用法範例: `repo.WhereDynamic("CategoryID == @0 && UnitPrice > @1", 5, 100.0m);`

具體 Repository 實作範例


public class ProductRepository : RepositoryBase
{
    public ProductRepository(string connStr) : base(connStr) { }

    // 業務專用查詢:熱銷商品
    public IList GetTopSelling(int count)
    {
        return Where(p => p.UnitsOnOrder > 0).OrderByDescending(p => p.UnitsOnOrder).Take(count).ToList();
    }

    // 複雜查詢:庫存預警
    public IList GetLowStockAlert(int threshold)
    {
        return WhereDynamic("UnitsInStock <= @0 AND Discontinued == false", threshold);
    }
}

進階議題與最佳實務

1. DataContext 生命週期管理 (關鍵)

  • Per Request / Per Unit of Work: ASP.NET 中每個 HTTP Request 建立一個 DataContext (或 Repository),請求結束時 `Dispose`。
  • 避免長壽命 DataContext: 記憶體洩漏、過時快取、並發衝突。
  • 依賴注入 (DI) 整合: 以 `Scoped` 生命週期註冊 `DataContext` / `Repository`。

2. 並發衝突處理

  • 實體類別需標記 `[Column(IsVersion=true)]` 對應 `TIMESTAMP`/`ROWVERSION` 欄位。
  • `SubmitChanges()` 會拋出 `ChangeConflictException`,捕獲後可 `Refresh(RefreshMode.KeepCurrentValues)` 或 `KeepChanges` 解決。

3. 擴充點:分頁、排序、投影 (DTO)


public virtual IPagedList GetPaged(int pageIndex, int pageSize, Expression> filter, Expression> orderBy)
{
    var query = Context.GetTable().Where(filter).OrderBy(orderBy);
    return new PagedList(query, pageIndex, pageSize);
}

4. 遷移至 Entity Framework Core / Dapper 的考量

  • LINQ to SQL 已進入維護模式,新專案建議 **EF Core** (功能豐富、跨平台) 或 **Dapper** (高效能、輕量、手寫 SQL)。
  • 本文泛型基類模式在 EF Core (`DbContext.Set()`) 與 Dapper (通用 `Query`) 同樣適用,遷移成本低。

結語

以 **泛型基類 + `GetTable()` + 動態查詢** 封裝 LINQ to SQL 通用 CRUD,能有效消除 Repository 樣板代碼,建立乾淨、可測試、可擴充的 N 層資料存取層。核心在於:**識別不變的存取模式 (變數:實體類型、謂詞),提煉為泛型協定;保留變化的業務邏輯於具體 Repository**。此設計模式跨越 ORM 框架,是資料存取層架構的永恆核心。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved