天天看點

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

FreeSql 發展到現在,已經有兩種穩定的開發模式,以下先簡單帶過一下。後面才是本文的主題。

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

dotnet add package FreeSql

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

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

dotnet add package FreeSql.Repository

這是一個擴充包,提供标準的 IRepository 接口定義與預設實作,以及 UnitOfWork 工作單元的支援,更可怕的是內建了局部/全局過濾器,實作租戶、軟删除等功能不在話下。

不相信嗎?請看以下代碼:

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 能解析能過,就算可用;

方式三:基于 DbContext

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

文字規則略顯複雜,後邊有代碼示範,以及圖文介紹在 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)

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 測試

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

打個岔:為什麼一條條的執行?

  • 有自增屬性需要擷取值;
  • sqlite 沒有批量插入擷取多個自增的辦法,或者您有招來支一支(萬分感謝);
  • 後面采用 sqlserver 測試,就不是這個境況了,insert into values(),(),(),然後利用 output 特性傳回所有值;
    • 比較蛋疼的是,這個特性不是所有資料庫都有
跟蹤測試 DbContext ,向"不是真正的 ORM" 說拜拜
跟蹤測試 DbContext ,向"不是真正的 ORM" 說拜拜

可以看見,最終 SaveChanges 時将不會産生影響的指令,一起打包執行,即采用優化合并的方式進行執行。

例如:

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

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

在 sqlserver 測試

其實大緻與 sqlite 下相同,唯一的差別在于 AddRange 的處理方式,如圖:

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

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

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

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

特别說明

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

總結

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

希望 FreeSql.DbContext 随着時間的積累,穩定性和成熟度有所提升,不久成為一個真正的 ORM。

有人會擔心,我們第三方做的不靠譜,沒有 EFCore 穩定的說話,這個是當然。

但是我們也有自己的特點,不是嗎?我們可以做到多種資料庫使用習慣的一緻性,這點 EFCore 目前是沒有辦法解決的難題。

從細節出發,我們的口号是:做 .NETCore 最友善的 ORM!

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

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

繼續閱讀