使用c#操作elasticsearch8

注意事项

若Es的客户端版本是8.x以下,建议用NEST或者Elasticsearch.Net,这两个包最高只支持7.x系列。

若Es的客户端是8.x以上,则推荐使用Elastic.Clients.Elasticsearch。

注意选择版本的时候最好和客户端版本保持一致。

1

2

创建项目

安装nuget包

创建两个项目,一个WebApi,一个类库项目,其中类库项目安装以下三个包

  • Elastic.Clients.Elasticsearch:版本和本地ES的版本保持一致即可。
  • Microsoft.Extensions.Configuration.Abstractions:8.0.0。
  • Microsoft.Extensions.DependencyInjection.Abstractions:8.0.2。
  • Microsoft.Extensions.Logging.Abstractions:8.0.3。
  • Microsoft.Extensions.Options.ConfigurationExtensions:8.0.0

3

Api项目引用类库项目。

4

类库模块
  1. 创建数据实体类Movie
public class Movie
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Year { get; set; }

    public string Country { get; set; }
}
  1. 创建配置信息实体类
 public class EsOptions
 {
     /// <summary>
     /// es集群连接
     /// </summary>
     public string[] ConnectionUrls { get; set; }

     /// <summary>
     /// es 用户名
     /// </summary>
     public string SecurityUserName { get; set; }

     /// <summary>
     /// es 密码
     /// </summary>
     public string SecurityPassword { get; set; }
 }
  1. 创建ESServiceExtension类文件,将配置节点绑定到实体,然后注册Es操作服务。
public static class ESServiceExtension
{
    public static IServiceCollection AddEsSetup(this IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<EsOptions>(configuration.GetSection(nameof(EsOptions)));
        services.AddSingleton<IESCurd, ESCurd>();
        return services;
    }
}
  1. 创建Es操作接口IESCurd
public interface IESCurd
{
    /// <summary>
    /// 根据文档id获取文档
    /// </summary>
    /// <typeparam name="Document"></typeparam>
    /// <param name="id">文档id</param>
    /// <param name="action"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<GetResponse<Document>> GetByIdAsync<Document>(Id id, Action<GetRequestDescriptor<Document>> action, CancellationToken cancellationToken = default(CancellationToken));

    /// <summary>
    /// 搜索文档
    /// </summary>
    /// <typeparam name="Document"></typeparam>
    /// <param name="action"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<SearchResponse<Document>> SearchAsync<Document>(Action<SearchRequestDescriptor<Document>> action, CancellationToken cancellationToken = default(CancellationToken));


    /// <summary>
    /// 创建文档
    /// </summary>
    /// <typeparam name="Document"></typeparam>
    /// <param name="document">文档对象</param>
    /// <param name="index">索引名称</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<IndexResponse> IndexAsync<Document>(Document document, IndexName index, CancellationToken cancellationToken = default(CancellationToken));

     /// <summary>
     /// 批量创建文档
     /// </summary>
     /// <typeparam name="Document"></typeparam>
     /// <param name="documents">文档对象集合</param>
     /// <param name="index">索引名称</param>
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
     Task<BulkResponse> IndexManyAsync<Document>(IEnumerable<Document> documents, IndexName index, CancellationToken cancellationToken = default(CancellationToken)) where Document : class;
    
    /// <summary>
    /// 更新文档
    /// </summary>
    /// <typeparam name="Document"></typeparam>
    /// <typeparam name="PartialDocument"></typeparam>
    /// <param name="index">索引名称</param>
    /// <param name="id">文档id</param>
    /// <param name="configureRequest"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<UpdateResponse<Document>> UpdateAsync<Document, PartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<Document, PartialDocument>> configureRequest, CancellationToken cancellationToken = default(CancellationToken));

    /// <summary>
    /// 删除文档
    /// </summary>
    /// <typeparam name="Document"></typeparam>
    /// <param name="index">索引名称</param>
    /// <param name="id">文档id</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<DeleteResponse> DeleteAsync<Document>(IndexName index, Id id, CancellationToken cancellationToken = default(CancellationToken));
}
  1. 创建Es操作具体实现类ESCurd
 public class ESCurd : IESCurd
 {
     private ElasticsearchClient client;
     private ILogger<ESCurd> logger;
     public ESCurd(IOptions<EsOptions> options, ILogger<ESCurd> logger)
     {
         if (options.Value == null)
         {
             logger.LogError("es配置参数为null");
             throw new ArgumentNullException(nameof(IOptions<EsOptions>));
         }

         ElasticsearchClientSettings settings = null;

         string[] connIpArray = options.Value.ConnectionUrls;
         string userName = options.Value.SecurityUserName;
         string passWord = options.Value.SecurityPassword;
         int length = connIpArray.Length;

         if (connIpArray.Length == 1)
         {
             settings = new ElasticsearchClientSettings(new Uri(connIpArray[0]))
                                        .ServerCertificateValidationCallback((sender, certificate, chain, errors) => true) //跳过SSL证书验证(仅开发环境使用)
                                        .Authentication(new BasicAuthentication(userName, passWord)); //配置身份验证
         }
         else
         {
             Uri[] nodes = options.Value.ConnectionUrls.Select(x => new Uri(x)).ToArray();
             var pool = new StaticNodePool(nodes);
             settings = new ElasticsearchClientSettings(pool)
                 .ServerCertificateValidationCallback((sender, certificate, chain, errors) => true) //跳过SSL证书验证(仅开发环境使用)
                 .Authentication(new BasicAuthentication(userName, passWord));
         }

         client = new ElasticsearchClient(settings);
     }

     public async Task<DeleteResponse> DeleteAsync<Document>(IndexName index, Id id, CancellationToken cancellationToken)
     {
         return await client.DeleteAsync(index, id, cancellationToken);
     }

     public async Task<GetResponse<Document>> GetByIdAsync<Document>(Id id, Action<GetRequestDescriptor<Document>> action, CancellationToken cancellationToken)
     {
         return await client.GetAsync(id, action, cancellationToken);
     }

     public async Task<IndexResponse> IndexAsync<Document>(Document document, IndexName index, CancellationToken cancellationToken)
     {
         return await client.IndexAsync(document, index, null, cancellationToken);
     }

     public async Task<BulkResponse> IndexManyAsync<Document>(IEnumerable<Document> documents, IndexName index, CancellationToken cancellationToken = default(CancellationToken)) where Document : class
     {
         return await client.IndexManyAsync(documents, index, cancellationToken);
     }

     public async Task<SearchResponse<Document>> SearchAsync<Document>(Action<SearchRequestDescriptor<Document>> action, CancellationToken cancellationToken)
     {
         return await client.SearchAsync(action, cancellationToken);
     }

     public async Task<UpdateResponse<Document>> UpdateAsync<Document, PartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<Document, PartialDocument>> configureRequest, CancellationToken cancellationToken)
     {
         // 创建更新请求描述符
         var descriptor = new UpdateRequestDescriptor<Document, PartialDocument>(index, id);

         // 应用外部配置(这里会包含 Doc 或 Script 等更新内容)
         configureRequest(descriptor);

         return await client.UpdateAsync<Document, PartialDocument>(descriptor, cancellationToken);
     }
Api模块
  1. 添加Es配置节点
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "EsOptions": {
    //es集群连接
    "ConnectionUrls": [ "https://localhost:9200" ],
    //es 用户名
    "SecurityUserName": "elastic",
    //es 密码
    "SecurityPassword": "123456"
  }
}

  1. 配置swagger注释显示服务,调用类库的注册服务
using ElasticSearchService.Core;
using System.Reflection;

namespace ElasticSearchService.Api
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(options =>
                                           {
                                               options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo()
                                   {
                                       Title = "ElasticSearch Demo",
                                       Description = ".net8集成Es",
                                       Version = "v1"
                                       });

                                               string xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Assembly.GetExecutingAssembly().GetName().Name + ".xml");
                                               options.IncludeXmlComments(xmlPath, true);
                                           });

            builder.Services.AddEsSetup(builder.Configuration);

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI(options =>
                                 {
                                     options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
                                 });
            }

            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }
    }
}

  1. 添加文档控制器
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Core.Search;
using ElasticSearchService.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static System.Reflection.Metadata.BlobBuilder;

namespace ElasticSearchService.Api.Controllers
{
    /// <summary>
    /// es文档
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class ESDocumentController : ControllerBase
    {
        private readonly IESCurd eSCurd;
        private readonly ILogger<ESDocumentController> logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="eSCurd"></param>
        /// <param name="logger"></param>
        public ESDocumentController(IESCurd eSCurd, ILogger<ESDocumentController> logger)
        {
            this.eSCurd = eSCurd;
            this.logger = logger;
        }


        /// <summary>
        /// 获取指定文档
        /// </summary>
        /// <param name="id">文档id</param>
        /// <returns></returns>
        [HttpGet("{id}")]
        public async Task<IActionResult> GetAsync([FromRoute] string id)
        {
            var response = await eSCurd.GetByIdAsync<Movie>(id, action => action.Index(nameof(Movie).ToLower()));
            if (response.IsValidResponse)
            {
                return Ok(response.Source);
            }

            return NotFound($"未查询到索引{nameof(Movie).ToLower()}");
        }

        /// <summary>
        /// 分页查询排序获取文档
        /// </summary>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">每页条数</param>
        /// <param name="search">查询内容</param>
        /// <returns></returns>
        [HttpGet("Search")]
        public async Task<IActionResult> SearchAsync([FromQuery] int pageIndex, [FromQuery] int pageSize, [FromQuery] string? search)
        {
            SearchResponse<Movie> response = await eSCurd.SearchAsync<Movie>(action =>
            {
                action.Indices(nameof(Movie).ToLower())
                .From((pageIndex - 1) * pageSize)
                .Size(pageSize);

                if (!string.IsNullOrEmpty(search))
                {
                    action.Query(q => q.Match(m => m.Field(f => f.Country).Query(search)));
                }
                else
                {
                    action.Query(q => q.MatchAll());
                }

                action.Highlight(config =>
                     config
                     .Fields(f =>
                     {
                         f.Add(new Field("country"), new HighlightField()
                         {
                             PreTags = new[] { "<span style='color:red'>" },
                             PostTags = new[] { "</span>" }
                         });
                     }))
                .Sort(sort =>
                {
                    sort.Field(s => s.Id).Doc(s =>
                    {
                        s.Order(SortOrder.Asc);
                    });
                });
            });

            if (response.IsValidResponse)
            {
                List<Movie?> list = response.Hits.Select(item => item.Source).ToList();
                //Movie movie = response.Hits.FirstOrDefault().Source;
                //var hit = response.Hits.FirstOrDefault().Highlight;
                return Ok(list);
            }

            return NotFound();
        }

        /// <summary>
        /// 更新文档
        /// </summary>
        /// <param name="id">文档id</param>
        /// <param name="movie">文档对象</param>
        /// <returns></returns>
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateAsync([FromRoute] string id, [FromBody] Movie movie)
        {
            UpdateResponse<Movie> response = await eSCurd.UpdateAsync<Movie, Movie>(nameof(Movie).ToLower(), id, u => u.Doc(movie));
            if (response.IsValidResponse)
            {
                return Ok("文档更新成功.");
            }
            return NotFound();
        }

        /// <summary>
        /// 创建文档
        /// </summary>
        /// <param name="movie">文档对象</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> IndexAsync([FromBody] Movie movie)
        {
            IndexResponse response = await eSCurd.IndexAsync<Movie>(movie, nameof(Movie).ToLower());
            if (response.IsValidResponse)
            {
                return Ok($"文档创建成功,ID:{response.Id}.");
            }
            return BadRequest();
        }


        /// <summary>
        /// 批量创建文档
        /// </summary>
        /// <param name="movies">文档对象集合</param>
        /// <returns></returns>
        [HttpPost("bulkinsert")]
        public async Task<IActionResult> IndexManyAsync([FromBody] List<Movie> movies)
        {
            BulkResponse response = await eSCurd.IndexManyAsync<Movie>(movies, nameof(Movie).ToLower());
            if (response.IsValidResponse)
            {
                return Ok($"文档批量创建成功.");
            }
            return BadRequest();
        }

        /// <summary>
        /// 删除文档
        /// </summary>
        /// <param name="id">文档id</param>
        /// <returns></returns>
        [HttpDelete("{id}")]
        public async Task<ActionResult> DeleteAsync([FromRoute] string id)
        {
            var response = await eSCurd.DeleteAsync<Movie>(nameof(Movie).ToLower(), id);
            if (response.IsValidResponse)
            {
                return Ok("文档删除成功.");
            }
            return NotFound();
        }
    }
}

  1. 添加索引控制器
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Nodes;
using ElasticSearchService.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace ElasticSearchService.Api.Controllers
{
    /// <summary>
    /// es索引
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class ESIndexController : ControllerBase
    {
        private readonly IESCurd eSCurd;
        private readonly ILogger<ESIndexController> logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="eSCurd"></param>
        /// <param name="logger"></param>
        public ESIndexController(IESCurd eSCurd, ILogger<ESIndexController> logger)
        {
            this.eSCurd = eSCurd;
            this.logger = logger;
        }

        /// <summary>
        /// 创建索引
        /// </summary>
        /// <param name="indexName">索引名称</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> CreateIndexAsync([FromBody] string indexName)
        {

            var response = await eSCurd.CreateIndexAsync(indexName);
            if (response.IsValidResponse)
            {
                return Ok($"索引创建成功,Index:{response.Index}.");
            }
            return BadRequest();
        }

        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="indexName">索引名称</param>
        /// <returns></returns>
        [HttpDelete]
        public async Task<IActionResult> DeleteIndexAsync([FromBody] string indexName)
        {

            var response = await eSCurd.DeleteIndexAsync(indexName);
            if (response.IsValidResponse)
            {
                return Ok($"索引删除成功.");
            }
            return BadRequest();
        }
    }
}

效果预览

5

创建文档

6

7

批量创建文档

8

9

更新文档

10

26

删除文档

11

获取指定文档

12

24

分页获取文档数据

不带查询条件

13

14

23

15

带查询条件:按国家查询

16

17

注意:由于代码用的是match查询,查询的时候中文会被分词处理,所以如果输入“美国”,则查询的结果不仅仅只有美国,带“美”和带“国”的数据都会被查出来,如下图所示。

18

创建索引

19

20

删除索引

21

22

posted @ 2025-10-09 22:40  相遇就是有缘  阅读(4)  评论(0)    收藏  举报