FreeSql.Repository

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ā)一次該事件。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容