Elasticsearch7.x+Nest升級到Elasticsearch8.x+Elastic.Clients.Elasticsearch分享(.net8)

笨笨熊三号發表於2024-06-27

背景

Elasticsearch升級到8.11了,對應的客戶端外掛也跟著升級,以為會相容Nest的語法,發現自己Too Young Too Simple!
沒辦法,只能去官網找,結果官網只有最基本的增、刪、改、查,只能繼續查資料,發現網上資料很少,避免大家少走彎路,這裡做個簡單的分享。

分享

1.ElasticsearchClient

var esSetting = _baseConfig.Es;
if (esSetting == null || string.IsNullOrEmpty(esSetting.EsHosts))
{
    throw new Exception("未配置EsHosts");
}

//讀取ES叢集配置
var nodeUris = GetAllNodes(esSetting);

services.AddSingleton<ElasticsearchClient>(provider =>
{
    //這裡用的StaticNodePool,單節點和叢集都支援,大家根據自己實際情況選擇
    var pool = new StaticNodePool(nodeUris);
    var settings = new ElasticsearchClientSettings(pool);
    //如果設定了賬號密碼
    if (!string.IsNullOrEmpty(esSetting.EsUser) && !string.IsNullOrEmpty(esSetting.EsPassword))
    {
        settings.Authentication(new BasicAuthentication(esSetting.EsUser, esSetting.EsPassword));
    }
    return new ElasticsearchClient(settings);
});

return services;
//settings.EsHosts示例:http://192.168.1.100:9200;http://192.168.1.101:9200;http://192.168.1.102:9200
private static List<Uri> GetAllNodes(EsConfig settings)
{
    var nodes = settings.EsHosts.Trim();
    string[] nodeshost = nodes.Split(';');
    var nodUris = new List<Uri>();
    for (int i = 0; i < nodeshost.Length; i++)
    {
        if (!string.IsNullOrEmpty(nodeshost[i]))
            nodUris.Add(new Uri(nodeshost[i]));
    }
    return nodUris;
}

2.具體使用

2.1注入_client

public class ElasticSearchForInformalEssay : IElasticSearchForInformalEssay
{
    private readonly string IndexName = "informal_essay_info";

    private readonly ILogger<ElasticSearchForInformalEssay> _logger;
    protected ElasticsearchClient _client { get; set; }
    public ElasticSearchForInformalEssay(ILogger<ElasticSearchForInformalEssay> logger, ElasticsearchClient client)
    {
        _logger = logger;
        _client = client;
    }
}

2.2增加-增加跟Nest版本基本一致

/// <summary>
/// 新增單條資料
/// </summary>
/// <param name="InformalEssay"></param>
/// <returns></returns>
public OutPutResult AddIndex(informal_essay_info InformalEssay)
{
    try
    {
        var resp = _client.Index<informal_essay_info>(InformalEssay, x => x.Index(IndexName));

        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// 新增多條資料
/// </summary>
/// <param name="InformalEssays"></param>
/// <returns></returns>
public OutPutResult BulkAddIndex(List<informal_essay_info> InformalEssays)
{
    try
    {
        //var resp = _client.BulkAll<informal_essay_info>(InformalEssays, x => x.Index(IndexName).Size(1000));

        var observableBulk = _client.BulkAll(InformalEssays, f => f
                .MaxDegreeOfParallelism(8)
                .BackOffTime(TimeSpan.FromSeconds(10))
                .BackOffRetries(2)
                .Size(1000)
                .RefreshOnCompleted()
                .Index(IndexName)
                .BufferToBulk((r, buffer) => r.IndexMany(buffer))
            );
        var countdownEvent = new CountdownEvent(1);
        var bulkAllObserver = new BulkAllObserver(
        onNext: response =>
        {
            //_logger.LogInformation($"索引 {response.Page} 頁,每頁1000條,重試 {response.Retries} 次");
        },
        onError: ex =>
        {
            _logger.LogInformation("新增索引異常 : {0}", ex);
            countdownEvent.Signal();
        },
        () =>
        {
            _logger.LogInformation("新增索引完成");
            countdownEvent.Signal();
        });

        observableBulk.Subscribe(bulkAllObserver);


        //var resp = _client.IndexMany(InformalEssays, IndexName);
        return OutPutResult.ok();

    }
    catch (Exception ex)
    {
        _logger.LogInformation("批次操作異常:", ex);
        return OutPutResult.error(ex.Message);
    }
}

2.3刪除

/// <summary>
/// Id單條刪除
/// </summary>
/// <param name="InformalEssayId"></param>
/// <returns></returns>
public OutPutResult DeleteById(string InformalEssayId)
{
    try
    {
        var resp = _client.Delete<informal_essay_info>(InformalEssayId, x => x.Index(IndexName));

        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// Ids批次刪除
/// </summary>
/// <param name="InformalEssayIds"></param>
/// <returns></returns>
public OutPutResult BulkDeleteById(string InformalEssayIds)
{
    try
    {
        var ids = CommonHelpers.GetStringArray(InformalEssayIds).ToList();

        List<FieldValue> terms = new();

        ids.ForEach(x =>
        {
            terms.Add(x);
        });

        var resp = _client.DeleteByQuery<informal_essay_info>(IndexName, x => x.
        Query(q => q.Terms(t => t.Field(f => f.id).Terms(new TermsQueryField(terms))))
        .Refresh(true)
        );

        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// 根據條件批次刪除
/// </summary>
/// <param name="keyword"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <returns></returns>
public OutPutResult BulkDeleteByQuery(string keyword, DateTime? start, DateTime? end)
{
    try
    {
        var mustQueries = new List<Action<QueryDescriptor<informal_essay_info>>>();

        if (start.HasValue || end.HasValue)
        {
            long startDate = CommonHelpers.DateTimeToTimeStamp(start ?? DateTime.MinValue);
            long endDate = CommonHelpers.DateTimeToTimeStamp(end ?? DateTime.MaxValue);

            mustQueries.Add(t => t.Range(r => r.NumberRange(n => n.Field(f => f.setdate).Gte(startDate).Lte(endDate))));
        }

        if (!string.IsNullOrEmpty(keyword))
        {
            mustQueries.Add(t => t.MultiMatch(m => m.Fields(
                new Field("title", 10).And(new Field("content", 5)).And(new Field("author", 5))
                ).Operator(Operator.And).Query(keyword)));
        }

        Action<QueryDescriptor<informal_essay_info>> retQuery = q => q.Bool(b => b.Filter(mustQueries.ToArray()));

        var resp = _client.DeleteByQuery<informal_essay_info>(IndexName, s => s
           .Query(q => q.Bool(b => b.Must(retQuery)))
       );

        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

2.4修改

public OutPutResult UpdateIndex(informal_essay_info InformalEssay)
{
    try
    {
        var resp = _client.Index<informal_essay_info>(InformalEssay, x => x.Index(IndexName).Refresh(Refresh.True));
        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// 批次修改
/// </summary>
/// <param name="InformalEssays"></param>
/// <returns></returns>
public OutPutResult BulkUpdateIndex(List<informal_essay_info> InformalEssays)
{
    try
    {
        var observableBulk = _client.BulkAll(InformalEssays, f => f
                .MaxDegreeOfParallelism(8)
                .BackOffTime(TimeSpan.FromSeconds(10))
                .BackOffRetries(2)
                .Size(1000)
                .RefreshOnCompleted()
                .Index(IndexName)
                .BufferToBulk((r, buffer) => r.IndexMany(buffer))
            );

        var bulkAllObserver = new BulkAllObserver(
        onNext: response =>
        {
            _logger.LogInformation($"索引 {response.Page} 頁,每頁1000條,重試 {response.Retries} 次");
        },
        onError: ex =>
        {
            _logger.LogInformation("更新索引異常 : {0}", ex);
        },
        () =>
        {
            _logger.LogInformation("完成更新索引");
        });

        observableBulk.Subscribe(bulkAllObserver);

        return OutPutResult.ok();
    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// 根據條件修改(稿件釋出/取消釋出後,更新ES)
/// </summary>
/// <param name="id"></param>
/// <param name="status">1:釋出,0:取消釋出</param>
/// <returns></returns>
public OutPutResult UpdateInformalEssayInfoAfterPublishOrCancel(string id, int status = 1)
{
    try
    {
        var sql = $"ctx._source.status = params.status;";

        var scriptParams = new Dictionary<string, object> { { "status", status } };
        var req = new UpdateByQueryRequest(IndexName)
        {
            Query = new TermQuery("id") { Field = "id", Value = id },
            Script = new Script(new InlineScript() { Source = sql, Params = scriptParams }),
            Refresh = true
        };

        var resp = _client.UpdateByQuery(req);

        if (resp.IsValidResponse)
        {
            return OutPutResult.ok();
        }
        else
        {
            return OutPutResult.error(resp.DebugInformation);
        }

    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "稿件釋出/取消釋出後,更新ES:");
        return OutPutResult.error("稿件釋出/取消釋出後,更新ES:" + ex.Message);
    }
}

2.5查詢

/// <summary>
/// id單條查詢
/// </summary>
/// <param name="InformalEssayId"></param>
/// <returns></returns>
public OutPutResult SerachById(string InformalEssayId)
{
    try
    {
        var search = _client.Get<informal_essay_info>(InformalEssayId, s => s.Index(IndexName));

        var data = search.Source;

        return OutPutResult.ok().put("data", data);

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}

/// <summary>
/// ids多條查詢
/// </summary>
/// <param name="InformalEssayIds"></param>
/// <returns></returns>
public OutPutResult SerachByIds(string InformalEssayIds)
{
    try
    {
        var ids = CommonHelpers.GetStringArray(InformalEssayIds).ToList();
        List<FieldValue> terms = new();

        ids.ForEach(x =>
        {
            terms.Add(x);
        });
        var search = _client.Search<informal_essay_info>(s => s.Index(IndexName).Query(
            q => q.Terms(t => t.Field(f => f.id).Terms(new TermsQueryField(terms)))
            ));

        var data = search.Documents.ToList();

        return OutPutResult.ok().put("data", data);

    }
    catch (Exception ex)
    {
        return OutPutResult.error(ex.Message);
    }
}


/// <summary>
/// 列表分頁查詢
///(不要說深度分頁效能問題,使用者說不用管,哈哈,就要全部查出來,慢點沒關係)
/// 其實嘗試過幾種方案,具體使用人員就覺得直接分頁最方便,最後直接max_result_window設定了1千萬
/// (使用者800萬左右的資料,三臺16核32GB的伺服器部署的ES叢集+Redis叢集,深度分頁1秒多,完全能接受)
/// </summary>
/// <param name="category"></param>
/// <param name="keyword"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="pageindex"></param>
/// <param name="rows"></param>
/// <param name="orderField"></param>
/// <param name="order"></param>
/// <returns></returns>
public OutPutResult SerachInformalEssays(string category, string keyword, DateTime? start, DateTime? end, int pageindex = 1, int rows = 20, int string orderField = "setdate", SortOrder order = SortOrder.Desc)
{
    try
    {
        var mustQueries = new List<Action<QueryDescriptor<informal_essay_info>>>();

        //狀態,0為正常,-1為刪除,1為釋出
        mustQueries.Add(t => t.Term(f => f.status, 1));

        if (!string.IsNullOrEmpty(category))
        {
            mustQueries.Add(t => t.MatchPhrase(f => f.Field(fd => fd.category).Query(category)));
        }

        if (!string.IsNullOrEmpty(keyword))
        {
            mustQueries.Add(t => t.MultiMatch(m => m.Fields(
                new Field("title", 10).And(new Field("content", 5)).And(new Field("author", 5)).And(new Field("keyword", 5))
                ).Operator(Operator.And).Query(keyword)));

        }

        if (start.HasValue || end.HasValue)
        {
            long startDate = CommonHelpers.DateTimeToTimeStamp(start ?? DateTime.MinValue);
            long endDate = 0;
            if (end != null && end.HasValue)
            {
                string endtime = string.Format("{0:d}", end) + " 23:59:59";
                end = Convert.ToDateTime(endtime);
                endDate = CommonHelpers.DateTimeToTimeStamp(end ?? DateTime.MaxValue);
            }
            else
            {
                endDate = CommonHelpers.DateTimeToTimeStamp(end ?? DateTime.MaxValue);
            }

            mustQueries.Add(t => t.Range(r => r.NumberRange(n => n.Field("setdate").Gte(startDate).Lte(endDate))));
        }

        var search = _client.Search<informal_essay_info>(s => s
             .Index(IndexName)
             .Query(q => q.Bool(b => b.Must(mustQueries.ToArray())))
             .From((pageindex - 1) * rows)
             .Size(rows)
             .Sort(s => s.Field(orderField, new FieldSort { Order = order }))
         );
        return OutPutResult.ok().put("total", search.Total).put("data", search.Documents);

    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "查詢隨筆異常:");
        return OutPutResult.error(ex.Message);
    }
}

3.最後

有同事問為什麼不升級到最新版本,生產環境,用最新版本,嘿嘿,懂的都懂,除非一定要用什麼特性,就這樣,有問題大家補充

相關文章