注意事项
若Es的客户端版本是8.x以下,建议用NEST或者Elasticsearch.Net,这两个包最高只支持7.x系列。
若Es的客户端是8.x以上,则推荐使用Elastic.Clients.Elasticsearch。
注意选择版本的时候最好和客户端版本保持一致。
创建项目
安装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
Api项目引用类库项目。
类库模块
- 创建数据实体类Movie
public class Movie
{public int Id { get; set; }public string Name { get; set; }public int Year { get; set; }public string Country { get; set; }
}
- 创建配置信息实体类
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; }}
- 创建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;}
}
- 创建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));
}
- 创建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模块
- 添加Es配置节点
{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*","EsOptions": {//es集群连接"ConnectionUrls": [ "https://localhost:9200" ],//es 用户名"SecurityUserName": "elastic",//es 密码"SecurityPassword": "123456"}
}
- 配置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/swashbucklebuilder.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();}}
}
- 添加文档控制器
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();}}
}
- 添加索引控制器
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();}}
}
效果预览
创建文档
批量创建文档
更新文档
删除文档
获取指定文档
分页获取文档数据
不带查询条件
带查询条件:按国家查询
注意:由于代码用的是match查询,查询的时候中文会被分词处理,所以如果输入“美国”,则查询的结果不仅仅只有美国,带“美”和带“国”的数据都会被查出来,如下图所示。