簡介
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.NET的SqlBulkCopy等技術,性能優異。 - 官方基準測試表明,
RepoDB在單記錄和批量操作中性能優於Dapper和Entity Framework Core。
- 使用編譯表達式緩存(
-
內置倉儲支持
- 提供
BaseRepository<T, TDbConnection>基類,簡化倉儲模式實現。 - 支持自定義倉儲,靈活性高。
- 提供
-
第二層緩存(
2nd-Layer Cache)- 支持內存緩存(默認 180 分鐘),可自定義緩存實現。*
- 顯著減少數據庫查詢,提升性能(可達 90% 以上)。
-
數據庫支持
- 支持
SQL Server、MySQL、PostgreSQL、SQLite等主流數據庫。 - 通過擴展包(如
RepoDb.SqlServer)支持特定數據庫功能。
- 支持
-
Fluent API和原始SQLFluent 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 支持。