【摘錄】使用實體框架、Dapper和Chain的倉儲模式實現策略

来源:http://www.cnblogs.com/rjf1979/archive/2017/01/11/6272812.html
-Advertisement-
Play Games

以下文章摘錄來自InfoQ,是一篇不錯的軟問,大家細細的品味 關鍵要點: Dapper這類微ORM(Micro-ORM)雖然提供了最好的性能,但也需要去做最多的工作。 在無需複雜對象圖時,Chain這類Fluent ORM更易於使用。 對實體框架(Entity Framework)做大量的工作後,其 ...


以下文章摘錄來自InfoQ,是一篇不錯的軟問,大家細細的品味

關鍵要點:

  • Dapper這類微ORM(Micro-ORM)雖然提供了最好的性能,但也需要去做最多的工作。
  • 在無需複雜對象圖時,Chain這類Fluent ORM更易於使用。
  • 對實體框架(Entity Framework)做大量的工作後,其性能可顯著提高。
  • 為獲得資料庫的最大性能,需要採用可能會有些繁瑣的投影(Projection)操作。
  • ORM整體上的局部更新可能會存在問題。

在現代企業開發中,可採用多種方法構建數據存取層(data access layer ,DAL)。使用C#做開發時,DAL的最底層幾乎總是使用ADO.NET。但這時常會形成一個笨重的庫,所以通常會在DAL的底層之上再部署一個ORM層。為允許模擬和隱藏ORM的細節,整個DAL包裝在存儲內。

在這一系列的文章中,我們將審視三種使用不同類型ORM構建倉儲模式的方法,分別是:

  • 實體框架:一種傳統的“全特性”或“OOP”類型的ORM。
  • Dapper:一種主要專註結果集映射的輕量級微ORM。
  • Tortuga Chain:一種基於函數式編程理念的Fluent ORM。
 

本文將側重於開發人員可在典型倉儲中用到的那些基本功能。在本系列文章的第二部分,我們將著眼於那些開發人員基於實際情況而實現的高級技術。

插入(Insert)操作

對於任何CRUD操作集,通常會首先實現基本的插入操作,進而可用插入操作對其它的操作進行測試。

Chain

Chain使用列名和屬性名間的運行時匹配。對於在資料庫中並不存在的對象,除非啟用了嚴格模式(strict model),否則將忽略該對象上的屬性。類似地,沒有匹配屬性的列不能成為生成SQL的組成部分。

相關廠商內容

     
public int Insert(Employee employee)
        {
            return m_DataSource.Insert("HR.Employee", employee).ToInt32().Execute();
        }

Dapper

沒有第三方擴展時,Dapper需要編程人員手工指定所需的SQL,其中包括了特定於資料庫的邏輯,用於返回新創建的主鍵。

 public int Insert(Employee employee)
        {
            const string sql = @"INSERT INTO HR.Employee
        (FirstName,
         MiddleName,
         LastName,
         Title,
         ManagerKey,
         OfficePhone,
         CellPhone
        )
VALUES  (@FirstName,
         @MiddleName,
         @LastName,
         @Title,
         @ManagerKey,
         @OfficePhone,
         @CellPhone
        );

SELECT SCOPE_IDENTITY()
";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.ExecuteScalar<int>(sql, employee);
            }
        }

實體框架

實體框架使用編譯階段映射在運行時生成SQL。需將任何沒有匹配列的屬性標記為NotMapped,否則將會產生錯誤。

public int Insert(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Employees.Add(employee);
                context.SaveChanges();
                return employee.EmployeeKey;
            }
        }

更新(Update)操作

Chain

Chain預設使用資料庫中所定義的主鍵。但是在設置了適當的插入選項後,它將在模型中使用Key屬性。

public void Update(Employee employee)
        {
            m_DataSource.Update("HR.Employee", employee).Execute();
        }

Dapper

與插入操作一樣,純Dapper需用戶手工編寫必要的SQL語句。

public void Update(Employee employee)
    {
            const string sql = @"UPDATE HR.Employee
    SET     FirstName = @FirstName,
            MiddleName = @MiddleName,
            LastName = @LastName,
            Title = @Title,
            ManagerKey = @ManagerKey,
            OfficePhone = @OfficePhone,
            CellPhone = @CellPhone
    WHERE   EmployeeKey = @EmployeeKey
    ";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute(sql, employee);
            }
     }

實體框架(初學者)

實體框架為UPDATE語句查找Key屬性,以生成WHERE語句。

public void Update(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                var entity = context.Employees.Where(e => e.EmployeeKey == employee.EmployeeKey).First();
                entity.CellPhone = employee.CellPhone;
                entity.FirstName = employee.FirstName;
                entity.LastName = employee.LastName;
                entity.ManagerKey = employee.ManagerKey;
                entity.MiddleName = employee.MiddleName;
                entity.OfficePhone = employee.OfficePhone;
                entity.Title = employee.Title;
                context.SaveChanges();
            }
        }

實體框架(中級用戶)

使用實體框架時,初學者常會在執行更新操作上犯錯誤。將實體添加到上下文中很容易就能實現它,而這種模式應成為中級使用者的常識。這裡給出使用實體狀態“Modified”修正後的例子。

public void Update(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Entry(employee).State = EntityState.Modified;
                context.SaveChanges();
            }
        }

讀取全部(Read All)操作

讀取全部操作在實體框架和Chain中是十分相似的,不同之處在於在實體框架中實現需要編寫更多行的代碼,而在Chain中實現需要編寫更長的代碼行。

Dapper當然是最為繁瑣的,因為它需要未經加工的SQL語句。即使如此,仍可以通過使用SELECT *語句替代手工地指定列名而在一定程度上降低Dapper的開銷。這在存在返回額外數據的風險的情況下,降低了出現類與SQL語句不匹配的可能性。

Chain

在Chain中,ToObject連接生成一系列所需的列。通過匹配所需列表與可用列的列表,From連接生成SQL語句。

public IList<Employee> GetAll()
        {
            return m_DataSource.From("HR.Employee").ToCollection<Employee>().Execute();
        }

Dapper

Dapper是最為繁瑣的,因為它需要原始未經加工的SQL語句。雖然這令人皺眉頭,但仍可以通過使用SELECT *語句替代手工地指定列名而在一定程度上降低Dapper的開銷,這樣是不太可能漏掉列的,雖然存在返回額外數據的風險。

 public IList<Employee> GetAll()
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>("SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e").AsList();
            }
        }

實體框架

像以前一樣,實體框架使用編譯期信息確定如何生成SQL語句。

public IList<Employee> GetAll()
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.ToList();
            }
        }

按標識符獲取(Get by Id)操作

需要註意的是,隨每個例子的語法稍作修改就可表明只返回一個對象。同樣的基本過濾技術可用於返回多個對象。

Chain

Chain嚴重依賴於“過濾對象”。這些對象直接被轉義成參數化的WHERE語句,語句中的每個屬性間具有“AND”操作符。

public Employee Get(int employeeKey)
        {
            return m_DataSource.From("HR.Employee", new { @EmployeeKey = employeeKey }).ToObject<Employee>().Execute();
        } 

Chain也允許用參數化的字元串表示WHERE語句,雖然這個功能很少被用到。

如果主鍵是標量,即主鍵中只有一列,那麼可使用簡化的語法。

public Employee Get(int employeeKey)
        {
            return m_DataSource.GetByKey("HR.Employee", employeeKey).ToObject<Employee>().Execute();
        }

Dapper

下例中,可以看到Dapper手工指定了SQL語句。該語句與Chain和實體框架所生成的SQL語句在本質上是一致的。

using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>("SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e WHERE e.EmployeeKey = @EmployeeKey", new { @EmployeeKey = employeeKey }).First();
            }

實體框架

實體框架將表名和首個ToList或First操作間的所有內容看作為一個表達式樹。在運行時評估該樹以生成SQL語句。

public Employee Get(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.Where(e => e.EmployeeKey == employeeKey).First();
            }
        }

刪除(Delete)操作

Chain

Chain期待包括主鍵的參數對象。而參數對象中的其它特性將被忽略(該語法不支持批量刪除)。

public void Delete(int employeeKey)
        {
            m_DataSource.Delete("HR.Employee", new { @EmployeeKey = employeeKey }).Execute();
        }

如果有標量主鍵,可使用簡化的語法。

 public void Delete(int employeeKey)
        {
            m_DataSource.DeleteByKey("HR.Employee", employeeKey).Execute();
        }

Dapper

public void Delete(int employeeKey)
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute("DELETE FROM HR.Employee WHERE EmployeeKey = @EmployeeKey", new { @EmployeeKey = employeeKey });
            }
        }

實體框架(初學者)

初學者一般會取回一個記錄然後迅速刪除,丟棄所有返回的信息。

public void Delete(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                var employee = context.Employees.Where(e => e.EmployeeKey == employeeKey).First();
                context.Employees.Remove(employee);
                context.SaveChanges();
            }
        }

實體框架(中級用戶)

可使用內嵌SQL避免資料庫的往返交互操作。

public void Delete(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                context.Database.ExecuteSqlCommand("DELETE FROM HR.Employee WHERE EmployeeKey = @p0", employeeKey);
            }
        }

投影(Projection)操作

投影是中間層開發中的一個重要部分。在取回了比實際所需更多的數據時,資料庫常會完全失去使用覆蓋索引或索引的能力,這將導致嚴重的性能影響。

Chain

同上,Chain將僅選取指定對象類型所需的所有列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            return m_DataSource.From("HR.Employee").ToCollection<EmployeeOfficePhone>().Execute();
        }

Dapper

鑒於Dapper是顯式的,所以是由開發人員確保只選取必需的列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<EmployeeOfficePhone>("SELECT e.EmployeeKey, e.FirstName, e.LastName, e.OfficePhone FROM HR.Employee e").AsList();
            }
        }

實體框架

實體框架需要額外的操作步驟,這些步驟常因為有些繁瑣而被忽視。

通過在調用ToList前就包括了額外的選擇語句,實體架構可生成正確的SQL語句,並避免從資料庫返回過多的信息。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.Select(e => new EmployeeOfficePhone()
                {
                    EmployeeKey = e.EmployeeKey,
                    FirstName = e.FirstName,
                    LastName = e.LastName,
                    OfficePhone = e.OfficePhone
                }).ToList();
            }
        }

使用投影做更新操作

固然,在存在投影對象時直接從投影對象更新資料庫是一種好的方法。該方法在Chain和Dapper的基本模式中是天然存在的。而在實體框架中,則必須要在手工拷貝屬性和編寫Dapper風格的內嵌SQL這兩種方法間做出選擇。

Chain

註意,任何未在投影類上具有匹配屬性的列將不受到影響。

public void Update(EmployeeOfficePhone employee)
        {
            return m_DataSource.Update("HR.Employee", employee).Execute();
        }

Dapper

public void Update(EmployeeOfficePhone employee)
        {
            const string sql = @"UPDATE HR.Employee
SET     FirstName = @FirstName,
        LastName = @LastName,
        OfficePhone = @OfficePhone
WHERE   EmployeeKey = @EmployeeKey
";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute(sql, employee);
            }
        }

實體框架

public void Update(EmployeeOfficePhone employee)
        {
            using (var context = new CodeFirstModels())
            {
                var entity = context.Employees.Where(e => e.EmployeeKey == employee.EmployeeKey).First();
                entity.FirstName = employee.FirstName;
                entity.LastName = employee.LastName;
                entity.OfficePhone = employee.OfficePhone;
                context.SaveChanges();
            }
        }

反射插入(Reflexive Insert)

現在我們來看一些更有意思的用例。反射插入意味著返回被插入的對象。做反射插入通常是為了獲得預設的和計算的域。

模型

註意,實體框架和Chain需要對屬性進行註釋,這樣庫才會知道該域將由資料庫予以設置。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)] //Needed by EF
        [IgnoreOnInsert, IgnoreOnUpdate] //Needed by Chain
        public DateTime? CreatedDate { get; set; }

Chain

Chain允許將ToObject附加到任何插入或更新操作上。

public Employee InsertAndReturn(Employee employee)
        {
            return m_DataSource.Insert("HR.Employee", employee).ToObject<Employee>().Execute();
        }

Dapper

使用Dapper的反射插入,可以使用特定於資料庫的功能實現,例如OUTPUT語句。

public Employee InsertAndReturn(Employee employee)
        {
            const string sql = @"INSERT INTO HR.Employee
        (FirstName,
         MiddleName,
         LastName,
         Title,
         ManagerKey,
         OfficePhone,
         CellPhone
        )
    OUTPUT 
        Inserted.EmployeeKey,
        Inserted.FirstName,
        Inserted.MiddleName,
        Inserted.LastName,
        Inserted.Title,
        Inserted.ManagerKey,
        Inserted.OfficePhone,
        Inserted.CellPhone,
        Inserted.CreatedDate
VALUES  (@FirstName,
         @MiddleName,
         @LastName,
         @Title,
         @ManagerKey,
         @OfficePhone,
         @CellPhone
        );";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>(sql, employee).First();
            }
        }

如果一併考慮初學者級別模式,更典型的做法是僅在Get方法之後調用Insert方法。

public Employee InsertAndReturn_Novice(Employee employee)
        {
            return Get(Insert(employee));
        }

實體框架

使用前面提及的DatabaseGenerated屬性,你可以插入一個新的實體並讀回它的計算的和/或預設的列。

public Employee InsertAndReturn(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Employees.Add(employee);
                context.SaveChanges();
                return employee;
            }
        }

受限更新/局部更新

有時應用並沒有打算對每個列做更新,尤其是當模型是直接源自於UI並可能混合了可更新域和不可更新域時。

Chain

在Chain中,使用IgnoreOnInsert和IgnoreOnUpdate屬性去限制插入和更新操作。為允許用資料庫作為預設取值,典型的做法是將這兩個屬性都置於CreatedDate類型的列中。為避免更新操作過程中的意外改變,通常將IgnoreOnUpdate屬性置於CreatedBy之類的列上。

Dapper

就顯式編寫的插入和更新語句而言,Dapper最具靈活性。

實體框架

除了計算列(列值為表達式),實體框架並未給出一種簡單的方法可聲明某一列不參與插入或刪除操作,但可使用更新操作的“讀-拷貝-寫”(read-copy-write)模式模擬該行為。

更新或插入(Upsert)操作

經常需要作為一個單一操作完成記錄的插入或者更新,尤其是在使用自然主鍵(natural key)時。

Chain

在Chain中,Upsert操作的實現使用了與插入和刪除相同的設計。所生成的SQL隨資料庫引擎不同而各異(例如:SQL Server使用了MERGE,SQLit使用了一系列語句)。

public int Upsert(Employee employee)
        {
            return m_DataSource.Upsert("HR.Employee", employee).ToInt32().Execute();
        }

Dapper

在Dapper中,Upsert操作的實現需要多輪的來回交互,或是需要比較複雜的特定於資料庫的SQL語句。本文對此不作闡述。

實體框架

在實體框架中,這(過程?函數?都可以用“這”指代)僅作為被改進的更新操作的一個變體。

public int Upsert(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                if(employee.EmployeeKey == 0)
                    context.Entry(employee).State = EntityState.Added;
                else
                    context.Entry(employee).State = EntityState.Modified;
                context.SaveChanges();
                return employee.EmployeeKey;
            }
        }

性能

雖然本文所採用的主要基準測試是代碼量和易用性,但是對實際性能的考慮也是非常有用的。

所有的性能基準測試中都包括了預熱過程,其後是對主迴圈做1000次迭代操作。每次測試中都使用了同樣的模型,模型使用實體框架的代碼優先(Code First)技術從資料庫代碼生成器產生。所有迭代都相當於共計13個基本CRUD操作,其中包括創建、讀取、更新和刪除操作。

我要澄清的是,這裡所做的僅是一些粗略的測試,使用了任何人在剛開始接觸這些庫時通常就會看到的代碼類型。當然一些高級技術可以改進每個測試的性能,有時甚至是極大地改進。

BenchmarkDotNet計時

  • Chain:平均3.4160毫秒,標準偏差為0.2764毫秒;
  • 未使用經編譯的物化器(Compiled Materializers)的Chain:平均3.0955毫秒,標準偏差0.1391毫秒;
  • Dapper:平均2.7250毫秒,標準偏差0.1840毫秒;
  • 實體框架(初學者):平均13.1078毫秒,標準偏差0.4649毫秒;
  • 實體框架(中級用戶):平均10.11498毫秒,標準偏差0.1952毫秒;
  • 實體框架(未使用AsNoTracking的中級用戶):平均9.7290毫秒,標準偏差0.3281毫秒。

結論

雖然可使用任何ORM框架去實現基本的倉儲模式,但是各種實現的性能和所需的代碼量具有顯著的差異。選取實現方式時需要對這些因素進行平衡,此外還需考慮資料庫可移植性、跨平臺支持和開發人員經驗等。 

在該系列文章的第二部分,我們將著眼於那些不僅將倉儲模式作為瘦抽象層的高級用例。

你可以在GitHub上獲取本文的代碼。

關於作者

Jonathan Allen的首份工作是在上世紀九十年代末做診所的MIS項目,Allen將項目逐步由Access和Excel升級到企業級的解決方法。在從事為財政部門編寫自動交易系統代碼的工作五年之後,他成為項目顧問,參與了包括機器人倉庫UI、癌症研究軟體中間層、主要房地產保險企業的大數據需求等在內的各種行業項目。在閑暇時間,他喜歡研究源於16世紀的武術,併為其撰寫文章。

 

查看英文原文:Implementation Strategies for the Repository Pattern with Entity Framework, Dapper, and Chain

 


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

-Advertisement-
Play Games
更多相關文章
  • 使用橋接的鏈接方式,centos6.4配置靜態ip,能ping通網關,但ping 外網時出現 "network is unreachable" 如:ping www.baidu.com。 原來是路由配置問題。嘗試直接修改系統文件/etc/sysconfig/network-scripts/route ...
  • 安裝 squid yum install squid -y 備份squid.conf cp squid.conf squid.conf-list vi squid.conf 輸入: acl denyWeb dstdomain "/etc/squid/denyWeb.list" http_access ...
  • 伺服器添加3塊磁碟的體繫結構 [root@oldboylinux test]# free -m total used free shared buffers cached Mem: 992 133 859 0 24 37 -/+ buffers/cache: 70 921 Swap: 511 0 5... ...
  • 前面的話   在網上找了一些關於命令提示符CMD的資料,但是很多資料都是把所有的功能羅列出來,大部分都不會用到。所以,自己把常用的CMD命令總結如下,方便查閱   操作類 列出所有支持的指令及說明 ,如 說明cd命令的詳細用法 清屏 退出當前程式 使用ctrl+c快捷鍵 ...
  • Linux主要分為兩大系發行版,分別是RedHat和Debian,lamp環境的安裝和配置也會有所不同,所以分別以CentOS 7.1和Ubuntu 14.04做為主機(L) Linux下安裝軟體,最常見有源碼安裝方式、RPM/deb安裝方式、yum/apt get安裝方式等,在這裡使用yum/ap ...
  • 0、系統環境 0.1 此處選擇用戶較多的ubuntu作為入門系統,選擇U盤裝,在ubuntu官網https://www.ubuntu.com/download,選擇適合自己的ubuntu版本下載系統鏡像文件。 0.2 選擇容量大於4G的U盤 用Universal UBS installer 製作啟動 ...
  • 安裝並配置MySQL 5.6 從CentOS從7.x開始預設使用MariaDB。MariaDB完全相容MySQL,包括API和命令行。但是很多時候我們還是會想要安裝MySQL,所以不能直接通過yum命令安裝。 下載源安裝文件 本地安裝rpm包(配置MySQL安裝源) 查看所有MySQL安裝源(預設M ...
  • 一些常用的SQL語句大全參考:http://www.cnblogs.com/acpe/p/4970765.html 這篇博文整理的比較全,我摘抄一些基本常用的。 創建資料庫 CREATE DATABASE database-name 刪除資料庫 drop database dbname 創建新表 c ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...