動態

詳情 返回 返回

輕量又高效:C#.NET RepoDB 實戰指南 - 動態 詳情

簡介

  • RepoDB 是一個“混合” ORM(Hybrid ORM),旨在彌合微型 ORM(如 Dapper)和全功能 ORM(如 EF Core)之間的鴻溝,既提供對 SQL 的直接控制,又封裝了常用的高級操作

核心特性

  • 混合 ORM 功能

    • 支持微型 ORM 的原始 SQL 查詢(ExecuteQuery<T>)和 Fluent API(QueryAll<T>)
    • 提供完整 ORM 的高級功能,如批量操作(BulkInsert)、緩存和屬性處理器。
  • 高性能

    • 使用編譯表達式緩存(Compiled Expression Caching)減少運行時開銷。
    • 批量和批量插入(BulkInsert)利用 ADO.NETSqlBulkCopy 等技術,性能優異。
    • 官方基準測試表明,RepoDB 在單記錄和批量操作中性能優於 DapperEntity Framework Core
  • 內置倉儲支持

    • 提供 BaseRepository<T, TDbConnection> 基類,簡化倉儲模式實現。
    • 支持自定義倉儲,靈活性高。
  • 第二層緩存(2nd-Layer Cache

    • 支持內存緩存(默認 180 分鐘),可自定義緩存實現。*
    • 顯著減少數據庫查詢,提升性能(可達 90% 以上)。
  • 數據庫支持

    • 支持 SQL Server、MySQL、PostgreSQL、SQLite 等主流數據庫。
    • 通過擴展包(如 RepoDb.SqlServer)支持特定數據庫功能。
  • Fluent API 和原始 SQL

    • Fluent API 提供強類型操作(如 Insert<T>、Query<T>)。
    • 原始 SQL 支持複雜查詢,結合參數化查詢防止 SQL 注入。
  • 批量和批量操作

    • 支持 InsertAll、UpdateAll、DeleteAll(批量操作)和 BulkInsert、BulkMerge(高效批量操作)。
    • 自動處理自增列值回寫。
  • 擴展性

    • 支持屬性處理器(Property Handlers)自定義映射邏輯。
    • 提供跟蹤(Tracing)和查詢提示(Hints)優化數據庫操作。
  • 開源和社區支持

    • Apache-2.0 許可證,免費開源。
    • GitHub 倉庫(https://github.com/mikependon/RepoDB)提供文檔和支持。

安裝與配置

安裝 NuGet 包

# 核心
Install-Package RepoDb
# SQL Server 提供程序
Install-Package RepoDb.SqlServer   
# MySQL 提供程序
Install-Package RepoDb.MySql  
# MySqlConnector 
Install-Package RepoDb.MySqlConnector

初始化依賴

SqlServer

  • 最新版本
GlobalConfiguration
    .Setup()
    .UseSqlServer();
  • 1.13.0 版本之前
RepoDb.SqlServerBootstrap.Initialize();

MySql

  • 最新版本
GlobalConfiguration
    .Setup()
    .UseMySql();

GlobalConfiguration
    .Setup()
    .UseMySqlConnector();
  • 1.13.0 版本之前
RepoDb.MySqlBootstrap.Initialize();

RepoDb.MySqlConnectorBootstrap.Initialize();

標準用法

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    // 之後可直接調用 RepoDB 的擴展方法
}

倉儲模式

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public class UserDto
{
    public int Id { get; set; }
    public string FullName { get; set; }
}

public class UserRepository : BaseRepository<User, SqlConnection>
{
    public UserRepository(string connectionString)
        : base(connectionString)
    {
    }
}

var builder = WebApplication.CreateBuilder(args);

// 初始化 RepoDB
SqlServerBootstrap.Initialize();

// 配置 Mapster
TypeAdapterConfig<User, UserDto>
    .NewConfig()
    .Map(dest => dest.FullName, src => src.Name);
builder.Services.AddMapster();

// 配置倉儲
builder.Services.AddScoped(_ => new UserRepository("Server=(local);Database=TestDb;Integrated Security=True;"));

var app = builder.Build();

app.MapGet("/users", async (UserRepository repo) =>
{
    var users = await repo.QueryAllAsync();
    var userDtos = users.Adapt<List<UserDto>>();
    return Results.Ok(userDtos);
});

app.Run();

實體映射

// 自動映射(表名 = 類名複數,列名 = 屬性名)
[Map("Products")]
public class Product
{
    public int Id { get; set; }
    
    [Map("ProductName")]
    public string Name { get; set; }
    
    public decimal Price { get; set; }
    
    [Map(ignore: true)] // 忽略映射
    public string TempData { get; set; }
}

核心 CRUD 操作

所有操作均基於對 IDbConnection 的擴展方法。

插入

// 單條插入,返回自增 Id
var person = new Person { Name = "John", Age = 54, CreatedDateUtc = DateTime.UtcNow };
var id = connection.Insert(person);

// 多條插入
var people = GetPeople(100);
var rows = connection.InsertAll(people);

查詢

public static void Main()
{
    SqlServerBootstrap.Initialize();
    var connectionString = "Server=(local);Database=TestDb;Integrated Security=True;";

    using var connection = new SqlConnection(connectionString);

    // Fluent API 查詢
    var users = connection.QueryAll<User>();
    foreach (var user in users)
    {
        Console.WriteLine($"Id: {user.Id}, Name: {user.Name}");
    }

    // 原始 SQL 查詢
    var sqlUsers = connection.ExecuteQuery<User>("SELECT * FROM Users WHERE Age > @Age", new { Age = 18 });
    foreach (var user in sqlUsers)
    {
        Console.WriteLine($"Id: {user.Id}, Name: {user.Name}");
    }
}
// 按主鍵查詢單條
var person = connection.Query<Person>(id).FirstOrDefault();

// 帶表達式過濾
var adults = connection.Query<Person>(e => e.Age >= 18);

// 查詢所有
var all = connection.QueryAll<Person>();

更新

// 根據實體主鍵更新所有可寫屬性
person.Name = "James";
var updated = connection.Update(person);

// 批量更新
people.ForEach(p => p.Age += 1);
var updRows = connection.UpdateAll(people);

刪除

// 按主鍵刪除
var del = connection.Delete<Person>(10045);

// 刪除所有
var delAll = connection.DeleteAll<Person>();

高級查詢

多表查詢(JOIN)

using (var connection = new SqlConnection(connectionString))
{
    var result = connection.QueryMultiple<Product, Category>(
        p => p.CategoryId,          // 產品外鍵
        c => c.Id,                   // 類別主鍵
        (product, category) =>       // 結果映射
        {
            product.Category = category;
            return product;
        },
        where: p => p.Price > 500    // 篩選條件
    );
}

分頁查詢

using (var connection = new SqlConnection(connectionString))
{
    // 排序 + 分頁
    var orderBy = OrderField.Parse(new { Price = Order.Descending });
    var result = connection.Query<Product>(
        where: p => p.Price > 100,
        orderBy: orderBy,
        page: 2,        // 第2頁
        rowsPerBatch: 10 // 每頁10條
    );
    
    // 分頁對象
    var page = new Page(2, 10); // 第2頁,每頁10條
    var pagedResult = connection.Query<Product>(page: page);
}

存儲過程調用

using (var connection = new SqlConnection(connectionString))
{
    var parameters = new { CategoryId = 5, MinPrice = 100.00m };
    var result = connection.ExecuteQuery<Product>(
        "usp_GetProductsByCategory", 
        parameters, 
        commandType: CommandType.StoredProcedure);
}

動態查詢

using (var connection = new SqlConnection(connectionString))
{
    // 動態條件
    var where = new { CategoryId = 3, Price = (decimal?)100.00m };
    var products = connection.Query<Product>(where: where);
    
    // 動態返回類型
    var result = connection.ExecuteQuery("SELECT * FROM Products WHERE CategoryId = @CategoryId", 
        new { CategoryId = 3 });
}

批量操作與性能優化

批量插入(BulkInsert)

using (var connection = new SqlConnection(connectionString))
{
    var products = GenerateProducts(10000); // 生成10000個產品
    var insertedCount = connection.BulkInsert(products);
}

批量合併(BulkMerge)

using (var connection = new SqlConnection(connectionString))
{
    var products = GetChangedProducts(); // 獲取變更的產品(新增+更新)
    var mergedCount = connection.BulkMerge(products);
}

二級緩存

using (var connection = new SqlConnection(connectionString))
{
    // 查詢結果緩存30分鐘
    var products = connection.Query<Product>(
        cacheKey: "expensive_products", 
        cacheItemExpiration: TimeSpan.FromMinutes(30),
        where: p => p.Price > 1000);
    
    // 清除緩存
    CacheFactory.Flush();
}

事務管理

基本事務

using (var connection = new SqlConnection(connectionString))
{
    using (var transaction = connection.EnsureOpen().BeginTransaction())
    {
        try
        {
            // 減少庫存
            connection.DecreaseStock(productId, quantity, transaction: transaction);
            
            // 創建訂單
            connection.Insert(new Order { /* ... */ }, transaction: transaction);
            
            transaction.Commit();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
}

分佈式事務

using (var scope = new TransactionScope())
{
    using (var connection1 = new SqlConnection(connString1))
    using (var connection2 = new SqlConnection(connString2))
    {
        // 數據庫1操作
        connection1.Insert(entity1);
        
        // 數據庫2操作
        connection2.Insert(entity2);
    }
    
    scope.Complete();
}

擴展功能

數據跟蹤(變更追蹤)

// 啓用變更追蹤
var product = connection.Query<Product>(1).First();
product.Price = 899.99m;

// 獲取變更
var changes = PropertyCache.Get<Product>().GetChanges(product);
// changes = { { "Price", (原值, 899.99m) } }

// 僅更新變更字段
connection.Update<Product>(product, changes);

字段映射擴展

// 自定義類型轉換
TypeMapper.Map(typeof(ProductStatus), DbType.Int32, true);

// 查詢時轉換
var products = connection.Query<Product>(p => p.Status == ProductStatus.Active);

// 自定義值轉換器
public class ProductStatusConverter : IPropertyHandler<string, ProductStatus>
{
    public ProductStatus Get(string input, PropertyHandlerGetOptions options) => 
        Enum.Parse<ProductStatus>(input);
    
    public string Set(ProductStatus input, PropertyHandlerSetOptions options) => 
        input.ToString();
}

事件鈎子

// 全局事件
GlobalConfiguration
    .BeforeInsert += (sender, args) => 
        args.Entity.CreatedDate = DateTime.UtcNow;
    
    .AfterQuery += (sender, args) => 
        Logger.LogDebug($"Query executed: {args.Statement}");

動態類型與原生表名操作

除了強類型實體,RepoDB 還支持 Dynamics,可對任意表執行 CRUD

// 動態查詢
var dyn = connection.Query(
    tableName: "[dbo].[Customer]",
    param: new { FirstName = "John", LastName = "Doe" }
).FirstOrDefault();

// 指定列
var cust = connection.Query(
    "[dbo].[Customer]",
    10045,
    Field.From("Id","FirstName","LastName")
).FirstOrDefault();

映射配置與屬性處理器

  • 默認按 屬性名 ↔ 列名 進行映射,支持大小寫與下劃線風格自動識別。
  • 可通過 FluentMapper 靈活定義表名、列名、主鍵、類型映射及自定義屬性處理器
FluentMapper
  .Entity<Customer>()
  .Table("[sales].[Customer]")
  .Primary(e => e.Id)
  .Identity(e => e.Id)
  .Column(e => e.FirstName, "[FName]")
  .PropertyHandler<AddressPropertyHandler>(e => e.Address);

異步操作

await connection.InsertAsync(person);
var list = await connection.QueryAsync<Person>(e => e.Age > 18);

跟蹤(Tracing)與日誌

  • 通過全局或實例級別的事件,捕獲並記錄 SQL 語句與參數:
connection.OnTraceExecuting = (command, trace) =>
{
    Console.WriteLine($"SQL: {trace.CommandText}");
    Console.WriteLine($"Param: {string.Join(", ", trace.ParameterValues)}");
};

最佳實踐

初始化 RepoDB

  • 在應用程序啓動時調用 SqlServerBootstrap.Initialize() 或其他數據庫初始化方法
SqlServerBootstrap.Initialize();

使用倉儲模式

  • 繼承 BaseRepository 實現標準化的數據訪問層
public class ProductRepository : BaseRepository<Product, SqlConnection>
{
    public ProductRepository(string connectionString) 
        : base(connectionString) { }
    
    public IEnumerable<Product> GetExpensiveProducts(decimal minPrice)
    {
        return Query(p => p.Price >= minPrice);
    }
    
    public int BulkUpdatePrices(decimal multiplier)
    {
        var products = QueryAll();
        products.ForEach(p => p.Price *= multiplier);
        return UpdateAll(products);
    }
}

參數化查詢

  • 使用匿名對象或字典防止 SQL 注入
connection.ExecuteQuery<User>("SELECT * FROM Users WHERE Age > @Age", new { Age = 18 });

工作單元模式

public class UnitOfWork : IDisposable
{
    private readonly SqlConnection _connection;
    private SqlTransaction _transaction;
    
    public UnitOfWork(string connectionString)
    {
        _connection = new SqlConnection(connectionString);
        _connection.Open();
        _transaction = _connection.BeginTransaction();
    }
    
    public void Commit()
    {
        _transaction.Commit();
        Dispose();
    }
    
    public void Rollback()
    {
        _transaction.Rollback();
        Dispose();
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _connection?.Dispose();
    }
    
    // 倉儲訪問器
    public ProductRepository Products => 
        new ProductRepository(_connection, _transaction);
}

性能關鍵路徑優化

// 1. 使用字段選擇器(避免 SELECT *)
var products = connection.Query<Product>(
    select: e => new { e.Id, e.Name }); // 只查詢需要的字段

// 2. 啓用批處理
GlobalConfiguration.Setup().UseSqlServer().SetBatchSize(1000);

// 3. 異步操作
public async Task<IEnumerable<Product>> GetProductsAsync()
{
    using (var connection = new SqlConnection(connectionString))
    {
        return await connection.QueryAllAsync<Product>();
    }
}

// 4. 緩存常用查詢
public class ProductCache
{
    private static IEnumerable<Product> _products;
    
    public static IEnumerable<Product> GetAll()
    {
        return _products ??= 
            new SqlConnection(connectionString)
                .QueryAll<Product>(cacheKey: "all_products", 
                    cacheItemExpiration: TimeSpan.FromHours(1));
    }
}
  • 短生命的 IDbConnection 實例:每次操作打開/關閉連接或結合 DI 容器按作用域管理。
  • 參數化方式:始終使用 RepoDB 的擴展方法代替手寫拼接,防止 SQL 注入並優化執行計劃。
  • 分級緩存:對熱點查詢開啓二級緩存,寫操作後記得手動清除相關緩存。
  • 合理分批:海量數據時優先使用 BulkInsert/BulkMerge,避免一次性操作過大導致 OOM
  • 映射校驗:在單元測試中,使用 FluentMapper.Validate() 確保映射配置完整且無衝突。
  • 監控與告警:結合 Trace 事件和 APM 工具,監控慢 SQL 與批量操作時長。

常見問題解決方案

複雜類型映射

public class Product
{
    // JSON列映射
    [TypeMap(typeof(JsonbTypeMapper))]
    public Dictionary<string, string> Specifications { get; set; }
}

public class JsonbTypeMapper : ITypeMapper
{
    public object Map(object value)
    {
        return JsonConvert.DeserializeObject<Dictionary<string, string>>(value?.ToString());
    }
}

多數據庫支持

// 應用啓動時
if (DatabaseType == DatabaseType.SqlServer)
{
    RepoDb.SqlServerBootstrap.Initialize();
}
else if (DatabaseType == DatabaseType.PostgreSql)
{
    RepoDb.PostgreSqlBootstrap.Initialize();
}

// 通用倉儲
public class GenericRepository<T> : BaseRepository<T, IDbConnection>
{
    public GenericRepository(IDbConnection connection) : base(connection) { }
}

分庫分表查詢

// 分表策略
public class OrderShardingStrategy : IShardingStrategy
{
    public string GetTableName(object entity)
    {
        var order = entity as Order;
        return $"Orders_{order.CreatedDate:yyyyMM}";
    }
}

// 分表查詢
var orders = connection.Query<Order>(
    tableName: ShardingStrategy.ResolveTableName<Order>(),
    where: o => o.CustomerId == customerId);

資源與擴展:

  • GitHub 倉庫:https://github.com/mikependon/RepoDB
  • 官方文檔:https://repodb.net/
  • 擴展包:

    • RepoDb.SqlServer:SQL Server 支持。
    • RepoDb.MySql:MySQL 支持。
    • RepoDb.SQLite:SQLite 支持。
user avatar verd 頭像 ydswin 頭像 zhuweitao 頭像 buildyuan 頭像 xiaolanbenlan 頭像 matrixorigin 頭像 _kysou 頭像 754582231 頭像 baozaodepingguo 頭像 chenzhuodegan_czbzv7 頭像
點贊 10 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.