FreeSql.Repository 作為擴展,實現(xiàn)了通用倉儲層功能。與其他規(guī)范標準一樣,倉儲層也有相應的規(guī)范定義。FreeSql.Repository 參考 abp vnext 接口,定義和實現(xiàn)基礎的倉儲層(CURD),應該算比較通用的方法吧。
安裝
dotnet add package FreeSql.Repository
定義
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true) //自動遷移實體的結(jié)構(gòu)到數(shù)據(jù)庫
.Build(); //請務必定義成 Singleton 單例模式
public class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Title { get; set; }
}
使用方法
1、IFreeSql 的擴展方法;
var curd = fsql.GetRepository<Song>();
注意:Repository對象多線程不安全
2、繼承實現(xiàn);
public class SongRepository : BaseRepository<Song, int> {
public SongRepository(IFreeSql fsql) : base(fsql, null, null) {}
//在這里增加 CURD 以外的方法
}
3、依賴注入;
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeRepository(filter => filter
.Apply<ISoftDelete>("SoftDelete", a => a.IsDeleted == false)
.Apply<ITenant>("Tenant", a => a.TenantId == 1)
,
this.GetType().Assembly
);
}
//在控制器使用
public SongsController(GuidRepository<Song> repos1) {
}
依賴注入的方式可實現(xiàn)全局【過濾與驗證】的設定,方便租戶功能的設計;
更多資料:《過濾器、全局過濾器》
過濾與驗證
假設我們有User(用戶)、Topic(主題)兩個實體,在領域類中定義了兩個倉儲:
var userRepository = fsql.GetGuidRepository<User>();
var topicRepository = fsql.GetGuidRepository<Topic>();
在開發(fā)過程中,總是擔心 topicRepository 的數(shù)據(jù)安全問題,即有可能查詢或操作到其他用戶的主題。因此我們在v0.0.7版本進行了改進,增加了 filter lambda 表達式參數(shù)。
var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);
var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);
- 在查詢/修改/刪除時附加此條件,從而達到不會修改其他用戶的數(shù)據(jù);
- 在添加時,使用表達式驗證數(shù)據(jù)的合法性,若不合法則拋出異常;
分表與分庫
FreeSql 提供 AsTable 分表的基礎方法,GuidRepository 作為分存式倉儲將實現(xiàn)了分表與分庫(不支持跨服務器分庫)的封裝。
var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}");
上面我們得到一個日志倉儲按年月分表,使用它 CURD 最終會操作 Log_201903 表。
注意事項:
- v0.11.12以后的版本可以使用 CodeFirst 遷移分表;
- 不可在分表分庫的實體類型中使用《延時加載》;
兼容問題
FreeSql 支持多種數(shù)據(jù)庫,分別為 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/達夢/人大金倉/翰高/MsAccess,雖然他們都為關(guān)系型數(shù)據(jù)庫,但各自有著獨特的技術(shù)亮點,有許多亮點值得我們使用;
比如 SqlServer 提供的 output inserted 特性,在表使用了自增或數(shù)據(jù)庫定義了默認值的時候,使用它可以快速將 insert 的數(shù)據(jù)返回。PostgreSQL 也有相應的功能,如此方便卻不是每個數(shù)據(jù)庫都支持。
IRepository 接口定義:
TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
于是我們做了兩種倉庫層實現(xiàn):
- BaseRepository 采用 ExecuteInserted 執(zhí)行;
- GuidRepository 采用 ExecuteAffrows 執(zhí)行(兼容性好);
當采用了不支持的數(shù)據(jù)庫時(Sqlite/MySql/Oracle),建議:
- 使用 uuid 作為主鍵(即 Guid);
- 避免使用數(shù)據(jù)庫的默認值功能;
- 倉儲層實現(xiàn)請使用 GuidRepository;
UnitOfWork
UnitOfWork 可將多個倉儲放在一個單元管理執(zhí)行,最終通用 Commit 執(zhí)行所有操作,內(nèi)部采用了數(shù)據(jù)庫事務;
using (var uow = fsql.CreateUnitOfWork()) {
var songRepo = uow.GetRepository<Song>();
var userRepo = uow.GetRepository<User>();
//上面兩個倉儲,由同一UnitOfWork uow 創(chuàng)建
//在此執(zhí)行倉儲操作
//這里不受異步方便影響
uow.Commit();
}
參考:在 asp.net core 中注入工作單元方法
//第一步:
public class UnitOfWorkRepository<TEntity, TKey> : BaseRepository<TEntity, TKey>
{
public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null)
{
this.UnitOfWork = uow;
}
}
public class UnitOfWorkRepository<TEntity> : BaseRepository<TEntity, int>
{
public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null)
{
this.UnitOfWork = uow;
}
}
//第二步:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFreeSql>(fsql);
services.AddScoped<FreeSql.IUnitOfWork>(sp => fsql.CreateUnitOfWork());
services.AddScoped(typeof(IReadOnlyRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(IBasicRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(BaseRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(IReadOnlyRepository<,>), typeof(UnitOfWorkRepository<,>));
services.AddScoped(typeof(IBasicRepository<,>), typeof(UnitOfWorkRepository<,>));
services.AddScoped(typeof(BaseRepository<,>), typeof(UnitOfWorkRepository<,>));
//批量注入程序集內(nèi)的所有自建倉儲類,可以根據(jù)自己需要來修改
Assembly[] assemblies = new [] { typeof(XxxRepository).Assembly };
if (assemblies?.Any() == true)
foreach (var asse in assemblies)
foreach (var repo in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(UnitOfWorkRepository).IsAssignableFrom(a)))
services.AddScoped(repo);
}
聯(lián)級保存
請移步文檔《聯(lián)級保存》
實體變化事件
全局設置:
fsql.SetDbContextOptions(opt => {
opt.OnEntityChange = report => {
Console.WriteLine(report);
};
});
單獨設置 DbContext 或者 UnitOfWork:
var ctx = fsql.CreateDbContext();
ctx.Options.OnEntityChange = report => {
Console.WriteLine(report);
};
var uow = fsql.CreateUnitOfWork();
uow.OnEntityChange = report => {
Console.WriteLine(report);
};
參數(shù) report 是一個 List 集合,集合元素的類型定義如下:
public class ChangeInfo
{
public object Object { get; set; }
public EntityChangeType Type { get; set; }
}
public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
| 變化類型 | 說明 |
|---|---|
| Insert | 實體對象被插入 |
| Update | 實體對象被更新 |
| Delete | 實體對象被刪除 |
| SqlRaw | 執(zhí)行了SQL語句 |
SqlRaw 目前有兩處地方比較特殊:
- 多對多聯(lián)級更新導航屬性的時候,對中間表的全部刪除操作;
- 通用倉儲類 BaseRepository 有一個 Delete 方法,參數(shù)為表達式,而并非實體;
int Delete(Expression<Func<TEntity, bool>> predicate);
DbContext.SaveChanges,或者 Repository 對實體的 Insert/Update/Delete,或者 UnitOfWork.Commit 操作都會最多觸發(fā)一次該事件。