.NET ORM 倉儲層必備的功能介紹之 FreeSql Repository 實現篇

来源:https://www.cnblogs.com/FreeSql/archive/2022/05/06/16225822.html
-Advertisement-
Play Games

FreeSql.Repository 除了 CRUD 還有很多實用性功能,不防耐下心花10分鐘看完。支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及還有說不出來的運行平臺,因為代碼綠色無依賴,支持新平臺非常簡單。目前單元測試數量:6336+,N... ...


寫在開頭

2018年11月的某一天,頭腦發熱開啟了 FreeSql 開源項目之旅,時間一晃已經四年多,當初從舒服區走向一個巨大的坑,回頭一看後背一涼。四年時間從無到有,經歷了數不清的日夜奮戰(有人問我花了多長時間投入,答案:全職x2 + 前兩年無休息,以及後面兩年的持續投入)。今天 FreeSql 已經很強大,感謝第一期、第二期、第N期持續提出建議的網友。

FreeSql 現如今已經是一個穩定的版本,主要體現:

  • API 已經確定,不會輕易推翻重作調整,堅持十年不變的原則,讓使用者真真正正的不再關心 ORM 使用問題;
  • 單元測試覆蓋面廣,6336+ 個單元測試,小版本更新升級無須考慮修東牆、補西牆的問題;
  • 經歷四年時間的生產考驗,nuget下載量已超過900K+,平均每日750+;

感嘆:有些人說 .Net 陷入 orm 怪圈,動手的沒幾個,指點江山的一堆,.Net orm 真的如他們所講的簡單嗎?


項目介紹

FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及還有說不出來的運行平臺,因為代碼綠色無依賴,支持新平臺非常簡單。目前單元測試數量:6336+,Nuget下載數量:900K+。QQ群:4336577(已滿)、8578575(線上)、52508226(線上)

溫馨提醒:以下內容無商吹成份,FreeSql 不打誑語

為什麼要重覆造輪子?

FreeSql 主要優勢在於易用性上,基本是開箱即用,在不同資料庫之間切換相容性比較好。作者花了大量的時間精力在這個項目,肯請您花半小時瞭解下項目,謝謝。FreeSql 整體的功能特性如下:

  • 支持 CodeFirst 對比結構變化遷移;
  • 支持 DbFirst 從資料庫導入實體類;
  • 支持 豐富的表達式函數,自定義解析;
  • 支持 批量添加、批量更新、BulkCopy;
  • 支持 導航屬性,貪婪載入、延時載入、級聯保存;
  • 支持 讀寫分離、分表分庫,租戶設計;
  • 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達夢/神通/人大金倉/翰高/MsAccess Ado.net 實現包,以及 Odbc 的專門實現包;

5500+個單元測試作為基調,支持10多數資料庫,我們提供了通用Odbc理論上支持所有資料庫,目前已知有群友使用 FreeSql 操作華為高斯、mycat、tidb 等資料庫。安裝時只需要選擇對應的資料庫實現包:

dotnet add packages FreeSql.Provider.MySql

FreeSql.Repository 是 FreeSql 項目的延申擴展類庫,支持 .NETFramework4.0+、.NETCore2.0+、.NET5+、Xamarin 平臺。

FreeSql.Repository 除了 CRUD 還有很多實用性功能,不防耐下心花10分鐘看完。


01 安裝

環境1:.NET Core 或 .NET 5.0+

dotnet add package FreeSql.Repository

環境2、.NET Framework

Install-Package FreeSql.DbContext
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Sqlite, connectionString)
    .UseAutoSyncStructure(true) //自動遷移實體的結構到資料庫
    .UseNoneCommandParameter(true)
    .UseMonitorCommand(cmd => Console.WriteLine(cmd.CommandText))
    .Build(); //請務必定義成 Singleton 單例模式

02 使用方法

方法1、IFreeSql 的擴展方法;

var curd = fsql.GetRepository<Topic>();

註意:Repository 對象多線程不安全,因此不應在多個線程上同時對其執行工作。

  • fsql.GetRepository 方法返回新倉儲實例
  • 不支持從不同的線程同時使用同一倉儲實例

以下為了方便測試代碼演示,我們都使用方法1,fsql.GetRepository 創建新倉儲實例


方法2、繼承實現;

public class TopicRepository: BaseRepository<Topic, int> {
    public TopicRepository(IFreeSql fsql) : base(fsql, null, null) {}

    //在這裡增加 CURD 以外的方法
}

方法3、依賴註入;

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFreeSql>(Fsql);
    services.AddFreeRepository(null, this.GetType().Assembly);
}

//在控制器使用
public TopicController(IBaseRepository<Topic> repo) {
}

03 添加數據

repo.Insert 插入數據,適配了各資料庫優化執行 ExecuteAffrows/ExecuteIdentity/ExecuteInserted

1、如果表有自增列,插入數據後應該要返回 id。

var repo = fsql.GetRepository<Topic>();
repo.Insert(topic);

內部會將插入後的自增值填充給 topic.Id

2、批量插入

var repo = fsql.GetRepository<Topic>();
var topics = new [] { new Topic { ... }, new Topic { ... } };
repo.Insert(topics);

3、插入資料庫時間

使用 [Column(ServerTime = DateTimeKind.Utc)] 特性,插入數據時,使用適配好的每種資料庫內容,如 getutcdate()

4、插入特殊類型

使用 [Column(RereadSql = "{0}.STAsText()", RewriteSql = "geography::STGeomFromText({0},4236)")] 特性,插入和讀取時特別處理

5、插入時忽略

使用 [Column(CanInsert = false)] 特性


04 更新數據

1、只更新變化的屬性

var repo = fsql.GetRepository<Topic>();
var item = repo.Where(a => a.Id == 1).First();  //此時快照 item

item.Name = "newtitle";
repo.Update(item); //對比快照時的變化
//UPDATE `Topic` SET `Title` = 'newtitle'
//WHERE (`Id` = 1)

2、手工管理狀態

var repo = fsql.GetRepository<Topic>();
var item = new Topic { Id = 1 };
repo.Attach(item); //此時快照 item

item.Title = "newtitle";
repo.Update(item); //對比快照時的變化
//UPDATE `Topic` SET `Title` = 'newtitle'
//WHERE (`Id` = 1)

3、直接使用 repo.UpdateDiy,它是 IFreeSql 提供的原生 IUpdate 對象,功能更豐富


05 級聯保存數據

實踐發現,N對1 不適合做級聯保存。保存 Topic 的時候把 Type 信息也保存?我個人認為自下向上保存的功能太不可控了,FreeSql 目前不支持自下向上保存。因此下麵我們只講 OneToOne/OneToMany/ManyToMany 級聯保存。至於 ManyToOne 級聯保存使用手工處理,更加安全可控。

功能1:SaveMany 手工保存

完整保存,對比表已存在的數據,計算出添加、修改、刪除執行。

遞歸保存導航屬性不安全,不可控,並非技術問題,而是出於安全考慮,提供了手工完整保存的方式。

var repo = fsql.GetRepository<Type>();
var type = new Type
{
    name = "c#",
    Topics = new List<Topic>(new[]
    {
        new Topic { ... }
    })
};
repo.Insert(type);
repo.SaveMany(type, "Topics"); //手工完整保存 Topics
  • SaveMany 僅支持 OneToMany、ManyToMany 導航屬性
  • 只保存 Topics,不向下遞歸追朔
  • 當 Topics 為 Empty 時,刪除 type 存在的 Topics 所有表數據,確認?
  • ManyToMany 機製為,完整對比保存中間表,外部表只追加不更新

如:

  • 本表 Topic
  • 外部表 Tag
  • 中間表 TopicTag

功能2:EnableCascadeSave 倉儲級聯保存

DbContext/Repository EnableCascadeSave 可實現保存對象的時候,遞歸追朔其 OneToOne/OneToMany/ManyToMany 導航屬性也一併保存,本文檔說明機制防止誤用。

1、OneToOne 級聯保存

v3.2.606+ 支持,並且支持級聯刪除功能(文檔請向下瀏覽)

2、OneToMany 追加或更新子表,不刪除子表已存在的數據

var repo = fsql.GetRepository<Type>();
repo.DbContextOptions.EnableCascadeSave = true; //需要手工開啟
repo.Insert(type);
  • 不刪除 Topics 子表已存在的數據,確認?
  • 當 Topics 屬性為 Empty 時,不做任何操作,確認?
  • 保存 Topics 的時候,還會保存 Topics[0-..] 的下級集合屬性,向下18層,確認?

向下18層的意思,比如【類型】表,下麵有集合屬性【文章】,【文章】下麵有集合屬性【評論】。

保存【類型】表對象的時候,他會向下檢索出集合屬性【文章】,然後如果【文章】被保存的時候,再繼續向下檢索出集合屬性【評論】。一起做 InsertOrUpdate 操作。

3、ManyToMany 完整對比保存中間表,追加外部表

完整對比保存中間表,對比【多對多】中間表已存在的數據,計算出添加、修改、刪除執行。

追加外部表,只追加不更新。

  • 本表 Topic
  • 外部表 Tag
  • 中間表 TopicTag

06 刪除數據

var repo = fsql.GetRepository<Topic>();
repo.Delete(new Topic { Id = 1 }); //有重載方法 repo.Delete(Topic[])

var repo2 = fsql.GetRepository<Topic, int>(); //int 是主鍵類型,相比 repo 對象多了 Delete(int) 方法
repo2.Delete(1);

07 級聯刪除數據

第一種:基於【對象】級聯刪除

比如使用過 Include/IncludeMany 查詢的對象,可以使用此方法級聯刪除它們。

var repo = fsql.GetRepository<Group>();
repo.DbContextOptions.EnableCascadeSave = true; //關鍵設置
repo.Insert(new UserGroup
{
    GroupName = "group01",
    Users = new List<User>
    {
        new User { Username = "admin01", Password = "pwd01", UserExt = new UserExt { Remark = "用戶備註01" } },
        new User { Username = "admin02", Password = "pwd02", UserExt = new UserExt { Remark = "用戶備註02" } },
        new User { Username = "admin03", Password = "pwd03", UserExt = new UserExt { Remark = "用戶備註03" } },
    }
}); //級聯添加測試數據
//INSERT INTO "usergroup"("groupname") VALUES('group01') RETURNING "id"
//INSERT INTO "user"("username", "password", "groupid") VALUES('admin01', 'pwd01', 1), ('admin02', 'pwd02', 1), ('admin03', 'pwd03', 1) RETURNING "id" as "Id", "username" as "Username", "password" as "Password", "groupid" as "GroupId"
//INSERT INTO "userext"("userid", "remark") VALUES(3, '用戶備註01'), (4, '用戶備註02'), (5, '用戶備註03')

var groups = repo.Select
    .IncludeMany(a => a.Users, 
        then => then.Include(b => b.UserExt))
    .ToList();
repo.Delete(groups); //級聯刪除,遞歸向下遍歷 group OneToOne/OneToMany/ManyToMany 導航屬性
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1)

第二種:基於【資料庫】級聯刪除,不依賴資料庫外鍵

根據設置的導航屬性,遞歸刪除 OneToOne/OneToMany/ManyToMany 對應數據,並返回已刪除的數據。此功能不依賴資料庫外鍵

var repo = fsql.GetRepository<Group>();
var ret = repo.DeleteCascadeByDatabase(a => a.Id == 1);
//SELECT a."id", a."username", a."password", a."groupid" FROM "user" a WHERE (a."groupid" = 1)
//SELECT a."userid", a."remark" FROM "userext" a WHERE (a."userid" IN (3,4,5))
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1)

//ret   Count = 7	System.Collections.Generic.List<object>
//  [0]	{UserExt}	object {UserExt}
//  [1]	{UserExt}	object {UserExt}
//  [2]	{UserExt}	object {UserExt}
//  [3]	{User}	    object {User}
//  [4]	{User}	    object {User}
//  [5]	{User}  	object {User}
//  [6]	{UserGroup}	object {UserGroup}

public class Group
{
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string GroupName { get; set; }

    [Navigate(nameof(User.GroupId))]
    public List<User> Users { get; set; }
}
public class User
{
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public int GroupId { get; set; }

    [Navigate(nameof(Id))]
    public UserExt UserExt { get; set; }
}
public class UserExt
{
    [Column(IsPrimary = true)]
    public int UserId { get; set; }
    public string Remark { get; set; }

    [Navigate(nameof(UserId))]
    public User User { get; set; }
}

08 添加或修改數據

var repo = fsql.GetRepository<Topic>();
repo.InsertOrUpdate(item);

如果內部的狀態管理存在數據,則更新。

如果內部的狀態管理不存在數據,則查詢資料庫,判斷是否存在。

存在則更新,不存在則插入

缺點:不支持批量操作

提醒:IFreeSql 也定義了 InsertOrUpdate 方法,兩者實現機制不同,它利用了資料庫特性:

Database Features Database Features
MySql on duplicate key update 達夢 merge into
PostgreSQL on conflict do update 人大金倉 on conflict do update
SqlServer merge into 神通 merge into
Oracle merge into 南大通用 merge into
Sqlite replace into MsAccess 不支持
Firebird merge into
fsql.InsertOrUpdate<T>()
  .SetSource(items) //需要操作的數據
  //.IfExistsDoNothing() //如果數據存在,啥事也不幹(相當於只有不存在數據時才插入)
  .ExecuteAffrows();

09 批量編輯數據

var repo = fsql.GetRepository<BeginEdit01>();
var cts = new[] {
    new BeginEdit01 { Name = "分類1" },
    new BeginEdit01 { Name = "分類1_1" },
    new BeginEdit01 { Name = "分類1_2" },
    new BeginEdit01 { Name = "分類1_3" },
    new BeginEdit01 { Name = "分類2" },
    new BeginEdit01 { Name = "分類2_1" },
    new BeginEdit01 { Name = "分類2_2" }
}.ToList();
repo.Insert(cts);

repo.BeginEdit(cts); //開始對 cts 進行編輯

cts.Add(new BeginEdit01 { Name = "分類2_3" });
cts[0].Name = "123123";
cts.RemoveAt(1);

var affrows = repo.EndEdit(); //完成編輯
Assert.Equal(3, affrows);
class BeginEdit01
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

上面的代碼 EndEdit 方法執行的時候產生 3 條 SQL 如下:

INSERT INTO "BeginEdit01"("Id", "Name") VALUES('5f26bf07-6ac3-cbe8-00da-7dd74818c3a6', '分類2_3')

UPDATE "BeginEdit01" SET "Name" = '123123' 
WHERE ("Id" = '5f26bf00-6ac3-cbe8-00da-7dd01be76e26')

DELETE FROM "BeginEdit01" WHERE ("Id" = '5f26bf00-6ac3-cbe8-00da-7dd11bcf54dc')

場景:winform 載入表數據後,一頓添加、修改、刪除操作之後,點擊【保存】

提醒:該操作只對變數 cts 有效,不是針對全表對比更新。


10 弱類型 CRUD

var repo = fsql.GetRepository<object>();
repo.AsType(typeof(Topic));

var item = (object)new Topic { Title = "object title" };
repo.Insert(item);

11 無參數化命令

支持參數化、無參數化命令執行,有一些特定的資料庫,使用無參數化命令執行效率更高哦,並且調試起來更直觀。

var repo = fsql.GetRepository<object>();
repo.DbContextOptions.NoneParameter = true;

12 工作單元(事務)

UnitOfWork 可將多個倉儲放在一個單元管理執行,最終通用 Commit 執行所有操作,內部採用了資料庫事務。

方法1:隨時創建使用

using (var uow = fsql.CreateUnitOfWork())
{
  var typeRepo = fsql.GetRepository<Type>();
  var topicRepo = fsql.GetRepository<Topic>();
  typeRepo.UnitOfWork = uow;
  topicRepo.UnitOfWork = uow;

  typeRepo.Insert(new Type());
  topicRepo.Insert(new Topic());

  uow.Orm.Insert(new Topic()).ExecuteAffrows();
  //uow.Orm 和 fsql 都是 IFreeSql
  //uow.Orm CRUD 與 uow 是一個事務
  //fsql CRUD 與 uow 不在一個事務

  uow.Commit();
}

方法2:使用 AOP + UnitOfWorkManager 實現多種事務傳播

本段內容引導,如何在 asp.net core 項目中使用特性(註解) 的方式管理事務。

UnitOfWorkManager 支持六種傳播方式(propagation),意味著跨方法的事務非常方便,並且支持同步非同步:

  • Requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,預設的選擇。
  • Supports:支持當前事務,如果沒有當前事務,就以非事務方法執行。
  • Mandatory:使用當前事務,如果沒有當前事務,就拋出異常。
  • NotSupported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • Never:以非事務方式執行操作,如果當前事務存在則拋出異常。
  • Nested:以嵌套事務方式執行。

第一步:配置 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 = Begin(attr.Propagation, attr.IsolationLevel) 方法

在 After 調用 Before 中的 uow.Commit 或者 Rollback 方法,最後調用 uow.Dispose

提醒:動態代理,一定註意處理好非同步 await,否則會出現事務異常的問題

第四步:在 Controller 或者 Service 中使用事務特性

public class TopicService
{
    IBaseRepository<Topic> _repoTopic;
    IBaseRepository<Detail> _repoDetail;

    public TopicService(IBaseRepository<Topic> repoTopic, IBaseRepository<Detail> repoDetail)
    {
        _repoTopic = repoTopic;
        _repoDetail = repoDetail;
    }

    [Transactional]
    public virtual void Test1()
    {
        //這裡 _repoTopic、_repoDetail 所有 CRUD 操作都是一個工作單元
        this.Test2();
    }

    [Transactional(Propagation = Propagation.Nested)]
    public virtual void Test2() //嵌套事務,新的(不使用 Test1 的事務)
    {
        //這裡 _repoTopic、_repoDetail 所有 CRUD 操作都是一個工作單元
    }
}

是不是進方法就開事務呢?

不一定是真實事務,有可能是虛的,就是一個假的 unitofwork(不帶事務)

也有可能是延用上一次的事務

也有可能是新開事務,具體要看傳播模式

示例項目:https://github.com/dotnetcore/FreeSql/tree/master/Examples/aspnetcore_transaction

Autofac 動態代理參考項目:


13 手工分表

FreeSql 原生用法、FreeSql.Repository 倉儲用法 都提供了 AsTable 方法對分表進行 CRUD 操作,例如:

var repo = fsql.GetRepository<Log>();
repo.AsTable(oldname => $"{oldname}_201903"); //對 Log_201903 表 CRUD

repo.Insert(new Log { ... });

跨庫,但是在同一個資料庫伺服器下,也可以使用 AsTable(oldname => $"db2.dbo.{oldname}")

//跨表查詢
var sql = fsql.Select<User>()
    .AsTable((type, oldname) => "table_1")
    .AsTable((type, oldname) => "table_2")
    .ToSql(a => a.Id);

//select * from (SELECT a."Id" as1 FROM "table_1" a) ftb 
//UNION ALL
//select * from (SELECT a."Id" as1 FROM "table_2" a) ftb 

分表總結:

  • 分表、相同伺服器跨庫 可以使用 AsTable 進行 CRUD;
  • AsTable CodeFirst 會自動創建不存在的分表;
  • 不可在分表分庫的實體類型中使用《延時載入》;

v3.2.500 按時間自動分表方案:https://github.com/dotnetcore/FreeSql/discussions/1066


寫在最後

FreeSql 他是免費自由的 ORM,也可以說是寶藏 ORM。更多文檔請前往 github wiki 查看。

FreeSql 已經步入第四個年頭,期待少年的你十年後還能歸來在此貼回覆,兌現當初吹過十年不變的承諾。

多的不說了,希望民間的開源力量越來越強大。

希望作者的努力能打動到你,請求正在使用的、善良的您能動一動小手指,把文章轉發一下,讓更多人知道 .NET 有這樣一個好用的 ORM 存在。謝謝!!

FreeSql 使用最寬鬆的開源協議 MIT https://github.com/dotnetcore/FreeSql ,完全可以商用,文檔齊全,甚至拿去賣錢也可以。QQ群:4336577(已滿)、8578575(線上)、52508226(線上)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 昨天在跟小伙伴聊天,當他談起自己正在做的項目時,一臉愁容。 他吐槽道:“該項目的 Python 代碼庫由多個人共同維護。由於每個人使用的編輯器不同,每個人的編碼風格也不同,最終導致了 代碼的縮進千奇百怪:有縮進 2 個空格的,有縮進 4 個空格的,有縮進 8 個空格,有縮進一個 Tab 的,更 ...
  • 來源:cnblogs.com/juncaoit/p/12422752.html 一直以為這個方法是java8的,今天才知道是是1.7的時候,然後翻了一下源碼。 這篇文章中會總結一下與a.equals(b)的區別,然後對源碼做一個小分析。 值是null的情況 1、a.equals(b), a 是nul ...
  • Java 17推出的新特性Sealed Classes經歷了2個Preview版本(JDK 15中的JEP 360、JDK 16中的JEP 397),最終定稿於JDK 17中的JEP 409。Sealed Classes有兩種主流翻譯:密封類、封閉類。個人喜歡前者多一些,所以在本文中都稱為密封類。其 ...
  • 排序 比較 分類 比較排序的時間複雜度的下界O(nlogn) 對於n個待排序元素,在未比較時,可能的正確結果有n!種。在經過一次比較後,其中兩個元素的順序被確定,所以可能的正確結果剩餘n!/2種(確定之前兩個元素的前後位置的情況是相同,確定之後相當於少了一半的可能性)。依次類推,直到經過m次比較,剩 ...
  • 字元串 String s1 = "aaa"; String s2 = "aa" + new String("a"); String s3 = new String("aaa"); System.out.println(s1.intern().equals(s1)); //true System.ou ...
  • 位運算 >>> 無符號右移,第一位符號位不變,其他位用0補齊 >> 右移,整體右移,左邊的用0補齊 << 左移,整體左移,右邊的用0補齊 | 或:有1則1 & 與:有0則0 ^ 異或:相反為1,否則為0 ~ 取反: 寫個測試 1.5以後的jdk中,Integer等數字類型內部維護了一個成員變數叫 S ...
  • 前言 實際項目中總能遇到一個"組件"不是基礎組件但是又會頻繁複用的情況,在開發MASA Auth時也封裝了幾個組件。既有簡單定義CSS樣式和界面封裝的組件(GroupBox),也有帶一定組件內部邏輯的組件(ColorGroup)。 本文將一步步演示如何封裝出一個如下圖所示的ColorGroup組件, ...
  • C#Delegate和Control中Invoke和BeginInvoke區別 最近總是遇到Control的Invoke和BeginInvoke問題,故作此總結。 **1、**Control的Invoke和BeginInvoke的委托方法是在主線程,即UI線程上執行的。也就是說如果你的委托方法比較耗 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...