天天看點

.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 a成立時取b值,否則取c值
a ?? b ifnull(a, b) isnull(a, b) coalesce(a, b) nvl(a, b) 當a為null時,取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 mod(a,b)
等等...

5、數組

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 (常量數組元素逗号分割) IN查詢
a.Contains(b) a @> array[b] a數組是否包含b元素
a.Concat(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>

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 @> b 字典是否包含b
a.ContainsKey(b) a? b 字典是否包含key
字典相連
同 Count

7、JSON JToken/JObject/JArray

jsonb_array_length(coalesce(a, '[])) json數組類型的長度
jsonb_array_length(coalesce(a, '[])) > 0 json數組類型,是否為空
coalesce(a, '{}') @> b::jsonb json中是否包含b
coalesce(a, '{}') ? b json中是否包含鍵b
coalesce(a, '{}') + b::jsonb 連接配接兩個json
Parse(a) a::jsonb 轉化字元串為json類型

8、字元串

Sqlite
string.Empty ''
string.IsNullOrEmpty(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
a.Contains('b') a like '%b%' a ilike'%b%'
a.EndsWith('b') a like '%b' a ilike'%b'
a.IndexOf(b) locate(a, b) - 1 strpos(a, b) - 1 instr(a, b, 1, 1) - 1 instr(a, b) - 1
char_length(a) len(a) length(a)
a.PadLeft(b, c) lpad(a, b, c)
a.PadRight(b, c) rpad(a, b, c)
a.Replace(b, c) replace(a, b, c)
a.StartsWith('b') a like 'b%' a ilike'b%'
a.Substring(b, c) substr(a, b, c + 1) substring(a, b, c + 1)
a.ToLower lower(a)
a.ToUpper upper(a)
a.Trim trim(a)
a.TrimEnd rtrim(a)
a.TrimStart ltrim(a)
使用字元串函數可能會出現性能瓶頸,雖然不推薦使用,但是作為功能庫這也是不可缺少的功能之一。

9、日期

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) 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
DateTime.Equals(a, b) a = b
DateTime.IsLeapYear(a) 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) 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
a.AddDays(b) date_add(a, interval b day) dateadd(day, b, a)
a.AddHours(b) date_add(a, interval b hour) dateadd(hour, b, a)
a.AddMilliseconds(b) date_add(a, interval b*1000 microsecond) dateadd(millisecond, b, a)
a.AddMinutes(b) date_add(a, interval b minute) dateadd(minute, b, a)
a.AddMonths(b) date_add(a, interval b month) dateadd(month, b, a)
a.AddSeconds(b) date_add(a, interval b second) dateadd(second, b, a)
a.AddTicks(b) date_add(a, interval b/10 microsecond) dateadd(millisecond, b / 10000, a)
a.AddYears(b) date_add(a, interval b year) dateadd(year, b, a)
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)
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.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)
a.Equals(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微秒
TimeSpan.MaxValue 922337203685477580 numtodsinterval(233720368.5477580,'second')
TimeSpan.MinValue -922337203685477580 numtodsinterval(-233720368.5477580,'second')
TimeSpan.Compare(a, b)
TimeSpan.Equals(a, b)
TimeSpan.FromDays(a) a * 1000000 * 60 * 60 * 24 numtodsinterval(a*86400,'second')
TimeSpan.FromHours(a) a * 1000000 * 60 * 60 numtodsinterval(a*3600,'second')
TimeSpan.FromMilliseconds(a) a * 1000 numtodsinterval(a/1000,'second')
TimeSpan.FromMinutes(a) a * 1000000 * 60 numtodsinterval(a*60,'second')
TimeSpan.FromSeconds(a) a * 1000000 numtodsinterval(a,'second')
TimeSpan.FromTicks(a) a / 10 numtodsinterval(a/10000000,'second')
a.Days a div (1000000 * 60 * 60 * 24) extract(day from a)
a.Hours a div (1000000 * 60 * 60) mod 24 extract(hour from a)
a.Milliseconds 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 extract(second from a)
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.TotalHours a / (1000000 * 60 * 60) (extract(day from a)*24+extract(hour from a))
a.TotalMilliseconds 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.TotalSeconds a / 1000000 (extract(day from a)86400+extract(hour from a)3600+extract(minute from a)*60+extract(second from a))
cast(a as varchar) to_char(a)

11、數學函數

Math.Abs(a) abs(a)
Math.Acos(a) acos(a)
Math.Asin(a) asin(a)
Math.Atan(a) atan(a)
Math.Atan2(a, b) atan2(a, b)
Math.Ceiling(a) ceiling(a) ceil(a)
Math.Cos(a) cos(a)
Math.Exp(a) exp(a)
Math.Floor(a) floor(a)
Math.Log(a) log(a) log(e,a)
Math.Log10(a) log10(a) log(10,a)
Math.PI(a) 3.1415926535897931
Math.Pow(a, b) pow(a, b) power(a, b)
Math.Round(a, b) round(a, b)
Math.Sign(a) sign(a)
Math.Sin(a) sin(a)
Math.Sqrt(a) sqrt(a)
Math.Tan(a) tan(a)
Math.Truncate(a) truncate(a, 0) trunc(a, 0)

12、類型轉換

Convert.ToBoolean(a), bool.Parse(a) a not in ('0','false') a::varchar not in ('0','false','f','no')
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) datetime(a)
Convert.ToDecimal(a), decimal.Parse(a) cast(a as decimal(36,18)) cast(a as decimal(36,19)) a::numeric
Convert.ToDouble(a), double.Parse(a) cast(a as decimal(32,16)) a::float8 cast(a as double)
Convert.ToInt16(a), short.Parse(a) cast(a as signed) cast(a as smallint)
Convert.ToInt32(a), int.Parse(a) cast(a as int) a::int4
Convert.ToInt64(a), long.Parse(a) cast(a as bigint) a::int8
Convert.ToSByte(a), sbyte.Parse(a)
Convert.ToString(a) cast(a as decimal(14,7)) a::float4 cast(a as character)
Convert.ToSingle(a), float.Parse(a) cast(a as char) cast(a as nvarchar) a::varchar
Convert.ToUInt16(a), ushort.Parse(a)
Convert.ToUInt32(a), uint.Parse(a) cast(a as decimal(10,0))
Convert.ToUInt64(a), ulong.Parse(a) 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() random() dbms_random.value

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

工作單元

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、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();
//強制讀【主庫】
           

下面是以前某項目的測試圖檔,以供參考,整個過程無感切換和恢複:

.NETCore 新型 ORM 功能介紹
.NETCore 新型 ORM 功能介紹

分區分表

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

繼續閱讀