.NETCore 新型 ORM 功能介紹
簡介
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<Xxx>.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<Xxx>.Where(a => new { 1,2,3 }.Contains(a.Id)).ToList();
4、聯表
fsql.Select<Xxx>.LeftJoin<Yyy>((a, b) => a.YyyId == b.Id).ToList();
5、Exists子表
fsql.Select<Xxx>.Where(a => fsql.Select<Yyy>(b => b.Id == a.YyyId).Any()).ToList();
6、GroupBy & Having
fsql.Select<Xxx>.GroupBy(a => new { a.CategoryId }).Having(a => a.Count > 2).ToList(a => new { a.Key, a.Count() });
7、指定欄位查詢
fsql.Select<Xxx>.Limit(10).ToList(a => a.Id); fsql.Select<Xxx>.Limit(10).ToList(a => new { a.Id, a.Name }); fsql.Select<Xxx>.Limit(10).ToList(a => new Dto());
8、執行SQL返回實體
fsql.Ado.Query<Xxx>("select * from xxx"); fsql.Ado.Query<(int, 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<Xxx>().AppendData(new Xxx()).InsertColumns(a => a.Title).ExecuteAffrows(); fsql.Insert<Xxx>().AppendData(new Xxx()).InsertColumns(a => new { a.Id, a.Title}).ExecuteAffrows();
7、忽略列
fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => a.Title).ExecuteAffrows(); fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => new { a.Id, a.Title}).ExecuteAffrows();
8、事務
fsql.Insert<Xxx>().AppendData(new Xxx()).WithTransaction(事務物件).ExecuteAffrows();
更新
1、指定列
fsql.Update<Xxx>(1).Set(a => a.CreateTime, DateTime.Now).ExecuteAffrows();
2、累加,set clicks = clicks + 1
fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).ExecuteAffrows();
3、儲存
fsql.Update<Xxx>().SetSource(單個實體).ExecuteAffrows();
4、批量儲存
fsql.Update<Xxx>().SetSource(陣列).ExecuteAffrows();
5、忽略列
fsql.Update<Xxx>().SetSource(陣列).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ExecuteAffrows();
6、更新條件
fsql.Update<Xxx>().SetSource(陣列).Where(a => a.Clicks > 100).ExecuteAffrows();
7、事務
fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).WithTransaction(事務物件).ExecuteAffrows();
刪除
1、dywhere
- 主鍵值
- new[] { 主鍵值1, 主鍵值2 }
- Xxx物件
- new[] { Xxx物件1, Xxx物件2 }
- new { id = 1 }
fsql.Delete<Xxx>(new[] { 1, 2 }).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` = 1 OR `Id` = 2) fsql.Delete<Xxx>(new Xxx { Id = 1, Title = "test" }).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` = 1) fsql.Delete<Xxx>(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<Xxx>(new { id = 1 }).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` = 1)
2、條件
fsql.Delete<Xxx>().Where(a => a.Id == 1).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` = 1) fsql.Delete<Xxx>().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<Xxx>().Where(item).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` = 1) var items = new List<Xxx>(); for (var a = 0; a < 10; a++) items.Add(new Xxx { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 }); fsql.Delete<Xxx>().Where(items).ExecuteAffrows(); //DELETE FROM `xxx` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))
3、事務
fsql.Delete<Xxx>().Where(a => a.Id == 1).WithTransaction(事務物件).ExecuteAffrows();
初級篇
表示式
支援功能豐富的表示式函式解析,方便程式設計師在不瞭解資料庫函式的情況下編寫程式碼。這是 FreeSql 非常特色的功能之一,深入細化函式解析儘量做到滿意,所支援的型別基本都可以使用對應的表示式函式,例如 日期、字串、IN查詢、陣列(PostgreSQL的陣列)、字典(PostgreSQL HStore)等等。
1、查詢今天建立的資料
fsql.Delete<Xxx>().Where(a => a.CreateTime.Date == DateTime.Now.Date).ToList();
2、SqlServer 下隨機獲取記錄
fsql.Delete<Xxx>().OrderBy(a => Guid.NewGuid()).Limit(1).ToSql();
4、表示式函式全覽
表示式 | MySql | SqlServer | PostgreSQL | Oracle | 功能說明 |
---|---|---|---|---|---|
a ? b : c | case when a then b else c end | case when a then b else c end | case when a then b else c end | case when a then b else c end | a成立時取b值,否則取c值 |
a ?? b | ifnull(a, b) | isnull(a, b) | coalesce(a, b) | nvl(a, b) | 當a為null時,取b值 |
數字 + 數字 | a + b | a + b | a + b | a + b | 數字相加 |
數字 + 字串 | concat(a, b) | cast(a as varchar) + cast(b as varchar) | case(a as varchar) + b | a+b | 字串相加,a或b任意一個為字串時 |
a - b | a - b | a - b | a - b | a - b | 減 |
a * b | a * b | a * b | a * b | a * b | 乘 |
a / b | a / b | a / b | a / b | a / b | 除 |
a % b | a % b | a % b | a % b | mod(a,b) | 模 |
等等...
5、陣列
表示式 | MySql | SqlServer | PostgreSQL | Oracle | 功能說明 |
---|---|---|---|---|---|
a.Length | - | - | case when a is null then 0 else array_length(a,1) end | - | 陣列長度 |
常量陣列.Length | - | - | array_length(array[常量陣列元素逗號分割],1) | - | 陣列長度 |
a.Any() | - | - | case when a is null then 0 else array_length(a,1) end > 0 | - | 陣列是否為空 |
常量陣列.Contains(b) | b in (常量陣列元素逗號分割) | b in (常量陣列元素逗號分割) | b in (常量陣列元素逗號分割) | b in (常量陣列元素逗號分割) | IN查詢 |
a.Contains(b) | - | - | a @> array[b] | - | a陣列是否包含b元素 |
a.Concat(b) | - | - | a + b | - | 陣列相連 |
a.Count() | - | - | 同 Length | - | 陣列長度 |
一個細節證明 FreeSql 匠心製作
通用的 in 查詢 select.Where(a => new []{ 1,2,3 }.Contains(a.xxx))
假設 xxxs 是 pgsql 的陣列欄位型別,其實會與上面的 in 查詢起衝突,FreeSql 解決了這個矛盾 select.Where(a => a.xxxs.Contains(1))
6、字典 Dictionary<string, string>
表示式 | MySql | SqlServer | PostgreSQL | Oracle | 功能說明 |
---|---|---|---|---|---|
a.Count | - | - | case when a is null then 0 else array_length(akeys(a),1) end | - | 字典長度 |
a.Keys | - | - | akeys(a) | - | 返回字典所有key陣列 |
a.Values | - | - | avals(a) | - | 返回字典所有value陣列 |
a.Contains(b) | - | - | a @> b | - | 字典是否包含b |
a.ContainsKey(b) | - | - | a? b | - | 字典是否包含key |
a.Concat(b) | - | - | a + b | - | 字典相連 |
a.Count() | - | - | 同 Count | - | 字典長度 |
7、JSON JToken/JObject/JArray
表示式 | MySql | SqlServer | PostgreSQL | Oracle | 功能說明 |
---|---|---|---|---|---|
a.Count | - | - | jsonb_array_length(coalesce(a, '[])) | - | json陣列型別的長度 |
a.Any() | - | - | jsonb_array_length(coalesce(a, '[])) > 0 | - | json陣列型別,是否為空 |
a.Contains(b) | - | - | coalesce(a, '{}') @> b::jsonb | - | json中是否包含b |
a.ContainsKey(b) | - | - | coalesce(a, '{}') ? b | - | json中是否包含鍵b |
a.Concat(b) | - | - | coalesce(a, '{}') + b::jsonb | - | 連線兩個json |
Parse(a) | - | - | a::jsonb | - | 轉化字串為json型別 |
8、字串
表示式 | MySql | SqlServer | PostgreSQL | Oracle | Sqlite |
---|---|---|---|---|---|
string.Empty | '' | '' | '' | '' | |
string.IsNullOrEmpty(a) | (a is null or a = '') | (a is null or a = '') | (a is null or a = '') | (a is null or a = '') | (a is null or a = '') |
a.CompareTo(b) | strcmp(a, b) | - | case when a = b then 0 when a > b then 1 else -1 end | case when a = b then 0 when a > b then 1 else -1 end | case when a = b then 0 when a > b then 1 else -1 end |
a.Contains('b') | a like '%b%' | a like '%b%' | a ilike'%b%' | a like '%b%' | a like '%b%' |
a.EndsWith('b') | a like '%b' | a like '%b' | a ilike'%b' | a like '%b' | a like '%b' |
a.IndexOf(b) | locate(a, b) - 1 | locate(a, b) - 1 | strpos(a, b) - 1 | instr(a, b, 1, 1) - 1 | instr(a, b) - 1 |
a.Length | char_length(a) | len(a) | char_length(a) | length(a) | length(a) |
a.PadLeft(b, c) | lpad(a, b, c) | - | lpad(a, b, c) | lpad(a, b, c) | lpad(a, b, c) |
a.PadRight(b, c) | rpad(a, b, c) | - | rpad(a, b, c) | rpad(a, b, c) | rpad(a, b, c) |
a.Replace(b, c) | replace(a, b, c) | replace(a, b, c) | replace(a, b, c) | replace(a, b, c) | replace(a, b, c) |
a.StartsWith('b') | a like 'b%' | a like 'b%' | a ilike'b%' | a like 'b%' | a like 'b%' |
a.Substring(b, c) | substr(a, b, c + 1) | substring(a, b, c + 1) | substr(a, b, c + 1) | substr(a, b, c + 1) | substr(a, b, c + 1) |
a.ToLower | lower(a) | lower(a) | lower(a) | lower(a) | lower(a) |
a.ToUpper | upper(a) | upper(a) | upper(a) | upper(a) | upper(a) |
a.Trim | trim(a) | trim(a) | trim(a) | trim(a) | trim(a) |
a.TrimEnd | rtrim(a) | rtrim(a) | rtrim(a) | rtrim(a) | rtrim(a) |
a.TrimStart | ltrim(a) | ltrim(a) | ltrim(a) | ltrim(a) | ltrim(a) |
使用字串函式可能會出現效能瓶頸,雖然不推薦使用,但是作為功能庫這也是不可缺少的功能之一。
9、日期
表示式 | MySql | SqlServer | PostgreSQL | Oracle | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DateTime.Now | now() | getdate() | current_timestamp | systimestamp | ||||||||||||
DateTime.UtcNow | utc_timestamp() | getutcdate() | (current_timestamp at time zone 'UTC') | sys_extract_utc(systimestamp) | ||||||||||||
DateTime.Today | curdate | convert(char(10),getdate(),120) | current_date | trunc(systimestamp) | ||||||||||||
DateTime.MaxValue | cast('9999/12/31 23:59:59' as datetime) | '9999/12/31 23:59:59' | '9999/12/31 23:59:59'::timestamp | to_timestamp('9999-12-31 23:59:59','YYYY-MM-DD HH24:MI:SS.FF6') | ||||||||||||
DateTime.MinValue | cast('0001/1/1 0:00:00' as datetime) | '1753/1/1 0:00:00' | '0001/1/1 0:00:00'::timestamp | to_timestamp('0001-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS.FF6') | ||||||||||||
DateTime.Compare(a, b) | a - b | a - b | extract(epoch from a::timestamp-b::timestamp) | extract(day from (a-b)) | ||||||||||||
DateTime.DaysInMonth(a, b) | dayofmonth(last_day(concat(a, '-', b, '-1'))) | datepart(day, dateadd(day, -1, dateadd(month, 1, cast(a as varchar) + '-' + cast(b as varchar) + '-1'))) | extract(day from (a | '-' | b | '-01')::timestamp+'1 month'::interval-'1 day'::interval) | cast(to_char(last_day(a | '-' | b | '-01'),'DD') as number) | ||||||
DateTime.Equals(a, b) | a = b | a = b | a = b | a = b | ||||||||||||
DateTime.IsLeapYear(a) | a%4=0 and a%100<>0 or a%400=0 | a%4=0 and a%100<>0 or a%400=0 | a%4=0 and a%100<>0 or a%400=0 | mod(a,4)=0 AND mod(a,100)<>0 OR mod(a,400)=0 | ||||||||||||
DateTime.Parse(a) | cast(a as datetime) | cast(a as datetime) | a::timestamp | to_timestamp(a,'YYYY-MM-DD HH24:MI:SS.FF6') | ||||||||||||
a.Add(b) | date_add(a, interval b microsecond) | dateadd(millisecond, b / 1000, a) | a::timestamp+(b | ' microseconds')::interval | 增加TimeSpan值 | a + b | ||||||||||
a.AddDays(b) | date_add(a, interval b day) | dateadd(day, b, a) | a::timestamp+(b | ' day')::interval | a + b | |||||||||||
a.AddHours(b) | date_add(a, interval b hour) | dateadd(hour, b, a) | a::timestamp+(b | ' hour')::interval | a + b/24 | |||||||||||
a.AddMilliseconds(b) | date_add(a, interval b*1000 microsecond) | dateadd(millisecond, b, a) | a::timestamp+(b | ' milliseconds')::interval | a + b/86400000 | |||||||||||
a.AddMinutes(b) | date_add(a, interval b minute) | dateadd(minute, b, a) | a::timestamp+(b | ' minute')::interval | a + b/1440 | |||||||||||
a.AddMonths(b) | date_add(a, interval b month) | dateadd(month, b, a) | a::timestamp+(b | ' month')::interval | add_months(a,b) | |||||||||||
a.AddSeconds(b) | date_add(a, interval b second) | dateadd(second, b, a) | a::timestamp+(b | ' second')::interval | a + b/86400 | |||||||||||
a.AddTicks(b) | date_add(a, interval b/10 microsecond) | dateadd(millisecond, b / 10000, a) | a::timestamp+(b | ' microseconds')::interval | a + b/86400000000 | |||||||||||
a.AddYears(b) | date_add(a, interval b year) | dateadd(year, b, a) | a::timestamp+(b | ' year')::interval | add_months(a,b*12) | |||||||||||
a.Date | cast(date_format(a, '%Y-%m-%d') as datetime) | convert(char(10),a,120) | a::date | trunc(a) | ||||||||||||
a.Day | dayofmonth(a) | datepart(day, a) | extract(day from a::timestamp) | cast(to_char(a,'DD') as number) | ||||||||||||
a.DayOfWeek | dayofweek(a) | datepart(weekday, a) - 1 | extract(dow from a::timestamp) | case when to_char(a)='7' then 0 else cast(to_char(a) as number) end | ||||||||||||
a.DayOfYear | dayofyear(a) | datepart(dayofyear, a) | extract(doy from a::timestamp) | cast(to_char(a,'DDD') as number) | ||||||||||||
a.Hour | hour(a) | datepart(hour, a) | extract(hour from a::timestamp) | cast(to_char(a,'HH24') as number) | ||||||||||||
a.Millisecond | floor(microsecond(a) / 1000) | datepart(millisecond, a) | extract(milliseconds from a::timestamp)-extract(second from a::timestamp)*1000 | cast(to_char(a,'FF3') as number) | ||||||||||||
a.Minute | minute(a) | datepart(minute, a) | extract(minute from a::timestamp) | cast(to_char(a,'MI') as number) | ||||||||||||
a.Month | month(a) | datepart(month, a) | extract(month from a::timestamp) | cast(to_char(a,'FF3') as number) | ||||||||||||
a.Second | second(a) | datepart(second, a) | extract(second from a::timestamp) | cast(to_char(a,'SS') as number) | ||||||||||||
a.Subtract(b) | timestampdiff(microsecond, b, a) | datediff(millisecond, b, a) * 1000 | (extract(epoch from a::timestamp-b::timestamp)*1000000) | a - b | ||||||||||||
a.Ticks | timestampdiff(microsecond, '0001-1-1', a) * 10 | datediff(millisecond, '1970-1-1', a) * 10000 + 621355968000000000 | extract(epoch from a::timestamp)*10000000+621355968000000000 | cast(to_char(a,'FF7') as number) | ||||||||||||
a.TimeOfDay | timestampdiff(microsecond, date_format(a, '%Y-%m-%d'), a) | '1970-1-1 ' + convert(varchar, a, 14) | extract(epoch from a::time)*1000000 | a - trunc(a) | ||||||||||||
a.Year | year(a) | datepart(year, a) | extract(year from a::timestamp) | 年 | cast(to_char(a,'YYYY') as number) | |||||||||||
a.Equals(b) | a = b | a = b | a = b | a = b | ||||||||||||
a.CompareTo(b) | a - b | a - b | a - b | a - b | ||||||||||||
a.ToString() | date_format(a, '%Y-%m-%d %H:%i:%s.%f') | convert(varchar, a, 121) | to_char(a, 'YYYY-MM-DD HH24:MI:SS.US') | to_char(a,'YYYY-MM-DD HH24:MI:SS.FF6') |
10、時間
表示式 | MySql(微秒) | SqlServer(秒) | PostgreSQL(微秒) | Oracle(Interval day(9) to second(7)) | |
---|---|---|---|---|---|
TimeSpan.Zero | 0 | 0 | - | 0微秒 | numtodsinterval(0,'second') |
TimeSpan.MaxValue | 922337203685477580 | 922337203685477580 | - | numtodsinterval(233720368.5477580,'second') | |
TimeSpan.MinValue | -922337203685477580 | -922337203685477580 | - | numtodsinterval(-233720368.5477580,'second') | |
TimeSpan.Compare(a, b) | a - b | a - b | - | extract(day from (a-b)) | |
TimeSpan.Equals(a, b) | a = b | a = b | - | a = b | |
TimeSpan.FromDays(a) | a 1000000 60 60 24 | a 1000000 60 60 24 | - | numtodsinterval(a*86400,'second') | |
TimeSpan.FromHours(a) | a 1000000 60 * 60 | a 1000000 60 * 60 | - | numtodsinterval(a*3600,'second') | |
TimeSpan.FromMilliseconds(a) | a * 1000 | a * 1000 | - | numtodsinterval(a/1000,'second') | |
TimeSpan.FromMinutes(a) | a 1000000 60 | a 1000000 60 | - | numtodsinterval(a*60,'second') | |
TimeSpan.FromSeconds(a) | a * 1000000 | a * 1000000 | - | numtodsinterval(a,'second') | |
TimeSpan.FromTicks(a) | a / 10 | a / 10 | - | numtodsinterval(a/10000000,'second') | |
a.Add(b) | a + b | a + b | - | a + b | |
a.Subtract(b) | a - b | a - b | - | a - b | |
a.CompareTo(b) | a - b | a - b | - | extract(day from (a-b)) | |
a.Days | a div (1000000 60 60 * 24) | a div (1000000 60 60 * 24) | - | extract(day from a) | |
a.Hours | a div (1000000 60 60) mod 24 | a div (1000000 60 60) mod 24 | - | extract(hour from a) | |
a.Milliseconds | a div 1000 mod 1000 | a div 1000 mod 1000 | - | cast(substr(extract(second from a)-floor(extract(second from a)),2,3) as number) | |
a.Seconds | a div 1000000 mod 60 | a div 1000000 mod 60 | - | extract(second from a) | |
a.Ticks | a * 10 | a * 10 | - | (extract(day from a) 86400+extract(hour from a) 3600+extract(minute from a) 60+extract(second from a)) 10000000 | |
a.TotalDays | a / (1000000 60 60 * 24) | a / (1000000 60 60 * 24) | - | extract(day from a) | |
a.TotalHours | a / (1000000 60 60) | a / (1000000 60 60) | - | (extract(day from a)*24+extract(hour from a)) | |
a.TotalMilliseconds | a / 1000 | a / 1000 | - | (extract(day from a) 86400+extract(hour from a) 3600+extract(minute from a) 60+extract(second from a)) 1000 | |
a.TotalMinutes | a / (1000000 * 60) | a / (1000000 * 60) | - | (extract(day from a) 1440+extract(hour from a) 60+extract(minute from a)) | |
a.TotalSeconds | a / 1000000 | a / 1000000 | - | (extract(day from a) 86400+extract(hour from a) 3600+extract(minute from a)*60+extract(second from a)) | |
a.Equals(b) | a = b | a = b | - | a = b | |
a.ToString() | cast(a as varchar) | cast(a as varchar) | - | to_char(a) |
11、數學函式
表示式 | MySql | SqlServer | PostgreSQL | Oracle |
---|---|---|---|---|
Math.Abs(a) | abs(a) | abs(a) | abs(a) | |
Math.Acos(a) | acos(a) | acos(a) | acos(a) | acos(a) |
Math.Asin(a) | asin(a) | asin(a) | asin(a) | asin(a) |
Math.Atan(a) | atan(a) | atan(a) | atan(a) | atan(a) |
Math.Atan2(a, b) | atan2(a, b) | atan2(a, b) | atan2(a, b) | - |
Math.Ceiling(a) | ceiling(a) | ceiling(a) | ceiling(a) | ceil(a) |
Math.Cos(a) | cos(a) | cos(a) | cos(a) | cos(a) |
Math.Exp(a) | exp(a) | exp(a) | exp(a) | exp(a) |
Math.Floor(a) | floor(a) | floor(a) | floor(a) | floor(a) |
Math.Log(a) | log(a) | log(a) | log(a) | log(e,a) |
Math.Log10(a) | log10(a) | log10(a) | log10(a) | log(10,a) |
Math.PI(a) | 3.1415926535897931 | 3.1415926535897931 | 3.1415926535897931 | 3.1415926535897931 |
Math.Pow(a, b) | pow(a, b) | power(a, b) | pow(a, b) | power(a, b) |
Math.Round(a, b) | round(a, b) | round(a, b) | round(a, b) | round(a, b) |
Math.Sign(a) | sign(a) | sign(a) | sign(a) | sign(a) |
Math.Sin(a) | sin(a) | sin(a) | sin(a) | sin(a) |
Math.Sqrt(a) | sqrt(a) | sqrt(a) | sqrt(a) | sqrt(a) |
Math.Tan(a) | tan(a) | tan(a) | tan(a) | tan(a) |
Math.Truncate(a) | truncate(a, 0) | floor(a) | trunc(a, 0) | trunc(a, 0) |
12、型別轉換
表示式 | MySql | SqlServer | PostgreSQL | Oracle | Sqlite |
---|---|---|---|---|---|
Convert.ToBoolean(a), bool.Parse(a) | a not in ('0','false') | a not in ('0','false') | a::varchar not in ('0','false','f','no') | - | a not in ('0','false') |
Convert.ToByte(a), byte.Parse(a) | cast(a as unsigned) | cast(a as tinyint) | a::int2 | cast(a as number) | cast(a as int2) |
Convert.ToChar(a) | substr(cast(a as char),1,1) | substring(cast(a as nvarchar),1,1) | substr(a::char,1,1) | substr(to_char(a),1,1) | substr(cast(a as character),1,1) |
Convert.ToDateTime(a), DateTime.Parse(a) | cast(a as datetime) | cast(a as datetime) | a::timestamp | to_timestamp(a,'YYYY-MM-DD HH24:MI:SS.FF6') | datetime(a) |
Convert.ToDecimal(a), decimal.Parse(a) | cast(a as decimal(36,18)) | cast(a as decimal(36,19)) | a::numeric | cast(a as number) | cast(a as decimal(36,18)) |
Convert.ToDouble(a), double.Parse(a) | cast(a as decimal(32,16)) | cast(a as decimal(32,16)) | a::float8 | cast(a as number) | cast(a as double) |
Convert.ToInt16(a), short.Parse(a) | cast(a as signed) | cast(a as smallint) | a::int2 | cast(a as number) | cast(a as smallint) |
Convert.ToInt32(a), int.Parse(a) | cast(a as signed) | cast(a as int) | a::int4 | cast(a as number) | cast(a as smallint) |
Convert.ToInt64(a), long.Parse(a) | cast(a as signed) | cast(a as bigint) | a::int8 | cast(a as number) | cast(a as smallint) |
Convert.ToSByte(a), sbyte.Parse(a) | cast(a as signed) | cast(a as tinyint) | a::int2 | cast(a as number) | cast(a as smallint) |
Convert.ToString(a) | cast(a as decimal(14,7)) | cast(a as decimal(14,7)) | a::float4 | to_char(a) | cast(a as character) |
Convert.ToSingle(a), float.Parse(a) | cast(a as char) | cast(a as nvarchar) | a::varchar | cast(a as number) | cast(a as smallint) |
Convert.ToUInt16(a), ushort.Parse(a) | cast(a as unsigned) | cast(a as smallint) | a::int2 | cast(a as number) | cast(a as unsigned) |
Convert.ToUInt32(a), uint.Parse(a) | cast(a as unsigned) | cast(a as int) | a::int4 | cast(a as number) | cast(a as decimal(10,0)) |
Convert.ToUInt64(a), ulong.Parse(a) | cast(a as unsigned) | cast(a as bigint) | a::int8 | cast(a as number) | cast(a as decimal(21,0)) |
Guid.Parse(a) | substr(cast(a as char),1,36) | cast(a as uniqueidentifier) | a::uuid | substr(to_char(a),1,36) | substr(cast(a as character),1,36) |
Guid.NewGuid() | - | newid() | - | - | - |
new Random().NextDouble() | rand() | rand() | random() | dbms_random.value | random() |
CodeFirst
引數選項 | 說明 |
---|---|
IsAutoSyncStructure | 【開發環境必備】自動同步實體結構到資料庫,程式執行中檢查實體表是否存在,然後建立或修改 |
IsSyncStructureToLower | 轉小寫同步結構 |
IsSyncStructureToUpper | 轉大寫同步結構,適用 Oracle |
IsConfigEntityFromDbFirst | 使用資料庫的主鍵和自增,適用 DbFirst 模式,無須在實體型別上設定 [Column(IsPrimary)] 或者 ConfigEntity。此功能目前可用於 mysql/sqlserver/postgresql。 |
IsNoneCommandParameter | 不使用命令引數化執行,針對 Insert/Update,除錯神器 |
IsLazyLoading | 延時載入導航屬性物件,導航屬性需要宣告 virtual |
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<Tag> Tags { get; set; } [Column(IsVersion = true)] public long versionRow { get; set; } }
2、在外部配置實體
fsql.CodeFirst .ConfigEntity<Song>(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:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", //模板目錄(事先下載) @"C:\Users\28810\Desktop\你的目錄", //生成後儲存的目錄 "cccddd" //資料庫 );
高階篇
Repository 倉儲實現
1、單個倉儲
var curd = fsql.GetRepository<Xxx, int>(); //curd.Find(1); var item = curd.Get(1); curd.Update(item); curd.Insert(item); curd.Delete(1); curd.Select.Limit(10).ToList();
2、工作單元
using (var uow = fsql.CreateUnitOfWork()) { var songRepos = uow.GetRepository<Song>(); var userRepos = uow.GetRepository<User>(); //上面兩個倉儲,由同一UnitOfWork uow 建立 //在此執行倉儲操作 //這裡不受非同步方便影響 uow.Commit(); }
3、區域性過濾器 + 資料驗證
var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);
之後在使用 topicRepository 操作方法時:
- 查詢/修改/刪除時附過濾條件,從而達到不會修改其他使用者的資料;
- 新增時,使用過濾條件驗證合法性,若不合法則丟擲異常;如以下方法就會報錯:
topicRepository.Insert(new Topic { UserId = 2 })
4、樂觀鎖
更新實體資料,在併發情況下極容易造成舊資料將新的記錄更新。FreeSql 核心部分已經支援樂觀鎖。
樂觀鎖的原理,是利用實體某欄位,如:long version,更新前先查詢資料,此時 version 為 1,更新時產生的 SQL 會附加 where version = 1,當修改失敗時(即 Affrows == 0)丟擲異常。
每個實體只支援一個樂觀鎖,在屬性前標記特性:[Column(IsVersion = true)] 即可。
無論是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都會增加 1
5、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<Tag> Tags { get; set; } } public class SongContext : DbContext { public DbSet<Song> Songs { get; set; } public DbSet<Tag> Tags { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder.UseFreeSql(fsql); } }
導航屬性
支援 1對1、1對多、多對1、多對多 的約定導航屬性配置,主要用於表示式內部查詢;
//OneToOne、ManyToOne var t0 = fsql.Select<Tag>().Where(a => a.Parent.Parent.Name == "粵語").ToList(); //OneToMany var t1 = fsql.Select<Tag>().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToList(); //ManyToMany var t2 = fsql.Select<Song>().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<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}");
上面我們得到一個日誌倉儲按年月分表,使用它 CURD 最終會操作 Log_201903 表。
合併兩個倉儲,實現分表下的聯表查詢:
fsql.GetGuidRepository<User>().Select.FromRepository(logRepository) .LeftJoin<Log>(b => b.UserId == a.Id) .ToList();
租戶
1、按租戶欄位區分
FreeSql.Repository 現實了 filter(過濾與驗證)功能,如:
var topicRepos = fsql.GetGuidRepository<Topic>(t => t.TerantId == 1);
使用 topicRepos 物件進行 CURD 方法:
- 在查詢/修改/刪除時附加此條件,從而達到不會修改 TerantId != 1 的資料;
- 在新增時,使用表示式驗證資料的合法性,若不合法則丟擲異常;
利用這個功能,我們可以很方便的實現資料分割槽,達到租戶的目的。
2、按租戶分表
FreeSql.Repository 現實了 分表功能,如:
var tenantId = 1; var reposTopic = orm.GetGuidRepository<Topic>(null, oldname => $"{oldname}{tenantId}");
上面我們得到一個倉儲按租戶分表,使用它 CURD 最終會操作 Topic_1 表。
3、按租戶分庫
與方案二相同,只是表儲存的位置不同。
4、全域性設定
通過注入的方式設定倉儲類的全域性過濾器。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IFreeSql>(Fsql); services.AddFreeRepository(filter => { var tenantId = 求出當前租戶id; filter .Apply<ISoftDelete>("softdelete", a => a.IsDeleted == false) .Apply<ITenant>("tenant", a => a.TenantId == tenantId) }, this.GetType().Assembly ); }
結束語
這次全方位介紹 FreeSql 的功能,只抽取了重要內容釋出,由於功能實在太多不方便在一篇文章介紹祥盡。
我個人是非常想展開編寫,將每個功能的設計和實現放大來介紹,但還是先希望得到更多人的關注,不然就是一臺獨角戲了。
gayhub: https://github.com/2881099/FreeSql ,肯請獻上寶貴的一星,謝謝!