C# ORM學習筆記:使用特性+反射實現簡單ORM

来源:https://www.cnblogs.com/atomy/archive/2020/04/25/12764967.html
-Advertisement-
Play Games

一、原理與環境 在生成數據表的實體類時,利用自定義特性,給它打上表及欄位的特性,然後使用反射原理,將自定義特性拼接成增、刪、改、查對應的SQL,即可完成一個簡單的ORM。 本示例的執行環境: 1)資料庫:SQL Server。(可根據自己的需要,建立不同的資料庫工廠。) 2)數據表:需使用自增類型( ...


    一、原理與環境

    在生成數據表的實體類時,利用自定義特性,給它打上表及欄位的特性,然後使用反射原理,將自定義特性拼接成增、刪、改、查對應的SQL,即可完成一個簡單的ORM。

    本示例的執行環境:

    1)資料庫:SQL Server。(可根據自己的需要,建立不同的資料庫工廠。)

    2)數據表:需使用自增類型(identity)作為數據表的主鍵。主鍵名字可以隨便起,如ID。

    3)實體類:實體類需提供無參構造函數。

    二、演示數據表

    Person表,包含主鍵(ID)、姓名(Name)、年齡(Age)、性別(Gender)。

CREATE TABLE [dbo].[Person](
    [ID] [BIGINT] IDENTITY(1,1) NOT NULL,
    [Name] [NVARCHAR](50) NULL,
    [Age] [INT] NULL,
    [Gender] [NVARCHAR](10) NULL,
 CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

    二、自定義特性

    定義兩個自定義特性:

    2.1、DataTableAttribute

    此為數據表特性,包含表名(TableName)、主鍵(Key)。

    [Serializable]
    public class DataTableAttribute : Attribute
    {
        /// <summary>
        /// 數據表
        /// </summary>
        public string TableName { get; }

        /// <summary>
        /// 主鍵
        /// </summary>
        public string Key { get; }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="key"></param>
        public DataTableAttribute(string tableName, string key)
        {
            TableName = tableName;
            Key = key;
        }
    }

    2.2、DataFieldAttribute

    此為欄位特性,包含欄位名(FieldName)、欄位類型(FieldType)、長度(Length)、是否自增(IsIdentity)。

    [Serializable]
    public class DataFieldAttribute : Attribute
    {
        public string FieldName { get; set; }
        public string FieldType { get; set; }
        public int Length { get; set; }
        public bool IsIdentity { get; set; }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="fieldName">欄位名</param>
        /// <param name="fieldType">欄位類型</param>
        /// <param name="length">長度</param>
        /// <param name="isIdentity">是否自增長</param>
        public DataFieldAttribute(string fieldName, string fieldType, int length, bool isIdentity)
        {
            FieldName = fieldName;
            FieldType = fieldType;
            Length = length;
            IsIdentity = isIdentity;
        }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="length"></param>
        /// <param name="isIdentity"></param>
        public DataFieldAttribute(string fieldName, string fieldType, int length) : this(fieldName, fieldType, length, false)
        { }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="fieldtype"></param>
        public DataFieldAttribute(string fieldName, string fieldType) : this(fieldName, fieldType, 0, false)
        { }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="isIdentity"></param>
        public DataFieldAttribute(string fieldName, bool isIdentity) : this(fieldName, "", 0, isIdentity)
        { }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="fieldName"></param>
        public DataFieldAttribute(string fieldName) : this(fieldName, false)
        { }
    }

    三、生成實體類

    3.1、實體類樣式

    依照前面的規劃,Person表需要生成下麵這個樣子:

using System;
using System.Collections.Generic;
using System.Text;
using LinkTo.ORM.CustomAttribute;

namespace LinkTo.ORM.Model
{
    [DataTable("Person","ID")]
    [Serializable]
    public class Person
    {
        public Person() { }

        [DataField("ID","bigint",19,true)]
        public long? ID {get; set;}

        [DataField("Name","nvarchar",50,false)]
        public string Name {get; set;}

        [DataField("Age","int",10,false)]
        public int? Age {get; set;}

        [DataField("Gender","nvarchar",10,false)]
        public string Gender {get; set;}
    }
}

    3.2、使用T4模板生成實體類

    3.2.1、T4Code文件夾的文本模板

<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data"#>
<#@ import namespace="System.Data.SqlClient"#>
<#+
    #region T4Code
    /// <summary>
    /// 資料庫架構介面
    /// </summary>
    public interface IDBSchema : IDisposable
    {
        List<string> GetTableList();
        DataTable GetTableMetadata(string tableName);
    }

    /// <summary>
    /// 資料庫架構工廠
    /// </summary>
    public class DBSchemaFactory
    {
        static readonly string DatabaseType = "SqlServer";
        public static IDBSchema GetDBSchema()
        {
            IDBSchema dbSchema;
            switch (DatabaseType) 
            {
                case "SqlServer":
                    {
                        dbSchema =new SqlServerSchema();
                        break;
                    }
                default: 
                    {
                        throw new ArgumentException("The input argument of DatabaseType is invalid.");
                    }
            }
            return dbSchema;
        }
    }

    /// <summary>
    /// SqlServer
    /// </summary>
    public class SqlServerSchema : IDBSchema
    {
        public string ConnectionString = "Server=.;Database=Test;Uid=sa;Pwd=********;";
        public SqlConnection conn;

        public SqlServerSchema()
        {
            conn = new SqlConnection(ConnectionString);
            conn.Open();
        }

        public List<string> GetTableList()
        {
            List<string> list = new List<string>();
            string commandText = "SELECT NAME TABLE_NAME FROM SYSOBJECTS WHERE XTYPE='U' ORDER BY NAME";

            using(SqlCommand cmd = new SqlCommand(commandText, conn))
            {
                using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    while (dr.Read())
                    {
                        list.Add(dr["TABLE_NAME"].ToString());
                    }
                }
            }

            return list;
        }
        
        public DataTable GetTableMetadata(string tableName)
        {
            string commandText=string.Format
                (
                    "SELECT A.NAME TABLE_NAME,B.NAME FIELD_NAME,C.NAME DATATYPE,ISNULL(B.PREC,0) LENGTH, "+
                        "CONVERT(BIT,CASE WHEN NOT F.ID IS NULL THEN 1 ELSE 0 END) ISKEY, "+
                        "CONVERT(BIT,CASE WHEN COLUMNPROPERTY(B.ID,B.NAME,'ISIDENTITY') = 1 THEN 1 ELSE 0 END) AS ISIDENTITY, "+
                        "CONVERT(BIT,B.ISNULLABLE) ISNULLABLE "+
                    "FROM SYSOBJECTS A INNER JOIN SYSCOLUMNS B ON A.ID=B.ID INNER JOIN SYSTYPES C ON B.XTYPE=C.XUSERTYPE "+
                        "LEFT JOIN SYSOBJECTS D ON B.ID=D.PARENT_OBJ AND D.XTYPE='PK' "+
                        "LEFT JOIN SYSINDEXES E ON B.ID=E.ID AND D.NAME=E.NAME "+
                        "LEFT JOIN SYSINDEXKEYS F ON B.ID=F.ID AND B.COLID=F.COLID AND E.INDID=F.INDID "+
                    "WHERE A.XTYPE='U' AND A.NAME='{0}' "+
                    "ORDER BY A.NAME,B.COLORDER", tableName
                );

            using(SqlCommand cmd = new SqlCommand(commandText, conn))
            {
                SqlDataAdapter da = new SqlDataAdapter(cmd);
                DataSet ds = new DataSet();
                da.Fill(ds,"Schema");
                return ds.Tables[0];
            }
        }

        public void Dispose()
        {
            if (conn != null)
            {
                conn.Close();
            }
        }
    }
    #endregion
#>
DBSchema.ttinclude
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data"#>
<#@ import namespace="System.IO"#>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>

<#+
// T4 Template Block manager for handling multiple file outputs more easily.
// Copyright (c) Microsoft Corporation.All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

// Manager class records the various blocks so it can split them up
class Manager
{
    public struct Block
    {
        public string Name;
        public int Start, Length;
    }

    public List<Block> blocks = new List<Block>();
    public Block currentBlock;
    public Block footerBlock = new Block();
    public Block headerBlock = new Block();
    public ITextTemplatingEngineHost host;
    public ManagementStrategy strategy;
    public StringBuilder template;
    public string OutputPath { get; set; }

    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader)
    {
        this.host = host;
        this.template = template;
        OutputPath = string.Empty;
        strategy = ManagementStrategy.Create(host);
    }

    public void StartBlock(string name)
    {
        currentBlock = new Block { Name = name, Start = template.Length };
    }

    public void StartFooter()
    {
        footerBlock.Start = template.Length;
    }

    public void EndFooter()
    {
        footerBlock.Length = template.Length - footerBlock.Start;
    }

    public void StartHeader()
    {
        headerBlock.Start = template.Length;
    }

    public void EndHeader()
    {
        headerBlock.Length = template.Length - headerBlock.Start;
    }    

    public void EndBlock()
    {
        currentBlock.Length = template.Length - currentBlock.Start;
        blocks.Add(currentBlock);
    }

    public void Process(bool split)
    {
        string header = template.ToString(headerBlock.Start, headerBlock.Length);
        string footer = template.ToString(footerBlock.Start, footerBlock.Length);
        blocks.Reverse();
        foreach(Block block in blocks) {
            string fileName = Path.Combine(OutputPath, block.Name);
            if (split) {
                string content = header + template.ToString(block.Start, block.Length) + footer;
                strategy.CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            } else {
                strategy.DeleteFile(fileName);
            }
        }
    }
}

class ManagementStrategy
{
    internal static ManagementStrategy Create(ITextTemplatingEngineHost host)
    {
        return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);
    }

    internal ManagementStrategy(ITextTemplatingEngineHost host) { }

    internal virtual void CreateFile(string fileName, string content)
    {
        File.WriteAllText(fileName, content);
    }

    internal virtual void DeleteFile(string fileName)
    {
        if (File.Exists(fileName))
            File.Delete(fileName);
    }
}

class VSManagementStrategy : ManagementStrategy
{
    private EnvDTE.ProjectItem templateProjectItem;

    internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host)
    {
        IServiceProvider hostServiceProvider = (IServiceProvider)host;
        if (hostServiceProvider == null)
            throw new ArgumentNullException("Could not obtain hostServiceProvider");

        EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
        if (dte == null)
            throw new ArgumentNullException("Could not obtain DTE from host");

        templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
    }

    internal override void CreateFile(string fileName, string content)
    {
        base.CreateFile(fileName, content);
        ((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);
    }

    internal override void DeleteFile(string fileName)
    {
        ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);
    }

    private void FindAndDeleteFile(string fileName)
    {
        foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
        {
            if (projectItem.get_FileNames(0) == fileName)
            {
                projectItem.Delete();
                return;
            }
        }
    }
}
#>
MultiDocument.ttinclude

    DBSchema.ttinclude主要實現了資料庫工廠的功能。註:請將資料庫連接字元串改成您自己的。

    MultiDocument.ttinclude主要實現了多文檔的功能。

    3.2.2、生成實體類的文本模板

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ include file="T4Code/DBSchema.ttinclude"#>
<#@ include file="T4Code/MultiDocument.ttinclude"#>
<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = Path.GetDirectoryName(Host.TemplateFile)}; #>
<#
    //System.Diagnostics.Debugger.Launch();//調試
    var dbSchema = DBSchemaFactory.GetDBSchema();
    List<string> tableList = dbSchema.GetTableList();
    foreach (string tableName in tableList)
    {
        manager.StartBlock(tableName+".cs");
        DataTable table = dbSchema.GetTableMetadata(tableName);

        //獲取主鍵
        string strKey = string.Empty;
        foreach (DataRow dataRow in table.Rows)
        {
            if ((bool)dataRow["ISKEY"] == true)
            {
                strKey = dataRow["FIELD_NAME"].ToString();
                break;
            }
        }
        
#>
//-------------------------------------------------------------------------------
// 此代碼由T4模板MultModelAuto自動生成
// 生成時間 <#= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") #>
// 對此文件的更改可能會導致不正確的行為,並且如果重新生成代碼,這些更改將會丟失。
//-------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using LinkTo.ORM.CustomAttribute;

namespace LinkTo.ORM.Model
{
    [DataTable("<#= tableName #>","<#= strKey #>")]
    [Serializable]
    public class <#= tableName #>
    {
        public <#= tableName #>() { }
<#
        foreach (DataRow dataRow in table.Rows)
        {
            //獲取數據類型
            string dbDataType = dataRow["DATATYPE"].ToString();
            string dataType = string.Empty;
                    
            switch (dbDataType)
            {
                case "decimal":
                case "numeric":
                case "money":
                case "smallmoney":
                    dataType = "decimal?";
                    break;
                case "char":
                case "nchar":
                case "varchar":
                case "nvarchar":
                case "text":
                case "ntext":
                    dataType = "string";
                    break;
                case "uniqueidentifier":
                    dataType = "Guid?";
                    break;
                case "bit":
                    dataType = "bool?";
                    break;
                case "real":
                    dataType = "Single?";
                    break;
                case "bigint":
                    dataType = "long?";
                    break;
                case "int":
                    dataType = "int?";
                    break;
                case "tinyint":
                case "smallint":
                    dataType = "short?";
                    break;
                case "float":
                    dataType = "float?";
                    break;
                case "date":
                case "datetime":
                case "datetime2":
                case "smalldatetime":
                    dataType = "DateTime?";
                    break;
                case "datetimeoffset ":
                    dataType = "DateTimeOffset?";
                    break;
                case "timeSpan ":
                    dataType = "TimeSpan?";
                    break;
                case "image":
                case "binary":
                case "varbinary":
                    dataType = "byte[]";
                    break;
                default:
                    break;
            }
#>

        [DataField("<#= dataRow["FIELD_NAME"].ToString() #>","<#= dataRow["DATATYPE"].ToString() #>",<#= dataRow["LENGTH"].ToString() #>,<#= dataRow["ISIDENTITY"].ToString().ToLower() #>)]
        public <#= dataType #> <#= dataRow["FIELD_NAME"].ToString() #> {get; set;}
<#
        }
#>
    }
}
<#
        manager.EndBlock();
    }
    dbSchema.Dispose();
    manager.Process(true);
#>
MultiModelAuto.tt

    註:由於ORM拼接SQL時使用的是表特性及欄位特性,可以看出表特性上使用的表名、欄位特性上使用的欄位名,都是與資料庫一致的。有了這個保障,數據表生成實體類的時候,類名是可以更改的,因為我只需要保證表特性與資料庫一致即可。舉個例子,我有個數據表Person_A,在生成實體類時,類名可以生成為Class PersonA {...},但是表特性依然是[DataTable("Person_A","...")]。相同的原理,屬性名也是可以更改的。

    四、ORM實現

    數據表的CURD,主要是通過反射來實現SQL拼接,實現如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using LinkTo.ORM.CustomAttribute;

namespace LinkTo.ORM
{
    public static class DBHelper
    {
        public static readonly string ConnectionString = "Server=.;Database=Test;Uid=sa;Pwd=********;";
        private static readonly Hashtable _HashTableName = new Hashtable(); //表名緩存
        private static readonly Hashtable _HashKey = new Hashtable();       //主鍵緩存

        /// <summary>
        /// 資料庫連接
        /// </summary>
        /// <returns></returns>
        public static SqlConnection GetConnection()
        {
            SqlConnection conn = 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在 SpringMVC 中,可以指定畫面的跳轉方式。使用 首碼實現請求轉發跳轉,使用 首碼實現重定向跳轉。有首碼的轉發和重定向操作和配置的視圖解析器沒有關係,視圖解析器不會進行拼串。 請求轉發首碼—forward: 使用請求轉發跳轉方式,url 地址不會改變,一次請求一次相應,跳轉到的地址可以獲得 ...
  • 面試題51. 數組中的逆序對 題目來源: "https://leetcode cn.com/problems/shu zu zhong de ni xu dui lcof/" 題目 在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的 ...
  • 首先我們來看解封裝: >>> mytuple=3,4,5 >>> mytuple (3, 4, 5) 這將 3,4,5 封裝到元組 mytuple 中。 現在我們將這些值解封裝到變數 x,y,z 中: >>> x,y,z=mytuple >>> x+y+z 得到結果12.結語 本文首發於python ...
  • 0. 前言 上一篇內容介紹了Console類和Math類,這篇內容著重介紹一下C 中時間日期的處理方式。 上一篇勘誤: 上一篇中關於靜態類沒有構造函數,這一表述有誤。正確的說法是C 中靜態類不包含常規構造函數,但可以添加一個靜態構造函數。 靜態構造函數與普通普通構造函數的區別是,靜態構造函數沒有訪問 ...
  • 前言: 這是 項目實踐系列 , 算是中高級系列博文, 用於為項目開發過程中不好解決的問題提出解決方案的. 不屬於入門級系列. 解釋起來也比較跳躍, 只講重點. 因為有網友的項目需求, 所以提前把這些解決方案做出來並分享. 問題: Blazor自己是攜帶一個簡單的路由功能的, 當切換Url的時候, 整 ...
  • 開題 ​ 既然決定了開始寫博客,那就從讀ASP.NET core的源碼開始吧!對於我這個不怎麼善於寫文章的人來說,也算是鍛煉鍛煉自己歸納總結能力。 千里之行,始於足下 ​ 俗話說,“千里之行,始於足下”,我們先看看ASP.NET core的 是如何使用的。為了方便說明,這裡建立的 的測試工程,使用 ...
  • 最近2年一直能看到 Net Core的介紹,看到支持WPF和Winform引起了興趣,寫簡單Demo運行看效果和瞭解部署。 現在準備把項目正式遷移到.Net Core, 就先用了一個比較單一的項目試試,編譯很大部分很順利通過,沒有什麼需要註意, 也就沒有什麼印象,一運行不得了各種報錯。 也沒有去看具... ...
  • 我想快速給WPF程式添加托盤菜單 1 簡單要求: 使用開源控制項庫 在XAML中聲明托盤菜單,就像給控制項添加ContextMenu一樣 封裝了常用命令,比如:打開主窗體、退出應用程式等 我在TerminalMACS中添加了托盤菜單,最終實現的托盤菜單效果: 2 如何做? 【Step 1】在已創建的WP ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...