歡迎光臨
每天分享高質量文章

.NET Core新型 ORM 功能介紹

作者:nicye

連結:http://www.cnblogs.com/kellynic/p/10645049.html

簡介

FreeSql 是一個功能強大的 .NETStandard 庫,用於物件關係對映程式(O/RM),支援 .NETCore 2.1+ 或 .NETFramework 4.6.1+。

 

定義

IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Sqlite,
        @"Data Source=|DataDirectory|/test.db;Pooling=true;Max Pool Size=10")
    .UseAutoSyncStructure(true) //自動同步物體結構到資料庫
    .Build();

入門篇

查詢

 

1、查詢一條

 

fsql.Select.Where(a => a.Id == 1).First();

 

 

2、分頁:第1頁,每頁20條

 

fsql.Select<Xxx>.Page(1, 20).ToList();

 

細節說明:SqlServer 2012 以前的版本,使用 row_number 分頁;SqlServer 2012+ 版本,使用最新的 fetch next rows 分頁;

 

3、IN

fsql.Select.Where(a => new { 1,2,3 }.Contains(a.Id)).ToList();

 

4、聯表

 

fsql.Select.LeftJoin((a, b) => a.YyyId == b.Id).ToList();

 

5、Exists子表

 

fsql.Select.Where(a => fsql.Select(b => b.Id == a.YyyId).Any()).ToList();

 

6、GroupBy & Having

 

fsql.Select.GroupBy(a => new { a.CategoryId }).Having(a => a.Count > 2).ToList(a => new { a.Key, a.Count() });

 

7、指定欄位查詢

 

fsql.Select.Limit(10).ToList(a => a.Id);
fsql.Select.Limit(10).ToList(a => new { a.Id, a.Name });
fsql.Select.Limit(10).ToList(a => new Dto());

 

8、執行SQL傳回物體

 

fsql.Ado.Query("select * from xxx");
fsql.Ado.Queryint, string, string)>("select * from xxx");
fsql.Ado.Query<dynamic>("select * from xxx");

 

插入

 

1、單條

 

fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteAffrows();

 

2、單條,傳回自增值

 

fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteIdentity();

 

3、單條,傳回插入的行(SqlServer 的 output 特性)

 

fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteInserted();

 

4、批次

 

fsql.Insert<Xxx>().AppendData(陣列).ExecuteAffrows();

 

5、批次,傳回插入的行(SqlServer 的 output 特性)

 

fsql.Insert<Xxx>().AppendData(陣列).ExecuteInserted();

 

6、指定列

 

fsql.Insert().AppendData(new Xxx()).InsertColumns(a => a.Title).ExecuteAffrows();
fsql.Insert().AppendData(new Xxx()).InsertColumns(a => new { a.Id, a.Title}).ExecuteAffrows();

 

7、忽略列

 

fsql.Insert().AppendData(new Xxx()).IgnoreColumns(a => a.Title).ExecuteAffrows();
fsql.Insert().AppendData(new Xxx()).IgnoreColumns(a => new { a.Id, a.Title}).ExecuteAffrows();

 

8、事務

fsql.Insert<Xxx>().AppendData(new Xxx()).WithTransaction(事務物件).ExecuteAffrows();

 

更新

 

1、指定列

 

fsql.Update(1).Set(a => a.CreateTime, DateTime.Now).ExecuteAffrows();

 

2、累加,set clicks = clicks + 1

 

fsql.Update(1).Set(a => a.Clicks + 1).ExecuteAffrows();

 

3、儲存

 

fsql.Update<Xxx>().SetSource(單個物體).ExecuteAffrows();

 

4、批次儲存

 

fsql.Update<Xxx>().SetSource(陣列).ExecuteAffrows();

 

5、忽略列

 

fsql.Update().SetSource(陣列).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ExecuteAffrows();

 

6、更新條件

 

fsql.Update().SetSource(陣列).Where(a => a.Clicks > 100).ExecuteAffrows();

 

7、事務

 

fsql.Update(1).Set(a => a.Clicks + 1).WithTransaction(事務物件).ExecuteAffrows();

 

刪除

 

1、dywhere

 

  • 主鍵值

  • new[] { 主鍵值1, 主鍵值2 }

  • Xxx物件

  • new[] { Xxx物件1, Xxx物件2 }

  • new { id = 1 }

fsql.Delete(new[] { 1, 2 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1 OR `Id` = 2)
fsql.Delete(new Xxx { Id = 1, Title = "test" }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
fsql.Delete(new[] { new Xxx { Id = 1, Title = "test" }, new Xxx { Id = 2, Title = "test" } }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1 OR `Id` = 2)
fsql.Delete(new { id = 1 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)

 

2、條件

 

fsql.Delete().Where(a => a.Id == 1).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
fsql.Delete().Where("id = ?id", new { id = 1 }).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (id = ?id)
var item = new Xxx { Id = 1, Title = "newtitle" };
var t7 = fsql.Delete().Where(item).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` = 1)
var items = new List();
for (var a = 0; a < 10; a++) items.Add(new Xxx { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 });
fsql.Delete().Where(items).ExecuteAffrows();
//DELETE FROM `xxx` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))

 

3、事務

 

fsql.Delete().Where(a => a.Id == 1).WithTransaction(事務物件).ExecuteAffrows();

初級篇

運算式

 

支援功能豐富的運算式函式解析,方便程式員在不瞭解資料庫函式的情況下編寫程式碼。這是 FreeSql 非常特色的功能之一,深入細化函式解析儘量做到滿意,所支援的型別基本都可以使用對應的運算式函式,例如 日期、字串、IN查詢、陣列(PostgreSQL的陣列)、字典(PostgreSQL HStore)等等。

 

1、查詢今天建立的資料

 

fsql.Delete().Where(a => a.CreateTime.Date == DateTime.Now.Date).ToList();

 

2、SqlServer 下隨機獲取記錄

 

fsql.Delete().OrderBy(a => Guid.NewGuid()).Limit(1).ToSql();

 

3、運算式函式全覽

 

 

4、陣列

 

 

一個細節證明 FreeSql 匠心製作

 

通用的 in 查詢 select.Where(a => new []{ 1,2,3 }.Contains(a.xxx))

 

假設 xxxs 是 pgsql 的陣列欄位型別,其實會與上面的 in 查詢起衝突,FreeSql 解決了這個矛盾 select.Where(a => a.xxxs.Contains(1)) 

 

5、字典 Dictionary

 

 

6、JSON JToken/JObject/JArray

 

7、字串

 

使用字串函式可能會出現效能瓶頸,雖然不推薦使用,但是作為功能庫這也是不可缺少的功能之一。

 

8、日期

 

9、時間

 

 

10、數學函式

 

 

11、型別轉換

 

CodeFirst

1、配置物體(特性)

 

public class Song {
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string Title { get; set; }
    public string Url { get; set; }
    public virtual ICollection Tags { get; set; }
    [Column(IsVersion = true)]
    public long versionRow { get; set; }
}

 

2、在外部配置物體

 

fsql.CodeFirst
    .ConfigEntity(a => {
        a.Property(b => b.Id).IsIdentity(true);
        a.Property(b => b.versionRow).IsVersion(true);
    });

 

DbFirst

 

1、獲取所有資料庫

 

fsql.DbFirst.GetDatabases();
//傳回字串陣列, ["cccddd", "test"]

 

2、獲取指定資料庫的表資訊

 

fsql.DbFirst.GetTablesByDatabase(fsql.DbFirst.GetDatabases()[0]);
//傳回包括表、列詳情、主鍵、唯一鍵、索引、外來鍵、備註等資訊

 

3、生成物體

 

new FreeSql.Generator.TemplateGenerator()
.Build(fsql.DbFirst,
@"C:Users8810DesktopgithubFreeSqlTemplatesMySqlsimple-entity",
    //模板目錄(事先下載)
    @"C:Users8810Desktop你的目錄",
    //生成後儲存的目錄
    "cccddd"
    //資料庫
);

高階篇

Repository 倉儲實現

 

1、單個倉儲

 

var curd = fsql.GetRepositoryint>();
//curd.Find(1);
var item = curd.Get(1);
curd.Update(item);
curd.Insert(item);
curd.Delete(1);
curd.Select.Limit(10).ToList();

 

工作單元

using (var uow = fsql.CreateUnitOfWork()) {
    var songRepos = uow.GetRepository();
    var userRepos = uow.GetRepository();
    //上面兩個倉儲,由同一UnitOfWork uow 建立
    //在此執行倉儲操作
    //這裡不受非同步方便影響
    uow.Commit();
}

 

區域性過濾器 + 資料驗證

var topicRepository = fsql.GetGuidRepository(a => a.UserId == 1);

 

之後在使用 topicRepository 操作方法時:

 

  • 查詢/修改/刪除時附過濾條件,從而達到不會修改其他使用者的資料;

  • 新增時,使用過濾條件驗證合法性,若不合法則丟擲異常;如以下方法就會報錯:

topicRepository.Insert(new Topic { UserId = 2 })

 

樂觀鎖

 

更新物體資料,在併發情況下極容易造成舊資料將新的記錄更新。FreeSql 核心部分已經支援樂觀鎖。

 

樂觀鎖的原理,是利用物體某欄位,如:long version,更新前先查詢資料,此時 version 為 1,更新時產生的 SQL 會附加 where version = 1,當修改失敗時(即 Affrows == 0)丟擲異常。

 

每個物體只支援一個樂觀鎖,在屬性前標記特性:[Column(IsVersion = true)] 即可。

 

無論是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都會增加 1

 

DbContext

dotnet add package FreeSql.DbContext

 

實現類似 EFCore 使用方法,跟蹤物件狀態,最終透過 SaveChanges 方法以事務的方式提交整段操作。

 

using (var ctx = new SongContext()) {
    var song = new Song { BigNumber = "1000000000000000000" };
    ctx.Songs.Add(song);
    song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString();
    ctx.Songs.Update(song);
    var tag = new Tag {
        Name = "testaddsublist",
        Tags = new[] {
            new Tag { Name = "sub1" },
            new Tag { Name = "sub2" },
            new Tag {
                Name = "sub3",
                Tags = new[] {
                    new Tag { Name = "sub3_01" }
                }
            }
        }
    };
    ctx.Tags.Add(tag);
    ctx.SaveChanges();
}
public class Song {
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string BigNumber { get; set; }
    [Column(IsVersion = true)] //樂觀鎖
    public long versionRow { get; set; }
}

public class Tag {
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public int? Parent_id { get; set; }
    public virtual Tag Parent { get; set; }
    public string Name { get; set; }
    public virtual ICollection Tags { get; set; }
}

public class SongContext : DbContext {
    public DbSet Songs { get; set; }
    public DbSet Tags { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder builder) {
        builder.UseFreeSql(fsql);
    }
}

 

導航屬性

 

支援 1對1、1對多、多對1、多對多 的約定導航屬性配置,主要用於運算式內部查詢;

 

//OneToOne、ManyToOne
var t0 = fsql.Select().Where(a => a.Parent.Parent.Name == "粵語").ToList();
//OneToMany
var t1 = fsql.Select().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToList();
//ManyToMany
var t2 = fsql.Select().Where(s => s.Tags.AsSelect().Any(t => t.Name == "國語")).ToList();

不朽篇

讀寫分離

 

資料庫讀寫分離,本功能是客戶端的讀寫分離行為,資料庫伺服器該怎麼配置仍然那樣配置,不受本功能影響,為了方便描術後面講到的【讀寫分離】都是指客戶端的功能支援。

 

各種資料庫的讀寫方案不一,資料庫端開啟讀寫分離功能後,讀寫分離的實現大致分為以下幾種:

 

1、nginx代理,配置繁瑣且容易出錯;

2、中件間,如MyCat,MySql可以其他資料庫怎麼辦?

3、在client端支援;

 

FreeSql 實現了第3種方案,支援一個【主庫】多個【從庫】,【從庫】的查詢策略為隨機方式。

 

若某【從庫】發生故障,將切換到其他可用【從庫】,若已全部不可用則使用【主庫】查詢。

 

出現故障【從庫】被隔離起來間隔性的檢查可用狀態,以待恢復。

 

IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.MySql, connstr)
    .UseSlave("connectionString1", "connectionString2")
    //使用從資料庫,支援多個
    .Build();
select.Where(a => a.Id == 1).ToOne();
//讀【從庫】(預設)
select.Master().WhereId(a => a.Id == 1).ToOne();
//強制讀【主庫】

 

下麵是以前某專案的測試圖片,以供參考,整個過程無感切換和恢復:

 

 

分割槽分表

 

FreeSql 提供 AsTable 分表的基礎方法,GuidRepository 作為分存式倉儲將實現了分表與分庫(不支援跨伺服器分庫)的封裝。

 

var logRepository = fsql.GetGuidRepository(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}");

 

上面我們得到一個日誌倉儲按年月分表,使用它 CURD 最終會操作 Log_201903 表。

 

合併兩個倉儲,實現分表下的聯表查詢:

 

fsql.GetGuidRepository().Select.FromRepository(logRepository)
    .LeftJoin(b => b.UserId == a.Id)
    .ToList();

 

租戶

 

1、按租戶欄位區分

 

FreeSql.Repository 現實了 filter(過濾與驗證)功能,如:

var topicRepos = fsql.GetGuidRepository(t => t.TerantId == 1);

 

使用 topicRepos 物件進行 CURD 方法:

 

  • 在查詢/修改/刪除時附加此條件,從而達到不會修改 TerantId != 1 的資料;

  • 在新增時,使用運算式驗證資料的合法性,若不合法則丟擲異常;

 

利用這個功能,我們可以很方便的實現資料分割槽,達到租戶的目的。

 

2、按租戶分表

 

FreeSql.Repository 現實了 分表功能,如:

 

var tenantId = 1;
var reposTopic = orm.GetGuidRepository(null, oldname => $"{oldname}{tenantId}");

 

上面我們得到一個倉儲按租戶分表,使用它 CURD 最終會操作 Topic_1 表。

 

3、按租戶分庫

 

與方案二相同,只是表儲存的位置不同。

 

4、全域性設定

 

透過註入的方式設定倉儲類的全域性過濾器。

 

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc();
    services.AddSingleton(Fsql);
    services.AddFreeRepository(filter => {
        var tenantId = 求出當前租戶id;
        filter
        .Apply("softdelete", a => a.IsDeleted == false)
        .Apply("tenant", a => a.TenantId == tenantId)
    }, this.GetType().Assembly
    );
}

結束語

這次全方位介紹 FreeSql 的功能,只抽取了重要內容釋出,由於功能實在太多不方便在一篇文章介紹祥盡。

 

我個人是非常想展開編寫,將每個功能的設計和實現放大來介紹,但還是先希望得到更多人的關註,不然就是一臺獨角戲了。

已同步到看一看
贊(0)

分享創造快樂