作者: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 的功能,只抽取了重要內容釋出,由於功能實在太多不方便在一篇文章介紹祥盡。
我個人是非常想展開編寫,將每個功能的設計和實現放大來介紹,但還是先希望得到更多人的關註,不然就是一臺獨角戲了。
朋友會在“發現-看一看”看到你“在看”的內容