跟蹤測試 DbContext ,向"不是真正的 ORM" 說拜拜

FreeSql 發展到如今,已經有兩種穩定的開發模式,如下先簡單帶過一下。後面纔是本文的主題。git

方法一:基於 helper 的方式,祼用;

dotnet add package FreeSqlgithub

提供 CodeFirst、DbFirst、豐富的表達式樹、讀寫分離、AOP等功能支持;sql

方法二:基於 Repository + UnitOfWok 的方式;

dotnet add package FreeSql.Repository數據庫

這是一個擴展包,提供標準的 IRepository 接口定義與默認實現,以及 UnitOfWork 工做單元的支持,更可怕的是集成了局部/全局過濾器,實現租戶、軟刪除等功能不在話下。ide

不相信嗎?請看如下代碼:sqlserver

public IServiceProvider ConfigureServices(IServiceCollection services) {
    services.AddSingleton<IFreeSql>(fsql);
    services.AddMvc();

    var builder = new ContainerBuilder();

    builder.RegisterFreeRepository(filter => filter
        .Apply<ISoftDelete>("SoftDelete", a => a.IsDeleted == false)
        .Apply<ITenant>("Tenant", a => a.TenantId == 1)
    );

    builder.Populate(services);
    var container = builder.Build();
    return new AutofacServiceProvider(container);
}

比 abpvnext 還要方便,由於 abp 的相關實體須要實現接口 ISoftDelete、ITenant;測試

咱們沒有這個限制,只要過濾器的表達式解析成功,就算可用;優化

使用在任何實體上的時候,只要 [實體].IsDeleted == false 能解析能過,就算可用;ui

方式三:基於 DbContext

這個項目仍然是一個擴展包,提相似 EFCore 那樣的開發習慣。目前定義的規則以下:url

文字規則略顯複雜,後邊有代碼演示,以及圖文介紹在 sqlite 和 sqlserver 下的測試過程。

DbContext

  • 提供 SaveChanges 方法;
  • 執行隊列;

DbSet

  • 提供 Add、AddRange、Remove、RemoveRange、Update、UpdateRange 方法;
  • 以及 Select 屬性(連去原有的 FreeSql 查詢對象);
  • 私有對象 states,存儲實體的副本哈希集合,key=實體的主鍵值,value=實體;

Add/AddRange(entitys)

  • 驗證 entitys 主鍵值,是否存在於 states 中,存在時報錯;
  • 驗證 entitys 主鍵中存在自增:
    • 如有,則當即開啓 DbContext 事務,按數據庫種類執行相應的方法,最終將返回的自增值,賦給entitys的屬性;
    • 若無,而且 entitys 無主鍵值,則報錯;
    • 不然,進入【打包執行隊列】;
  • 完成時更新 states;

Remove/RemoveRange(entitys)

  • 驗證 entitys 主鍵值,若無則報錯;
  • 驗證 states 中是否存在,若無則提醒應該先查詢,再刪除;
  • 刪除 states 對應的實體;
  • 清除 entitys 內的自增屬性值、Guid 類型的值,那這個 entitys 將變爲可 Add 狀態;
  • 進入【打包執行隊列】;

Update/UpdateRange(entitys)

  • 驗證 entitys 主鍵值,若無則報錯;
  • 驗證 states 中是否存在,若無則提醒應該先查詢,再刪除;
  • 進入【打包執行隊列】;

Select

  • 當即執行隊列中的命令(打包方式),以避免髒讀到未提交的數據;
  • 查詢完成時,更新 states 的值;

更新數據規則

  • 對比 states 中存在的歷史快照值,返回即將修改的 fields;

演示代碼

using FreeSql;

public class SongContext : DbContext {

    public DbSet<Song> Songs { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder builder) {
        builder.UseFreeSql(這裏是IFreeeSql對象);
    }
}

public class Song {
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public DateTime? Create_time { get; set; }
    public bool? Is_deleted { get; set; }
    public string Title { get; set; }
    public string Url { get; set; }
}
public class Tag {
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string Name { get; set; }
}

using (var ctx = new SongContext()) {

    ctx.Songs.Select.Where(a => a.Id > 10).ToList();
    //查詢結果,存入 states

    var song = new Song { };
    //可插入的 song

    ctx.Songs.Add(song);
    id = song.Id;
    //因有自增類型,當即開啓事務執行SQL,返回自增值

    var adds = Enumerable.Range(0, 100)
        .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" })
        .ToList();
    //建立一堆無主鍵值的數據

    ctx.Songs.AddRange(adds);
    //當即執行,將自增值賦給 adds 全部元素,由於有自增類型,若是其餘類型,指定傳入主鍵值,不會當即執行

    for (var a = 0; a < adds.Count; a++)
        adds[a].Title = "dkdkdkdk" + a;

    ctx.Songs.UpdateRange(adds);
    //批量修改,進入隊列

    ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList());
    //批量刪除,進入隊列,完成時 10-20 元素的主鍵值會被清除

    //ctx.Songs.Update(adds.First());

    adds.Last().Url = "skldfjlksdjglkjjcccc";
    ctx.Songs.Update(adds.Last());
    //單條修改 urls 的值,進入隊列

    //throw new Exception("回滾");

    //ctx.Songs.Select.First();
    //這裏作一個查詢,會當即打包【執行隊列】,避免沒有提交的數據,影響查詢結果

    ctx.SaveChanges();
    //打包【執行隊列】,提交事務
}

在 sqlite 測試

打個岔:爲何一條條的執行?

  • 有自增屬性須要獲取值;
  • sqlite 沒有批量插入獲取多個自增的辦法,或者您有招來支一支(萬分感謝);
  • 後面採用 sqlserver 測試,就不是這個境況了,insert into values(),(),(),而後利用 output 特性返回全部值;
    • 比較蛋疼的是,這個特性不是全部數據庫都有

能夠看見,最終 SaveChanges 時將不會產生影響的命令,一塊兒打包執行,即採用優化合並的方式進行執行。

例如:

ctx.Songs.Update(adds[0]);
ctx.Songs.Update(adds[1]);

這兩個更新操做,會合成一條 SQL 命令執行。

在 sqlserver 測試

其實大體與 sqlite 下相同,惟一的區別在於 AddRange 的處理方式,如圖:

當插入單條時,採用了第一行代碼的 SQL 命令;

當批量插入時,採用了後面看上去複雜的 SQL 命令;

全部傳入的實體屬性值在執行完成後,都會更新;

特別說明

FreeSql.DbContext 目前仍處於研究開發階段,不適合商用;

總結

爲何寫這篇文章,時常看見有人說某某 orm 不是真正的 orm,沒有 OO 思想。

但願 FreeSql.DbContext 隨着時間的積累,穩定性和成熟度有所提高,不久成爲一個真正的 ORM。

有人會擔憂,咱們第三方作的不靠譜,沒有 EFCore 穩定的說話,這個是固然。

可是咱們也有本身的特色,不是嗎?咱們能夠作到多種數據庫使用習慣的一致性,這點 EFCore 目前是沒有辦法解決的難題。

從細節出發,咱們的口號是:作 .NETCore 最方便的 ORM!

github: https://github.com/2881099/FreeSql 377星

還請獻上寶貴的一星,謝謝觀看!!

相關文章
相關標籤/搜索