Elasticsearch 系列(七)- 在ASP.NET Core中使用高級客戶端NEST來操作Elasticsearch

来源:https://www.cnblogs.com/xyh9039/p/18200453
-Advertisement-
Play Games

本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...


本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。

NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手和運行。

在底層,NEST使用Elasticsearch.Net低級客戶端來發送請求和接收響應,使用並擴展了Elasticsearch.Net中的許多類型。這個低級客戶端本身仍然可以通過高級客戶端的 .LowLevel 屬性來暴露。

高級客戶端NEST官方文檔地址:https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest-getting-started.html

廢話不多說,首選我們來看一下Demo的目錄結構,如下所示:

本Demo的Web項目為ASP.NET Core Web 應用程式(目標框架為.NET 8.0) MVC項目。

ORM框架用的是SqlSugarScope,實體映射用的是AutoMapper,DI框架用的是Autofac。

高級客戶端NEST版本用的 7.12.1 ,和Elasticsearch的版本保持一致。

一、連接Elasticsearch

1、單節點連接

var settings = new ConnectionSettings(new Uri("http://example.com:9200"))
    .DefaultIndex("people");

var client = new ElasticClient(settings);

2、多節點連接

var uris = new[]
{
    new Uri("http://localhost:9200"),
    new Uri("http://localhost:9201"),
    new Uri("http://localhost:9202"),
};

var connectionPool = new SniffingConnectionPool(uris);
var settings = new ConnectionSettings(connectionPool)
    .DefaultIndex("people");

var client = new ElasticClient(settings);

二、調試(Debugging)

官方文檔:

https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/debug-information.html

https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/debug-mode.html

https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/logging-with-on-request-completed.html

在使用NEST開發Elasticsearch應用程式時,查看NEST生成併發送給Elasticsearch的請求以及Elasticsearch返回的響應信息是非常有價值的。

我們直接來看一個示例,核心代碼如下:

using System.Text;
using Microsoft.Extensions.Configuration;
using Nest;
using Elasticsearch.Net;

namespace TianYaSharpCore.Elasticsearch
{
    /// <summary>
    /// ElasticClient提供者
    /// NEST官方文檔:https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest-getting-started.html#nest-getting-started
    /// </summary>
    public class ElasticClientProvider : IElasticClientProvider
    {
        /// <summary>
        /// Linq查詢的官方Client(高級客戶端)
        /// </summary>
        public IElasticClient ElasticLinqClient { get; set; }

        /// <summary>
        /// Json查詢的官方Client(低級客戶端)
        /// </summary>
        public IElasticLowLevelClient ElasticJsonClient { get; set; }

        /// <summary>
        /// 構造函數
        /// </summary>
        public ElasticClientProvider(IConfiguration configuration)
        {
            /*
                var uris = new[]
                {
                    new Uri("http://localhost:9200"),
                    new Uri("http://localhost:9201"),
                    new Uri("http://localhost:9202"),
                }; 
            */
            var uris = configuration["ElasticsearchConfig:Uris"];
            var defaultIndex = configuration["ElasticsearchConfig:DefaultIndex"]; //預設索引庫名稱
            var uriList = uris?.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries)
                .ToList().ConvertAll(u => new Uri(u)) ?? new List<Uri>();
            if (uriList.Count <= 0)
            {
                uriList.Add(new Uri("http://localhost:9200"));
            }

            if (string.IsNullOrEmpty(defaultIndex))
            {
                defaultIndex = "defaultIndex";
            }

            var list = new List<string>();
            var connectionPool = new SniffingConnectionPool(uriList); //連接池
            var settings = new ConnectionSettings(connectionPool)
                //.BasicAuthentication("root", "123456")     //驗證賬號密碼登錄
                .RequestTimeout(TimeSpan.FromSeconds(30))  //請求超時 30s
                .DefaultFieldNameInferrer(fieldName => fieldName) //移除NEST將類型屬性名稱序列化為駝峰式命名的預設行為

                /* Debug調試開始 */
                // 請註意,啟用詳細的調試信息可能會對性能產生影響,並且可能會占用更多的記憶體來存儲額外的信息。
                // 因此,在生產環境中應該禁用它,只在開發或故障排除時啟用。
                // 在生產環境中排查問題時建議使用 RequestConfiguration() 以針對某個請求單獨禁用直接流處理以捕獲請求和響應的位元組。
                // 官方文檔:
                // https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/debug-mode.html
                // https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/debug-information.html
                // https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/logging-with-on-request-completed.html
                .EnableDebugMode() // 啟用詳細的調試信息(在生產環境中應該禁用它,只在開發或故障排除時啟用)

                //.DisableDirectStreaming() // 禁用直接流處理以捕獲請求和響應的位元組
                //.PrettyJson() // 返回格式化的JSON響應

                // 這個回調會在每次請求完成(無論成功還是失敗)時被調用
                .OnRequestCompleted(apiCallDetails =>
                {
                    // 如果您有複雜的日誌記錄需求,這是一個很好的地方來實現它們,因為您可以訪問到請求和響應的詳細信息。
                    // 根據您的具體需求,您可能需要在回調中執行更複雜的邏輯,比如記錄詳細的日誌、發送警報或執行其他業務邏輯。

                    // log out the request and the request body, if one exists for the type of request
                    if (apiCallDetails.RequestBodyInBytes != null)
                    {
                        list.Add(
                            $"{apiCallDetails.HttpMethod} {apiCallDetails.Uri} " +
                            $"{Encoding.UTF8.GetString(apiCallDetails.RequestBodyInBytes)}"); //請求體
                    }
                    else
                    {
                        list.Add($"{apiCallDetails.HttpMethod} {apiCallDetails.Uri}");
                    }

                    // log out the response and the response body, if one exists for the type of response
                    if (apiCallDetails.ResponseBodyInBytes != null)
                    {
                        list.Add($"Status: {apiCallDetails.HttpStatusCode}" +
                                 $"{Encoding.UTF8.GetString(apiCallDetails.ResponseBodyInBytes)}"); //響應體
                    }
                    else
                    {
                        list.Add($"Status: {apiCallDetails.HttpStatusCode}");
                    }
                })
                /* Debug調試結束 */

                .DefaultIndex(defaultIndex);

            ElasticLinqClient = new ElasticClient(settings);
            ElasticJsonClient = ElasticLinqClient.LowLevel; //高級客戶端 NEST 可以通過訪問客戶端上的 .LowLevel 屬性來獲取 Elasticsearch.Net 低級客戶端
        }
    }
}

其中 .EnableDebugMode() 表示啟用詳細的調試信息。請註意,啟用詳細的調試信息可能會對性能產生影響,並且可能會占用更多的記憶體來存儲額外的信息。因此,在生產環境中應該禁用它,只在開發或故障排除時啟用。在生產環境中排查問題時建議使用 RequestConfiguration() 以針對某個請求單獨禁用直接流處理以捕獲請求和響應的位元組。

其中 .OnRequestCompleted() 這個回調會在每次請求完成(無論成功還是失敗)時被調用。如果您有複雜的日誌記錄需求,這是一個很好的地方來實現它們,因為您可以訪問到請求和響應的詳細信息。根據您的具體需求,您可能需要在回調中執行更複雜的邏輯,比如記錄詳細的日誌、發送警報或執行其他業務邏輯。

需要註意的是,此處的 .EnableDebugMode() 配置是針對所有的請求都生效的。在生產環境中,您可能不希望為所有請求都禁用直接流傳輸,因為這樣做會由於在記憶體中緩存請求和響應位元組而產生性能開銷。

為此,可以針對每個請求單獨啟用 DisableDirectStreaming 功能,如下所示:

/// <summary>
/// 調試
/// </summary>
public async Task DebugInformationAsync()
{
    // 其中HotelDoc類為自定義酒店數據對應的ES文檔
    var searchResponse = await _elasticClientProvider.ElasticLinqClient.SearchAsync<HotelDoc>(s => s
        .RequestConfiguration(r => r
            // 官方文檔:https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/logging-with-on-request-completed.html
            // 在生產環境中運行應用程式時,您可能不希望為所有請求都禁用直接流傳輸,因為這樣做會由於在記憶體中緩存請求和響應位元組而產生性能開銷。
            // 然而,在臨時需要時捕獲請求和響應可能是有用的,比如為了排查生產環境中的問題。
            // 為此,可以針對每個請求單獨啟用 DisableDirectStreaming 功能。
            // 利用此功能,可以在 OnRequestCompleted 中配置一個通用的日誌記錄機制,並僅在必要時記錄請求和響應信息。
            .DisableDirectStreaming() // 僅針對此請求禁用直接流
        )
        .From(0)
        .Size(2)
        .Query(q => q
                .Match(m => m
                .Field(f => f.city)
                .Query("上海")
                )
        )
    );

    // 每個響應都包含一個DebugInformation屬性
    // 訪問DebugInformation屬性來獲取調試信息
    string debugInfo = searchResponse.DebugInformation;
}

當你使用 Elasticsearch.Net 和 NEST 客戶端庫與 Elasticsearch 伺服器進行交互時,每個響應對象都包含一個 DebugInformation 屬性,該屬性提供了有關請求和響應的詳細信息,以幫助你進行調試和故障排除。通過配置 ConnectionSettings 和 RequestConfiguration 上的屬性,你可以控制哪些額外的信息被包含在調試信息中。這些控制可以針對所有請求統一設置,或者針對每個請求單獨設置。

具體來說:

  • ConnectionSettings:允許你在客戶端初始化時全局設置調試信息的詳細程度。例如,你可以決定是否包含請求正文、響應正文或是時間戳等信息。
  • RequestConfiguration:提供了更細粒度的控制,使得你能夠為特定的請求覆蓋全局設置,添加或移除某些調試信息項目。這意味著對於某個特別關註的請求,你可以增加更多的調試細節,而不影響其他請求的輸出。

最後我們來看一下 searchResponse.DebugInformation 輸出的具體內容,如下所示:

Valid NEST response built from a successful (200) low level call on POST: /hotel/_search?pretty=true&error_trace=true&typed_keys=true
# Audit trail of this API call:
 - [1] SniffOnStartup: Took: 00:00:00.2151884
 - [2] SniffSuccess: Node: http://localhost:9200/ Took: 00:00:00.2027398
 - [3] PingSuccess: Node: http://127.0.0.1:9200/ Took: 00:00:00.0043129
 - [4] HealthyResponse: Node: http://127.0.0.1:9200/ Took: 00:00:00.0943867
# Request:
{"from":0,"query":{"match":{"city":{"query":"上海"}}},"size":2}
# Response:
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 83,
      "relation" : "eq"
    },
    "max_score" : 0.88342106,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "36934",
        "_score" : 0.88342106,
        "_source" : {
          "id" : 36934,
          "name" : "7天連鎖酒店(上海寶山路地鐵站店)",
          "address" : "靜安交通路40號",
          "price" : 336,
          "score" : 37,
          "brand" : "7天酒店",
          "city" : "上海",
          "starName" : "二鑽",
          "business" : "四川北路商業區",
          "location" : "31.251433, 121.47522",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg",
          "suggestion" : [
            "7天酒店",
            "四川北路商業區"
          ]
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "38609",
        "_score" : 0.88342106,
        "_source" : {
          "id" : 38609,
          "name" : "速8酒店(上海赤峰路店)",
          "address" : "廣靈二路126號",
          "price" : 249,
          "score" : 35,
          "brand" : "速8",
          "city" : "上海",
          "starName" : "二鑽",
          "business" : "四川北路商業區",
          "location" : "31.282444, 121.479385",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G2/M00/DF/96/Cii-TFkx0ImIQZeiAAITil0LM7cAALCYwKXHQ4AAhOi377_w200_h200_c1_t0.jpg",
          "suggestion" : [
            "速8",
            "四川北路商業區"
          ]
        }
      }
    ]
  }
}

# TCP states:
  Established: 162
  TimeWait: 14
  SynSent: 4
  CloseWait: 13
  LastAck: 1
  FinWait1: 1

# ThreadPool statistics:
  Worker: 
    Busy: 1
    Free: 32766
    Min: 12
    Max: 32767
  IOCP: 
    Busy: 0
    Free: 1000
    Min: 1
    Max: 1000

三、索引庫操作

對於索引庫操作本人更傾向於使用DSL語句去執行。高級客戶端 NEST 可以通過訪問客戶端上的 .LowLevel 屬性來獲取 Elasticsearch.Net 低級客戶端。

在某些場景下,低級客戶端非常有用,比如你已經有了代表你想發送請求的JSON,此時並不想將其轉換成Fluent API或對象初始化語法,又或者客戶端中存在一個可以通過發送字元串請求或匿名類型來規避的bug。

通過 .LowLevel 屬性使用低級客戶端意味著你可以兼得兩者之長:

  • 利用高級客戶端
  • 在合適的情況下使用低級客戶端,同時充分利用NEST中的所有強類型及其序列化器進行反序列化。

示例:

using Nest;
using Elasticsearch.Net;
using Elasticsearch.Net.Specification.IndicesApi;

namespace TianYaSharpCore.Elasticsearch
{
    /// <summary>
    /// ES幫助類
    /// </summary>
    public class ElasticsearchHelper : IElasticsearchHelper
    {
        /*
            1、高級客戶端 NEST 可以通過訪問客戶端上的 .LowLevel 屬性來獲取 Elasticsearch.Net 低級客戶端。

            2、在某些場景下,低級客戶端非常有用,比如你已經有了代表你想發送請求的JSON,此時並不想將其轉換成Fluent API或對象初始化語法,
               又或者客戶端中存在一個可以通過發送字元串請求或匿名類型來規避的bug。

            3、通過 .LowLevel 屬性使用低級客戶端意味著你可以兼得兩者之長:
                *利用高級客戶端
                *在合適的情況下使用低級客戶端,同時充分利用NEST中的所有強類型及其序列化器進行反序列化。
        */

        private readonly IElasticClientProvider _elasticClientProvider;
        public ElasticsearchHelper(IElasticClientProvider elasticClientProvider)
        {
            _elasticClientProvider = elasticClientProvider;
        }

        #region 索引庫操作

        /// <summary>
        /// 判斷某個索引庫是否存在
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <returns>返回true表示已存在</returns>
        public async Task<bool> IsIndexExistsAsync(string indexName)
        {
            ExistsResponse existsResponse = await _elasticClientProvider.ElasticLinqClient.Indices
                .ExistsAsync(indexName);
            return existsResponse.IsValid && existsResponse.Exists;
        }

        /// <summary>
        /// 創建索引庫
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <param name="dsl">用於創建索引庫的DSL語句</param>
        /// <returns>返回true表示創建索引庫成功</returns>
        public async Task<bool> CreateIndexAsync(string indexName, string dsl)
        {
            // 發送PUT請求到 Elasticsearch 創建索引  
            CreateIndexResponse createIndexResponse = await _elasticClientProvider.ElasticJsonClient.Indices
                .CreateAsync<CreateIndexResponse>(indexName, PostData.String(dsl));
            return createIndexResponse.IsValid && createIndexResponse.Acknowledged;
        }

        /// <summary>
        /// 創建索引庫
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <param name="body">請求數據</param>
        /// <returns>返回true表示創建索引庫成功</returns>
        public async Task<bool> CreateIndexAsync(string indexName, PostData body,
            CreateIndexRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken))
        {
            // 發送PUT請求到 Elasticsearch 創建索引  
            CreateIndexResponse createIndexResponse = await _elasticClientProvider.ElasticJsonClient.Indices
                .CreateAsync<CreateIndexResponse>(indexName, body, requestParameters, ctx);
            return createIndexResponse.IsValid && createIndexResponse.Acknowledged;
        }

        /// <summary>
        /// 修改索引庫(註意:索引庫和mapping一旦創建無法修改,但是可以添加新的欄位。)
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <param name="dsl">用於修改索引庫的DSL語句</param>
        /// <returns>返回true表示修改索引庫成功</returns>
        public async Task<bool> PutMappingAsync(string indexName, string dsl)
        {
            PutMappingResponse putMappingResponse = await _elasticClientProvider.ElasticJsonClient.Indices
                .PutMappingAsync<PutMappingResponse>(indexName, PostData.String(dsl));
            return putMappingResponse.IsValid && putMappingResponse.Acknowledged;
        }

        /// <summary>
        /// 修改索引庫(註意:索引庫和mapping一旦創建無法修改,但是可以添加新的欄位。)
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <param name="body">請求數據</param>
        /// <returns>返回true表示修改索引庫成功</returns>
        public async Task<bool> PutMappingAsync(string indexName, PostData body,
            PutMappingRequestParameters requestParameters = null, CancellationToken ctx = default(CancellationToken))
        {
            PutMappingResponse putMappingResponse = await _elasticClientProvider.ElasticJsonClient.Indices
                .PutMappingAsync<PutMappingResponse>(indexName, body, requestParameters, ctx);
            return putMappingResponse.IsValid && putMappingResponse.Acknowledged;
        }

        /// <summary>
        /// 刪除索引庫
        /// </summary>
        /// <param name="indexName">索引庫名稱</param>
        /// <returns>返回true表示刪除索引庫成功</returns>
        public async Task<bool> DeleteIndexAsync(string indexName)
        {
            DeleteIndexResponse deleteIndexResponse = await _elasticClientProvider.ElasticLinqClient.Indices
                .DeleteAsync(indexName);
            return deleteIndexResponse.IsValid && deleteIndexResponse.Acknowledged;
        }

        #endregion

        #region 文檔操作

        /// <summary>
        /// 獲取文檔
        /// </summary>
        /// <typeparam name="TDocument">索引庫對應的文檔類型</typeparam>
        /// <param name="documentId">文檔Id</param>
        /// <returns></returns>
        public async Task<GetResponse<TDocument>> GetAsync<TDocument>(DocumentPath<TDocument> documentId,
            Func<GetDescriptor<TDocument>, IGetRequest> selector = null, CancellationToken ct = default(CancellationToken))
            where TDocument : class
        {
            return await _elasticClientProvider.ElasticLinqClient.GetAsync(documentId, selector, ct);
        }

        /// <summary>
        /// 新增文檔或全量修改文檔
        /// </summary>
        /// <typeparam name="TDocument">索引庫對應的文檔類型</typeparam>
        /// <param name="document">文檔</param>
        /// <returns>返回true表示操作成功</returns>
        public async Task<bool> IndexDocumentAsync<TDocument>(TDocument document, CancellationToken ct = de

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

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 在之前的文章中我們有介紹過SpringAI這個項目。SpringAI 是Spring 官方社區項目,旨在簡化 Java AI 應用程式開發, 讓 Java 開發者想使用 Spring 開發普通應用一樣開發 AI 應用。 而SpringAI 主要面向的是國外的各種大模型接入,對於國內開發者可 ...
  • 目錄一、背景介紹1.1 爬取目標1.2 演示視頻1.3 軟體說明二、代碼講解2.1 調用API-搜索介面2.2 調用API-詳情介面2.3 API_KEY說明2.4 軟體界面模塊2.5 日誌模塊三、轉載聲明 一、背景介紹 1.1 爬取目標 用Python獨立開發了一款爬蟲軟體,作用是:通過搜索關鍵詞 ...
  • 大屏設置網卡開啟熱點後,經常收到反饋,手機端無法搜索到大屏熱點、或者手機連接大屏熱點失敗 這類問題一般有以下幾類情況: 1. 物理網卡IP與熱點網卡IP相同 2. 熱點網卡IP,非正常熱點IP(192.168.137.X) 熱點IP我們一般定為192.168.137.X,192.168.137.X是 ...
  • 在日常工作中,有時可能會需要獲取或修改客戶端電腦的系統時間,比如軟體設置了Licence有效期,預計2024-06-01 00:00:00到期,如果客戶手動修改了客戶端電腦時間,往前調整了一年,則軟體就可以繼續使用一年,如此迴圈往複,則Licence將形同虛設。所以有時候需要校驗客戶端電腦時間和服務... ...
  • PDF表單是PDF中的可編輯區域,允許用戶填寫指定信息。當表單填寫完成後,有時候我們可能需要將其設置為不可編輯,以保護表單內容的完整性和可靠性。或者需要從PDF表單中提取數據以便後續處理或分析。 之前文章詳細介紹過如何使用免費Spire.PDF庫通過C# 創建、填寫表單,本文將繼續介紹該免費.NET ...
  • 除了"在操作系統中修改時區信息,然後重啟.NET應用程式,使其生效"之外。如何在不修改操作系統時區的前提下,修改.NET中的預設時區呢? 這是一位 同學兼同事 於5月21日在技術群里問的問題,我當時簡單地研究了一下,就寫出來了。 現在寫文章分享給大家,雖然我覺得這種需求非常小眾,幾乎不會有人用到。 ...
  • 一、需求 為預防gitlab出現故障,每天定時備份,備份完成後把之前的備份文件刪除,備份成功或失敗的時候自動發送郵件提醒,這裡的gitlab為docker部署。 二、備份命令準備 1)備份命令 創建一個 gitlab_auto_backup.sh文件,文件內容 #!/bin/bash # 進入Git ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...
一周排行
    -Advertisement-
    Play Games
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...