.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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...