這篇文章是我有史以來編輯最長時間的,歷時 4小時!!!本來我能夠利用這 4小時編寫一堆膠水代碼,真心但願善良的您點個贊,謝謝了!!前端
好久好久沒有寫文章了,上一次仍是在元旦發佈 1.0 版本的時候,今年版本規劃是每個月底發佈小版本(年末發佈 2.0),整年的開源工做主要是收集用戶需求增長功能,完善測試,修復 bug。FreeSql 1.0 -> 1.5 相隔半年有哪些新功能?只能說每一個功能都能讓我興奮,而且能感覺到使用者也同樣興奮(妄想症)。mysql
火燒眉毛的人會問,這更新速度也太快了吧,升級會不會有問題?git
FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、XAUI、Blazor、以及還有說不出來的運行平臺,由於代碼綠色無依賴,支持新平臺很是簡單。目前單元測試數量:4000+,Nuget下載數量:123K+,源碼幾乎天天都有提交。值得高興的是 FreeSql 加入了 ncc 開源社區:https://github.com/dotnetcore/FreeSql,加入組織以後社區責任感更大,須要更努力作好品質,爲開源社區出一份力。QQ開發羣:4336577github
爲何要重複造輪子?sql
FreeSql 主要優點在於易用性上,基本是開箱即用,在不一樣數據庫之間切換兼容性比較好。做者花了大量的時間精力在這個項目,肯請您花半小時瞭解下項目,謝謝。數據庫
FreeSql 總體的功能特性以下:編程
1.0 -> 1.5 更新的重要功能以下:json
1、UnitOfWorkManager 工做單元管理器,可實現 Spring 事務設計;c#
2、IFreeSql.InsertOrUpdate 實現批量保存,執行時根據數據庫自動適配執行 merge into 或者 on duplicate key update;後端
3、ISelect.WhereDynamicFilter 方法實現動態過濾條件(與前端交互);
4、自動適配表達式解析 yyyyMMdd 經常使用 c# 日期格式化;
5、IUpdate.SetSourceIgnore 方法實現忽略屬性值爲 null 的字段;
6、FreeSql.Provider.Dameng 基於 DmProvider Ado.net 訪問達夢數據庫;
7、自動識別 EFCore 經常使用的實體特性,FreeSql.DbContext 擁有和 EFCore 高類似度的語法,而且支持 90% 類似的 FluentApi;
8、ISelect.ToTreeList 擴展方法查詢數據,把配置父子導航屬性的實體加工爲樹型 List;
9、BulkCopy 相關方法提高大批量數據插入性能;
10、Sqlite :memrory: 內存模式;
FreeSql 使用很是簡單,只須要定義一個 IFreeSql 對象便可:
static IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, connectionString) .UseAutoSyncStructure(true) //自動同步實體結構到數據庫 .Build(); //請務一定義成 Singleton 單例模式
public class SongService { BaseRepository<Song> _repo; public SongService(BaseRepository<Song> repo) { _repo = repo; } [Transactional] public virtual void Test1() { _repo.Insert(new Song { Title = "卡農1" }); //事務1 this.Test2(); } [Transactional(Propagation = Propagation.Nested)] //嵌套事務,新的(不使用 Test1 的事務) public virtual void Test2() { _repo.Insert(new Song { Title = "卡農2" }); } }
BaseRepository 是 FreeSql.BaseRepository 包實現的通用倉儲類,實際項目中能夠繼承它再使用。
Propagation 的模式參考了 Spring 事務,在如下幾種模式:
UnitOfWorkManager 正是幹這件事的。避免了每次對數據操做都要現得到 Session 實例來啓動事務/提交/回滾事務還有繁瑣的Try/Catch操做。這些也是 AOP(面向切面編程)機制很好的應用。一方面使開發業務邏輯更清晰、專業分工更加容易進行。另外一方面就是應用 AOP 隔離下降了程序的耦合性使咱們能夠在不一樣的應用中將各個切面結合起來使用大大提升了代碼重用度。
使用前準備第一步:配置 Startup.cs 注入
//Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IFreeSql>(fsql); services.AddScoped<UnitOfWorkManager>(); services.AddFreeRepository(null, typeof(Startup).Assembly); }
UnitOfWorkManager 成員 | 說明 |
---|---|
IUnitOfWork Current | 返回當前的工做單元 |
void Binding(repository) | 將倉儲的事務交給它管理 |
IUnitOfWork Begin(propagation, isolationLevel) | 建立工做單元 |
使用前準備第二步:定義事務特性
[AttributeUsage(AttributeTargets.Method)] public class TransactionalAttribute : Attribute { /// <summary> /// 事務傳播方式 /// </summary> public Propagation Propagation { get; set; } = Propagation.Requierd; /// <summary> /// 事務隔離級別 /// </summary> public IsolationLevel? IsolationLevel { get; set; } }
使用前準備第三步:引入動態代理庫
在 Before 從容器中獲取 UnitOfWorkManager,調用它的 var uow = uowManager.Begin(attr.Propagation, attr.IsolationLevel) 方法
在 After 調用 Before 中的 uow.Commit 或者 Rollback 方法,最後調用 uow.Dispose
自問自答:是否是進方法就開事務呢?
不必定是真實事務,有多是虛的,就是一個假的 unitofwork(不帶事務),也有多是延用上一次的事務,也有多是新開事務,具體要看傳播模式。
IFreeSql 定義了 InsertOrUpdate 方法實現批量插入或更新的功能,利用的是數據庫特性進行保存,執行時根據數據庫自動適配:
Database | Features |
---|---|
MySql | on duplicate key update |
PostgreSQL | on conflict do update |
SqlServer | merge into |
Oracle | merge into |
Sqlite | replace into |
Dameng | merge into |
fsql.InsertOrUpdate<T>() .SetSource(items) //須要操做的數據 .ExecuteAffrows();
因爲咱們前面定義 fsql 變量的類型是 MySql,因此執行的語句大概是這樣的:
INSERT INTO `T`(`id`, `name`) VALUES(1, '001'), (2, '002'), (3, '003'), (4, '004') ON DUPLICATE KEY UPDATE `name` = VALUES(`name`)
當實體類有自增屬性時,批量 InsertOrUpdate 最多可被拆成兩次執行,內部計算出未設置自增值、和有設置自增值的數據,分別執行 insert into 和 上面講到的 merge into 兩種命令(採用事務執行)。
是否見過這樣的高級查詢功能,WhereDynamicFilter 在後端能夠輕鬆完成這件事情,前端根據 UI 組裝好對應的 json 字符串傳給後端就行,以下:
DynamicFilterInfo dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(@" { ""Logic"" : ""Or"", ""Filters"" : [ { ""Field"" : ""Code"", ""Operator"" : ""NotContains"", ""Value"" : ""val1"", ""Filters"" : [ { ""Field"" : ""Name"", ""Operator"" : ""NotStartsWith"", ""Value"" : ""val2"", } ] }, { ""Field"" : ""Parent.Code"", ""Operator"" : ""Eq"", ""Value"" : ""val11"", ""Filters"" : [ { ""Field"" : ""Parent.Name"", ""Operator"" : ""Contains"", ""Value"" : ""val22"", } ] } ] } "); fsql.Select<VM_District_Parent>().WhereDynamicFilter(dyfilter).ToList(); //SELECT a.""Code"", a.""Name"", a.""ParentCode"", a__Parent.""Code"" as4, a__Parent.""Name"" as5, a__Parent.""ParentCode"" as6 //FROM ""D_District"" a //LEFT JOIN ""D_District"" a__Parent ON a__Parent.""Code"" = a.""ParentCode"" //WHERE (not((a.""Code"") LIKE '%val1%') AND not((a.""Name"") LIKE 'val2%') OR a__Parent.""Code"" = 'val11' AND (a__Parent.""Name"") LIKE '%val22%')
支持的操做符:Contains/StartsWith/EndsWith/NotContains/NotStartsWith/NotEndsWith、Equals/Eq/NotEqual、GreaterThan/GreaterThanOrEqual、LessThan/LessThanOrEqual
不知道你們有沒有這個困擾,在 ORM 表達式使用 DateTime.Now.ToString("yyyyMM") 是件很難轉換的事,在我適配的這些數據庫中,只有 MsAccess 能夠直接翻譯成對應的 SQL 執行。
這個想法來自另外一個 ORM issues,我時不時會去了解其餘 ORM 優勢和缺陷,以便給 FreeSql 作補充。
想法出來以後當於,也就是昨天 2020/5/24 奮戰一宿完成的,除了每一個數據庫進行編碼適配外,更多的時間耗在了單元測試上,目前已所有經過(4000+單元測試不是吹的)。
僅以此功能讓你們感覺一下 FreeSql 的認真,他不是一些人口中所說的我的項目,謝謝。
var dtn = DateTime.Parse("2020-1-1 0:0:0"); var dts = Enumerable.Range(1, 12).Select(a => dtn.AddMonths(a)) .Concat(Enumerable.Range(1, 31).Select(a => dtn.AddDays(a))) .Concat(Enumerable.Range(1, 24).Select(a => dtn.AddHours(a))) .Concat(Enumerable.Range(1, 60).Select(a => dtn.AddMinutes(a))) .Concat(Enumerable.Range(1, 60).Select(a => dtn.AddSeconds(a))); foreach (var dt in dts) { Assert.Equal(dt.ToString("yyyy-MM-dd HH:mm:ss.fff"), fsql.Select<T>().First(a => dt.ToString())); Assert.Equal(dt.ToString("yyyy-MM-dd HH:mm:ss"), fsql.Select<T>().First(a => dt.ToString("yyyy-MM-dd HH:mm:ss"))); Assert.Equal(dt.ToString("yyyy-MM-dd HH:mm"), fsql.Select<T>().First(a => dt.ToString("yyyy-MM-dd HH:mm"))); Assert.Equal(dt.ToString("yyyy-MM-dd HH"), fsql.Select<T>().First(a => dt.ToString("yyyy-MM-dd HH"))); Assert.Equal(dt.ToString("yyyy-MM-dd"), fsql.Select<T>().First(a => dt.ToString("yyyy-MM-dd"))); Assert.Equal(dt.ToString("yyyy-MM"), fsql.Select<T>().First(a => dt.ToString("yyyy-MM"))); Assert.Equal(dt.ToString("yyyyMMddHHmmss"), fsql.Select<T>().First(a => dt.ToString("yyyyMMddHHmmss"))); Assert.Equal(dt.ToString("yyyyMMddHHmm"), fsql.Select<T>().First(a => dt.ToString("yyyyMMddHHmm"))); Assert.Equal(dt.ToString("yyyyMMddHH"), fsql.Select<T>().First(a => dt.ToString("yyyyMMddHH"))); Assert.Equal(dt.ToString("yyyyMMdd"), fsql.Select<T>().First(a => dt.ToString("yyyyMMdd"))); Assert.Equal(dt.ToString("yyyyMM"), fsql.Select<T>().First(a => dt.ToString("yyyyMM"))); Assert.Equal(dt.ToString("yyyy"), fsql.Select<T>().First(a => dt.ToString("yyyy"))); Assert.Equal(dt.ToString("HH:mm:ss"), fsql.Select<T>().First(a => dt.ToString("HH:mm:ss"))); Assert.Equal(dt.ToString("yyyy MM dd HH mm ss yy M d H hh h"), fsql.Select<T>().First(a => dt.ToString("yyyy MM dd HH mm ss yy M d H hh h"))); Assert.Equal(dt.ToString("yyyy MM dd HH mm ss yy M d H hh h m s tt t").Replace("上午", "AM").Replace("下午", "PM").Replace("上", "A").Replace("下", "P"), fsql.Select<T>().First(a => dt.ToString("yyyy MM dd HH mm ss yy M d H hh h m s tt t"))); }
支持經常使用 c# 日期格式化,yyyy MM dd HH mm ss yy M d H hh h m s tt t
tt t 爲 AM PM
AM PM 這兩個轉換不完美,勉強能使用。
這個功能被用戶提了幾回,每一次都認爲 FreeSql.Repository 的狀態對比能夠完成這件事。
這一次做者心疼他們了,爲何必定要用某個功能限制住使用者?你們是否常常聽誰說 EF框架、MVC框架,框架的定義實際上是約束+規範。
做者不想作這樣的約束,做者更但願儘可能提供多一些實用功能讓用戶本身選擇,把項目定義爲:功能組件。
fsql.Update<Song>() .SetSourceIgnore(item, col => col == null) .ExecuteAffrows();
第二個參數是 Func<object, bool> 類型,col 至關於屬性的值,上面的代碼更新實體 item 的時候會忽略 == null 的屬性。
武漢達夢數據庫有限公司成立於2000年,爲中國電子信息產業集團(CEC)旗下基礎軟件企業,專業從事數據庫管理系統的研發、銷售與服務,同時可爲用戶提供大數據平臺架構諮詢、數據技術方案規劃、產品部署與實施等服務。多年來,達夢公司始終堅持原始創新、獨立研發,目前已掌握數據管理與數據分析領域的核心前沿技術,擁有所有源代碼,具備徹底自主知識產權。
不知道你們沒有據說過相關政策,政府推進國產化之後是趨勢,雖然 .NET 不是國產,可是目前沒法限制編程語言,當下正在對操做系統、數據庫強制推動。
咱們知道 EFCore for oracle 問題多,而且如今還沒更新到 3.x,在這樣的背景下,一個國產數據庫更不能期望誰實現好用的 EFCore。目前看來除了 EFCore for sqlserver 咱們沒把握徹底佔優點,起碼在其餘數據庫確定是咱們更接地氣。
言歸正傳,達夢數據庫其實蠻早就支持了,以前是以 Odbc 的方式實現的,後面根據使用者的反饋 Odbc 環境問題比較麻煩,經研究決定支持 ado.net 適配,讓使用者更加方便。使用 ado.net 方式鏈接達夢只須要修改 IFreeSql 建立時候的類型便可,以下:
static IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Dameng, connectionString) .UseAutoSyncStructure(true) //自動同步實體結構到數據庫 .Build(); //請務一定義成 Singleton 單例模式
EFCore 目前用戶量最多,爲了方便一些項目過渡到 FreeSql,咱們作了一些 「AI」:
[Table("table01")] //這個實際上是 EFCore 的特性 class MyTable { [Key] public int Id { get; set; } }
fsql.CodeFirst.Entity<Song>(eb => { eb.ToTable("tb_song"); eb.Ignore(a => a.Field1); eb.Property(a => a.Title).HasColumnType("varchar(50)").IsRequired(); eb.Property(a => a.Url).HasMaxLength(100); eb.Property(a => a.RowVersion).IsRowVersion(); eb.Property(a => a.CreateTime).HasDefaultValueSql("current_timestamp"); eb.HasKey(a => a.Id); eb.HasIndex(a => new { a.Id, a.Title }).IsUnique().HasName("idx_xxx11"); //一對多、多對一 eb.HasOne(a => a.Type).HasForeignKey(a => a.TypeId).WithMany(a => a.Songs); //多對多 eb.HasMany(a => a.Tags).WithMany(a => a.Songs, typeof(Song_tag)); }); fsql.CodeFirst.Entity<SongType>(eb => { eb.HasMany(a => a.Songs).WithOne(a => a.Type).HasForeignKey(a => a.TypeId); eb.HasData(new[] { new SongType { Id = 1, Name = "流行", Songs = new List<Song>(new[] { new Song{ Title = "真的愛你" }, new Song{ Title = "愛你一萬年" }, }) }, new SongType { Id = 2, Name = "鄉村", Songs = new List<Song>(new[] { new Song{ Title = "鄉里鄉親" }, }) }, }); }); public class SongType { public int Id { get; set; } public string Name { get; set; } public List<Song> Songs { get; set; } } public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string Title { get; set; } public string Url { get; set; } public DateTime CreateTime { get; set; } public int TypeId { get; set; } public SongType Type { get; set; } public int Field1 { get; set; } public long RowVersion { get; set; } }
這是幾個意思?有作過父子關係的表應該知道的,把數據查回來了是平面的,須要再用遞歸轉化爲樹型。考慮到這個功能實用性比較高,因此就集成了進來。來自單元測試的一段代碼:
var repo = fsql.GetRepository<VM_District_Child>(); repo.DbContextOptions.EnableAddOrUpdateNavigateList = true; repo.DbContextOptions.NoneParameter = true; repo.Insert(new VM_District_Child { Code = "100000", Name = "中國", Childs = new List<VM_District_Child>(new[] { new VM_District_Child { Code = "110000", Name = "北京市", Childs = new List<VM_District_Child>(new[] { new VM_District_Child{ Code="110100", Name = "北京市" }, new VM_District_Child{ Code="110101", Name = "東城區" }, }) } }) }); var t3 = fsql.Select<VM_District_Child>().ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); Assert.Equal("110000", t3[0].Childs[0].Code); Assert.Equal(2, t3[0].Childs[0].Childs.Count); Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code);
注意:實體須要配置父子導航屬性
原先 FreeSql 對批量數據操做就作得還能夠,例如批量數據超過數據庫某些限制的,會拆分執行,性能其實也還行。
本需求也是來自用戶,而後就實現了,實現完了我還專門作了性能測試對比,sqlserver bulkcopy 收益比較大,mysql 收益很是小。
測試結果(52個字段,18W-50行數據,單位ms):
18W | 1W | 5K | 500 | 50 | |
---|---|---|---|---|---|
MySql 5.5 ExecuteAffrows | 38,481 | 2,234 | 1,136 | 167 | 30 |
MySql 5.5 ExecuteMySqlBulkCopy | 28,405 | 1,142 | 657 | 592 | 22 |
SqlServer Express ExecuteAffrows | 402,355 | 24,847 | 11,465 | 915 | 88 |
SqlServer Express ExecuteSqlBulkCopy | 21,065 | 578 | 326 | 79 | 48 |
PostgreSQL 10 ExecuteAffrows | 46,756 | 3,294 | 2,269 | 209 | 37 |
PostgreSQL 10 ExecutePgCopy | 10,090 | 583 | 337 | 61 | 25 |
Oracle XE ExecuteAffrows | - | - | - | 10,648 | 200 |
Sqlite ExecuteAffrows | 28,554 | 1,149 | 701 | 91 | 35 |
Oracle 插入性能不用懷疑,可能安裝學生版限制較大
測試結果(10個字段,18W-50行數據,單位ms):
18W | 1W | 5K | 500 | 50 | |
---|---|---|---|---|---|
MySql 5.5 ExecuteAffrows | 11,171 | 866 | 366 | 50 | 34 |
MySql 5.5 ExecuteMySqlBulkCopy | 6,504 | 399 | 257 | 100 | 16 |
SqlServer Express ExecuteAffrows | 47,204 | 2,275 | 1,108 | 123 | 16 |
SqlServer Express ExecuteSqlBulkCopy | 4,248 | 127 | 71 | 14 | 10 |
PostgreSQL 10 ExecuteAffrows | 9,786 | 568 | 336 | 34 | 6 |
PostgreSQL 10 ExecutePgCopy | 4,081 | 167 | 93 | 12 | 2 |
Oracle XE ExecuteAffrows | - | - | - | 731 | 33 |
Sqlite ExecuteAffrows | 4,524 | 246 | 137 | 19 | 11 |
測試結果,是在相同操做系統下進行的,而且都有預熱
ExecuteMySqlBulkCopy 方法在 FreeSql.Provider.MySqlConnector 中實現的
瞭解 EFCore 應該知道有一個 inMemory 實現,Sqlite 其實也有內存模式,因此在很是棒(忍不住)的 FreeSql.Provider.Sqlite 稍加適配就能夠實現 inMemory 模式了。
使用 inMemory 模式很是簡單,只須要修改 IFreeSql 建立的類型,以及鏈接字符串便可:
static IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=:memory:") .UseAutoSyncStructure(true) //自動同步實體結構到數據庫 .Build(); //請務一定義成 Singleton 單例模式
內存模式 + FreeSql CodeFirst 功能,用起來體驗仍是不錯的。由於每次都要遷移結構,fsql 釋放數據就沒了。
終於寫完了,這篇文章是我有史以來編輯最長時間的,歷時 4小時!!!本來我能夠利用這 4小時編寫一堆膠水代碼,卻非要寫推廣的文章,真心但願正在使用的、善良的您能動一動小手指,把文章轉發一下,讓更多人知道 .NET 有這樣一個好用的 ORM 存在。謝謝了!!
FreeSql 開源協議 MIT https://github.com/dotnetcore/FreeSql,能夠商用,文檔齊全。QQ開發羣:4336577
CSRedisCore 說:FreeSql 的待遇也好太多了。
若是你有好的 ORM 實現想法,歡迎給做者留言討論,謝謝觀看!