.NETCore 新型 ORM 功能介紹

簡介

FreeSql 是一個功能強大的 .NETStandard 庫,用於對象關係映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+。mysql

定義

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

入門篇

查詢

一、查詢一條nginx

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

二、分頁:第1頁,每頁20條git

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

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

三、INgithub

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

四、聯表sql

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

五、Exists子表數據庫

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

六、GroupBy & Havingjson

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

七、指定字段查詢數組

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());

八、執行SQL返回實體服務器

fsql.Ado.Query<Xxx>("select * from xxx");

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

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

插入

一、單條

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

二、單條,返回自增值

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

三、單條,返回插入的行(SqlServer 的 output 特性)

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

四、批量

fsql.Insert<Xxx>().AppendData(數組).ExecuteAffrows();

五、批量,返回插入的行(SqlServer 的 output 特性)

fsql.Insert<Xxx>().AppendData(數組).ExecuteInserted();

六、指定列

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();

七、忽略列

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();

八、事務

fsql.Insert<Xxx>().AppendData(new Xxx()).WithTransaction(事務對象).ExecuteAffrows();

更新

一、指定列

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

二、累加,set clicks = clicks + 1

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

三、保存

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

四、批量保存

fsql.Update<Xxx>().SetSource(數組).ExecuteAffrows();

五、忽略列

fsql.Update<Xxx>().SetSource(數組).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ExecuteAffrows();

六、更新條件

fsql.Update<Xxx>().SetSource(數組).Where(a => a.Clicks > 100).ExecuteAffrows();

七、事務

fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).WithTransaction(事務對象).ExecuteAffrows();

刪除

一、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)

二、條件

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))

三、事務

fsql.Delete<Xxx>().Where(a => a.Id == 1).WithTransaction(事務對象).ExecuteAffrows();

初級篇

表達式

支持功能豐富的表達式函數解析,方便程序員在不瞭解數據庫函數的狀況下編寫代碼。這是 FreeSql 很是特點的功能之一,深刻細化函數解析儘可能作到滿意,所支持的類型基本均可以使用對應的表達式函數,例如 日期、字符串、IN查詢、數組(PostgreSQL的數組)、字典(PostgreSQL HStore)等等。

一、查找今天建立的數據

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

二、SqlServer 下隨機獲取記錄

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

四、表達式函數全覽

表達式 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)
等等...

五、數組

表達式 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))

六、字典 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 - 字典長度

七、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類型

八、字符串

表達式 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)
使用字符串函數可能會出現性能瓶頸,雖然不推薦使用,可是做爲功能庫這也是不可缺乏的功能之一。

九、日期

表達式 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')

十、時間

表達式 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)

十一、數學函數

表達式 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)

十二、類型轉換

表達式 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

一、配置實體(特性)

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; }
}

二、在外部配置實體

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

DbFirst

一、獲取全部數據庫

fsql.DbFirst.GetDatabases();
//返回字符串數組, ["cccddd", "test"]

二、獲取指定數據庫的表信息

fsql.DbFirst.GetTablesByDatabase(fsql.DbFirst.GetDatabases()[0]);
//返回包括表、列詳情、主鍵、惟一鍵、索引、外鍵、備註等信息

三、生成實體

new FreeSql.Generator.TemplateGenerator()
.Build(fsql.DbFirst, 
    @"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", 
    //模板目錄(事先下載)
    @"C:\Users\28810\Desktop\你的目錄",
    //生成後保存的目錄
    "cccddd"
    //數據庫
);

高級篇

Repository 倉儲實現

一、單個倉儲

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();

二、工做單元

using (var uow = fsql.CreateUnitOfWork()) {
    var songRepos = uow.GetRepository<Song>();
    var userRepos = uow.GetRepository<User>();

    //上面兩個倉儲,由同一UnitOfWork uow 建立
    //在此執行倉儲操做
    
    //這裏不受異步方便影響

    uow.Commit();
}

三、局部過濾器 + 數據驗證

var topicRepository = fsql.GetGuidRepository<Topic>(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<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對多、多對一、多對多 的約定導航屬性配置,主要用於表達式內部查詢;

//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();

不朽篇

讀寫分離

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

各類數據庫的讀寫方案不一,數據庫端開啓讀寫分離功能後,讀寫分離的實現大體分爲如下幾種:

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

二、中件間,如MyCat,MySql能夠其餘數據庫怎麼辦?

三、在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();

租戶

一、按租戶字段區分

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

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

使用 topicRepos 對象進行 CURD 方法:

  • 在查詢/修改/刪除時附加此條件,從而達到不會修改 TerantId != 1 的數據;
  • 在添加時,使用表達式驗證數據的合法性,若不合法則拋出異常;

利用這個功能,咱們能夠很方便的實現數據分區,達到租戶的目的。

二、按租戶分表

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

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

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

三、按租戶分庫

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

四、全局設置

經過注入的方式設置倉儲類的全局過濾器。

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,肯請獻上寶貴的一星,謝謝!

相關文章
相關標籤/搜索