SQL Server CLR 使用 C# 自定義存儲過程和觸發器

来源:http://www.cnblogs.com/Brambling/archive/2017/12/10/8016060.html
-Advertisement-
Play Games

這一篇博客接著上一篇博客繼續介紹 SQL CLR Stored Procedure 和 CLR Trigger, 上一篇博客介紹了 SQL CLR Function 的使用,以及 CLR 程式集的註冊和 CLR Function 的註冊。 我的上一篇博客:SQL Server CLR 使用 C# 自 ...


這一篇博客接著上一篇博客繼續介紹 SQL CLR Stored Procedure 和 CLR Trigger,

上一篇博客介紹了 SQL CLR Function 的使用,以及 CLR 程式集的註冊和 CLR Function 的註冊。

我的上一篇博客:SQL Server CLR 使用 C# 自定義函數

 

四、CLR Stored Procedure

接下來在之前的項目選擇添加新項,選擇 SQL CLR C# 存儲過程。

public partial class StoredProcedures
{
    /// <summary>
    /// 無輸入參數,無輸出參數,無輸出結果,有輸出消息,無返回值的存儲過程
    /// </summary>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "HelloWorld")]
    public static void HelloWorld()
    {
        SqlContext.Pipe.Send("Hello World");
    }

    /// <summary>
    /// 有輸入參數,無輸出參數,無輸出結果,無輸出消息,有返回值的存儲過程
    /// </summary>
    /// <param name="name"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStrLength")]
    public static SqlInt32 GetStrLength(SqlString str)
    {
        return str.ToString().Length;
    }

    /// <summary>
    /// 有輸入參數,有輸出參數,無輸出結果,無輸出消息,無返回值的存儲過程
    /// </summary>
    /// <param name="name"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "SayHello")]
    public static void SayHello(SqlString name,out SqlString sayHello)
    {
        sayHello = "Hello " + name.ToString();
    }
}

註冊程式集和註冊存儲過程的 SQL 後面再貼出來,這裡我們先看看結果。

PS:如果你用的是 Visual Studio 2015,那麼你可以在【項目路徑>obj>Debug】文件夾下麵找到自動生成的註冊程式集和存儲過程的 SQL 語句。至於其他版本大家可以試試。

執行存儲過程 HelloWorld:

--執行存儲過程 HelloWorld
exec [dbo].[HelloWorld]

結果:

這就是輸出消息,輸出消息和輸出結果是不一樣的,輸出消息是沒辦法獲取的(我沒辦法),而輸出結果就相當於用 Select 語句查詢出來的結果一樣,是可以獲取的。

執行存儲過程 GetStrLength:

--執行存儲過程 GetStrLength
declare @res int 
exec @res=[dbo].[GetStrLength] '123456' 
select @res 

結果:

這個 C# 代碼裡面是有返回值的,也可以通過這種方式獲取到返回值,但是這種返回值的方式只能返回 int 類型的返回值。

如果需要多個返回值呢?看下麵的存儲過程,可以通過設置多個輸出參數來達到。

執行存儲過程 SayHello:

--執行存儲過程 SayHello
declare @SayHello nvarchar(32)
exec [dbo].[SayHello] 'Brambling',@SayHello output 

select @SayHello

結果:

其實弄明白輸入參數、輸出參數、輸出消息、輸出結果和返回值這幾個問題,CLR 存儲過程的介紹就可以完了。

但是存儲過程裡面總是免不了要操作數據的,那麼下麵就看看對於資料庫數據的操作和輸出結果集的方法吧。

   /// <summary>
    /// 根據學生學號獲取學生姓名
    /// </summary>
    /// <param name="Id"></param>
    /// <returns></returns>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentNameByStuNo")]
    public static void GetStudentNameByStuNo(SqlString stuNo,out SqlString stoName)
    {
        stoName = string.Empty;

        //因為程式是在SQL Server內執行,所以連接字元串寫成"context connection=true"即可
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select StuName from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();
            if (dataReader.Read())
            {
                stoName = dataReader.GetString(0);
            }
            dataReader.Close();
        }
    }

執行存儲過程 GetStudentNameByStuNo:

declare @StuName nvarchar(32)
exec [GetStudentNameByStuNo] 'A001',@StuName output 
select @StuName 
exec [GetStudentNameByStuNo] 'A003',@StuName output 
select @StuName 

結果:

可以看到我們通過輸出參數獲取到了返回值。如果現在我需要獲取整個學生的所有信息呢?

雖然可以通過設置多個輸出參數得到,但是學生信息的欄位過多呢?下麵看看輸出結果集的方式。

   /// <summary>
    /// 根據學生的學號獲取該學生的所有信息
    /// 返回的是一個結果集,即有多少條數據就返回多少條數據
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_First")]
    public static void GetStudentInfoByStuNo_First(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();
            SqlContext.Pipe.Send(dataReader);
            dataReader.Close();
        }
    }

    /// <summary>
    /// 根據學生的學號獲取該學生的所有信息
    /// 這種方式效率比較高,是通過直接執行 SqlCommand 指令,然後把數據發送到客戶端,不需要經過托管記憶體
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_Second")]
    public static void GetStudentInfoByStuNo_Second(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlContext.Pipe.ExecuteAndSend(comm);
        }
    }

    /// <summary>
    /// 根據學生的學號獲取該學生的所有信息
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_Third")]
    public static void GetStudentInfoByStuNo_Third(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            if(dataReader.Read())
            {
                dataRecord.SetInt32(0,(int)dataReader["ID"]);
                dataRecord.SetString(1,(string)dataReader["StuNo"]);
                dataRecord.SetString(2,(string)dataReader["StuName"]);
                dataRecord.SetInt32(3,(int)dataReader["StuAge"]);
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

執行存儲過程:

--執行存儲過程 GetStudentInfoByStuNo_First
exec [GetStudentInfoByStuNo_First] 'A003'

--執行存儲過程 GetStudentInfoByStuNo_Second
exec [GetStudentInfoByStuNo_Second] 'A003'

--執行存儲過程 GetStudentInfoByStuNo_Third
exec [GetStudentInfoByStuNo_Third] 'A003'

結果:

上面三個方法中,第一個方法和第二個方法都是直接返回查詢結果的,但是在實際存儲過程當中是不會這樣寫的,裡面應該包含有邏輯操作等等,所以就有了第三個方法。

那麼現在是返回的一條數據,如果是返回多條數據呢?第一種方法和第二種方法就不說了,因為這兩種方法都是返回結果集的。

   /// <summary>
    /// 根據年齡查詢學生的信息
    /// 這種方式是一條數據返回一個結果集
    /// </summary>
    /// <param name="stuAge"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentsInfoByStuAge_Single")]
    public static void GetStudentsInfoByStuAge_Single(SqlInt32 stuAge)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuAge=@StuAge;";

            SqlParameter param = new SqlParameter("@StuAge", SqlDbType.Int);
            param.SqlValue = stuAge;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            while (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //發送結果集到客戶端
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

    /// <summary>
    /// 根據年齡查詢學生的信息
    /// 這種方式是所有的數據返回一個結果集
    /// </summary>
    /// <param name="stuAge"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentsInfoByStuAge_Multiple")]
    public static void GetStudentsInfoByStuAge_Multiple(SqlInt32 stuAge)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuAge=@StuAge;";

            SqlParameter param = new SqlParameter("@StuAge", SqlDbType.Int);
            param.SqlValue = stuAge;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            //標記結果集的開始
            SqlContext.Pipe.SendResultsStart(dataRecord);
            while (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //填充數據到結果集
                SqlContext.Pipe.SendResultsRow(dataRecord);
            }
            //標記結果集的結束
            SqlContext.Pipe.SendResultsEnd();
            dataReader.Close();
        }
    }

執行存儲過程:

--執行存儲過程 GetStudentsInfoByStuAge_Single
exec [dbo].[GetStudentsInfoByStuAge_Single] '18'

--執行存儲過程 GetStudentsInfoByStuAge_Multiple
exec [dbo].[GetStudentsInfoByStuAge_Multiple] '18'

結果:

可以很清楚的看到,方法一是一條數據返回一個結果集,方法二是所有數據返回一個結果集。

下麵貼出註冊存儲過程的 SQL 語句,註冊程式集的就不貼了,我的上一篇博客有過介紹。

--註冊存儲過程 HelloWorld
CREATE PROCEDURE [dbo].[HelloWorld] 
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[HelloWorld];    --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStrLength CREATE PROCEDURE [dbo].[GetStrLength] @str [nvarchar](MAX) WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStrLength]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 SayHello CREATE PROCEDURE [dbo].[SayHello] @name [nvarchar](MAX), @sayHello [nvarchar](MAX) OUTPUT WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[SayHello]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentNameByStuNo CREATE PROCEDURE [dbo].[GetStudentNameByStuNo] @stuNo [nvarchar](MAX), @stoName [nvarchar](MAX) OUTPUT WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentNameByStuNo]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentInfoByStuNo_First CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_First] @stuNo [nvarchar](MAX) WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_First]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentInfoByStuNo_Second CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_Second] @stuNo [nvarchar](MAX) WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_Second]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentInfoByStuNo_Third CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_Third] @stuNo [nvarchar](MAX) WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_Third]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentsInfoByStuAge_Single CREATE PROCEDURE [dbo].[GetStudentsInfoByStuAge_Single] @stuAge [int] WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentsInfoByStuAge_Single]; --EXTERNAL NAME 程式集名.類名.方法名
GO --註冊存儲過程 GetStudentsInfoByStuAge_Multiple CREATE PROCEDURE [dbo].[GetStudentsInfoByStuAge_Multiple] @stuAge [int] WITH EXECUTE AS CALLER AS EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentsInfoByStuAge_Multiple]; --EXTERNAL NAME 程式集名.類名.方法名 GO

 

五、CLR Trigger

接下來選擇添加新項,選擇 SQL CLR C# 觸發器。

1、DML 觸發器

(1) after trigger

public partial class Triggers
{
    /// <summary>
    /// 輸出操作的數據
    /// </summary>
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "FirstSqlTrigger", Target = "StudentInfo", Event = "FOR INSERT,UPDATE,DELETE")]
    public static void FirstSqlTrigger()
    {
        switch (SqlContext.TriggerContext.TriggerAction)
        {
            case TriggerAction.Insert:
                GetInsertedOrDeleted(InsOrDel.Inserted);
                break;
            case TriggerAction.Update:
                GetInsertedOrDeleted(InsOrDel.Inserted);
                GetInsertedOrDeleted(InsOrDel.Deleted);
                break;
            case TriggerAction.Delete:
                GetInsertedOrDeleted(InsOrDel.Deleted);
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// 獲取操作的數據或之後的數據
    /// </summary>
    /// <param name="insOrDel"></param>
    /// <returns></returns>
    private static void GetInsertedOrDeleted(InsOrDel insOrDel)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from " + insOrDel.ToString() + ";";
            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            if (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //發送結果集到客戶端
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

    private enum InsOrDel
    {
        Inserted,
        Deleted
    }
}

測試 SQL 語句:

  -- Insert 操作
  insert into StudentInfo(StuNo,StuName,StuAge)
  values('A006','小飛',20)

  -- Update 操作
  update StudentInfo set StuName='小飛飛' where StuNo='A006' 

  -- Delete 操作
  delete from StudentInfo where StuNo='A006'

結果:

這裡說明一下,Microsoft.SqlServer.Server.SqlTrigger 有三個屬性。

Name:表示觸發器的名稱。

Target:表示觸發器的目標表的名稱。

Event:表示觸發執行觸發器的動作。

 

(2) instead of trigger

public partial class Triggers
{
    /// <summary>
    /// 輸出操作類型
    /// </summary>
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "InsteadOfTrigger",Target = "StudentInfo",Event = "INSTEAD OF INSERT,UPDATE,DELETE")]
    public static void InsteadOfTrigger()
    {
        SqlDataRecord dataRecord = new SqlDataRecord(
            new SqlMetaData[]
            {
                new SqlMetaData("Message",SqlDbType.NVarChar,128)
            }
        );

        switch (SqlContext.TriggerContext.TriggerAction)
        {
            case TriggerAction.Insert:
                dataRecord.SetString(0, "Insert操作");
                break;
            case TriggerAction.Update:
                dataRecord.SetString(0, "Update操作");
                break;
            case TriggerAction.Delete:
                dataRecord.SetString(0, "Delete操作");
                break;
            default:
                dataRecord.SetString(0, "Nothing");
                break;
        }
        SqlContext.Pipe.Send(dataRecord);
    }
}

測試 SQL 語句:

-- Insert 操作
insert into StudentInfo(StuNo,StuName,StuAge)
values('A006','小飛',20)

-- Update 操作
update StudentInfo set StuName='小飛飛' where StuNo='A006' 

-- Delete 操作
delete from StudentInfo where StuNo='A006'

結果:

Instead of 是一種特殊的觸發器,它只執行觸發器本身,也就是觸發器裡面的操作,

所以 Insert、Update、Delete 操作是不執行的,只是用於觸發該觸發器,而且 Instead of 觸發器會覆蓋掉 after 觸發器。

 

2、DDL 觸發器

DDL 觸發器又分為資料庫級別的觸發器和伺服器級別的觸發器,這裡只介紹資料庫級別的觸發器。

public partial class Triggers
{
    /// <

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

-Advertisement-
Play Games
更多相關文章
  • 本節主要介紹Linux的文件系統,包括文件存儲結構,硬碟的分區、格式化、掛載,文件和目錄的軟鏈接、硬鏈接。 ...
  • 1.當驅動有誤時,比如,訪問的記憶體地址是非法的,便會列印一大串的oops出來 1.1以LED驅動為例 將open()函數里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下圖所示: 1.2然後編譯裝載26th_segmentfault並執行測試程式後,內核便列印了oops出來,如下圖所示 ...
  • 本節主要介紹Linux的記憶體和進程,用到的基本命令是top,free,ps,kill,pstree,psgrep;用思維導圖進行展示說明。 ...
  • shell 條件表達式 if case 大量企業實際應用 ...
  • 本節主要介紹Linux系統啟動的完整過程、詳細說明瞭Linux的7種啟動模式;對Linux系統的破解展示,來說明Linux系統基本安全防護的必要性。 ...
  • Mysql實現企業級日誌管理、備份與恢復實戰 環境背景:隨著業務的發展,公司業務和規模不斷擴大,網站積累了大量的用戶信息和數據,對於一家互聯網公司來說,用戶和業務數據是根基。一旦公司的數據錯亂或者丟失,對於互聯網公司而言就等於說是滅頂之災,為防止系統出現操作失誤或系統故障導致數據丟失,公司要求加強用 ...
  • 我用的vmware 安裝kali,期間遇到不少小坑: NO1: 安裝一半提示安裝失敗,停在了這裡,經各種百度,解決,分區大小最好大於25G才能過(不知道什麼原理) NO2: 安裝完之後用ssh鏈接一直伺服器拒絕密碼,賬號密碼都是正確的,趕緊確定下ssh伺服器動了沒,然後找到 /etc/ssh/ssh ...
  • 一、主題美化 系統清理 系統更新: 安裝完系統之後,需要更新一些補丁。Ctrl+Alt+T調出終端,執行一下代碼: 卸載libreOffice: libreOffice是ubuntu自帶的開源office軟體,我們一般習慣用WPS,在第二部分會說明WPS在Ubuntu中的安裝 卸載不常用軟體: 刪除 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...