使用c#操作elasticsearch8
注意事项
若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/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();
}
}
}
- 添加文档控制器
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查询,查询的时候中文会被分词处理,所以如果输入“美国”,则查询的结果不仅仅只有美国,带“美”和带“国”的数据都会被查出来,如下图所示。
创建索引
删除索引
人生如逆旅
我亦是行人