Github: https://github.com/elastic/elasticsearch-net
官方文档: https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html
其他: Elasticsearch .net client NEST 5.x 使用总结
示例说明
本示例了解如何使用 .NET 客户端执行各种基本的Elasticsearch CRUD 操作。演示了如何通过将对象索引到 Elasticsearch、读回文档、通过 ID 检索文档或执行搜索、更新文档中的某个字段以及删除特定文档来创建文档。
创建项目
创建一个名为ES.NET.Client.Demo的Web API项目,目录结构如下
安装 .NET 客户端
Install-Package Elastic.Clients.Elasticsearch -IncludePrerelease
公共部分
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSetting": {
"SqlServerSetting": {
"Connection": "Server=192.168.3.40;Database=webdemo;User=sa;Password=longfuchu;"
},
"MysqlSetting": {
"Connection": "server=192.168.3.40;port=3306;user=root;password=123456;database=webdemo;charset=utf8;Allow Zero Datetime=true;sslmode=none;Old Guids=true;Allow User Variables=True"
},
"PostgreSqlSetting": {
"Connection": "HOST=192.168.3.40;PORT=5432;DATABASE=test_db;USER ID=postgres;PASSWORD=123456;Pooling = false"
},
"RedisSetting": {
"Connection": "192.168.3.40:6379,password=",
"Database": "test"
},
"RabbitMQSetting": {
"HostName": "localhost",
"UserName": "admin",
"Password": "admin",
"Port": 5672
},
"MongoDbSetting": {
"Connection": "mongodb://192.168.3.40:2717", ///?replicaSet=rs0
"Database": "test"
},
"KafkaSetting": {
"Servers": "kafka1:9091,kafka2:9092,kafka3:9093"
},
"ElasticSetting": {
"Uri": "http://192.168.3.40:9200",
"Fingerprint": "14:0C:36:4D:47:5F:23:23:FF:30:FD:92:9D:25:C2:1B:F5:56:61:73:56:C5:5F:45:F9:B8:E7:D5:40:BB:65:59",
"CloudId": "",
"ApiKey": "ZpNle_iRTSmSzk7iAln0DQ",
"UserName": "elastic",
"Password": "F3SXEhBp3Jysfd9OyhJX",
"IndexSetting": {
"TweetIndex": "my-tweet-index",
"BOutIndex": "mssql_blood_boutitem"
}
}
}
}
namespace ES.NET.Client.Demo.Models;
/// <summary>
/// mongodb setting
/// </summary>
public class AppSetting
{
/// <summary>
/// SqlServerSetting
/// </summary>
public SqlServerSetting SqlServerSetting { get; set; }
/// <summary>
/// MysqlSetting
/// </summary>
public MysqlSetting MysqlSetting { get; set; }
/// <summary>
/// RedisSetting
/// </summary>
public RedisSetting RedisSetting { get; set; }
/// <summary>
/// RabbitMQSetting
/// </summary>
public RabbitMQSetting RabbitMQSetting { get; set; }
/// <summary>
/// MongoDbSetting
/// </summary>
public MongoDbSetting MongoDbSetting { get; set; }
/// <summary>
/// KafkaSetting
/// </summary>
public KafkaSetting KafkaSetting { get; set; }
/// <summary>
/// ElasticSetting
/// </summary>
public ElasticSetting ElasticSetting { get; set; }
}
public class RabbitMQSetting
{
/// <summary>
/// HostName
/// </summary>
public string HostName { get; set; } = "localhost";
/// <summary>
/// Password
/// </summary>
public string Password { get; set; }
/// <summary>
/// Username
/// </summary>
public string UserName { get; set; }
/// <summary>
/// The port to connect on.
/// </summary>
public int Port { get; set; } = 5672;
}
public class RedisSetting
{
/// <summary>
/// connection string
/// </summary>
public string Connection { get; set; }
/// <summary>
/// database name
/// </summary>
public string Database { get; set; }
}
public class MongoDbSetting
{
/// <summary>
/// connection string
/// </summary>
public string Connection { get; set; }
/// <summary>
/// database name
/// </summary>
public string Database { get; set; }
}
public class SqlServerSetting
{
/// <summary>
/// connection string
/// </summary>
public string Connection { get; set; }
}
public class MysqlSetting
{
/// <summary>
/// connection string
/// </summary>
public string Connection { get; set; }
}
public class KafkaSetting
{
/// <summary>
/// Servers
/// </summary>
public string Servers { get; set; }
}
public class ElasticSetting
{
/// <summary>
/// Uri
/// </summary>
public string Uri { get; set; }
public string Fingerprint { get; set; }
public string CloudId { get; set; }
public string ApiKey { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public IndexSetting IndexSetting { get; set; }
}
public class IndexSetting {
public string TweetIndex { get; set; }
public string BOutIndex { get; set; }
}
using Microsoft.AspNetCore.Mvc;
namespace ES.NET.Client.Demo.Controllers;
/// <summary>
/// 基础控制器
/// </summary>
[Route("api/[area]/[controller]/[action]")]
[ApiController]
public abstract class BaseController : ControllerBase
{
}
using ES.NET.Client.Demo.Controllers;
using Microsoft.AspNetCore.Mvc;
namespace ES.NET.Client.Demo.Controllers.ES;
/// <summary>
/// 域控制器
/// </summary>
[Area("ES")]
public abstract class AreaController : BaseController
{
}
using ES.NET.Client.Demo.Models;
using ES.NET.Client.Demo.Services;
using System;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var appSetting = new AppSetting();
builder.Configuration.GetSection("AppSetting").Bind(appSetting);
//把AppSetting实体注入到容器,方便在构造函数里使用IOptionsSnapshot<AppSetting> options
builder.Services.Configure<AppSetting>(builder.Configuration.GetSection("AppSetting"));
//[FromServices] ESClientConnHelp esClientConnHelp
builder.Services.AddScoped<IESClientConnHelp,ESClientConnHelp>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
初始化ESClient
using Elastic.Clients.Elasticsearch;
namespace ES.NET.Client.Demo.Services;
public abstract class IElasticSearchHelper
{
public ElasticsearchClient ESClient { get; set; }
}
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;
using ES.NET.Client.Demo.Models;
using Microsoft.Extensions.Options;
namespace ES.NET.Client.Demo.Services;
public class ElasticSearchHelper: IElasticSearchHelper
{
//ES连接
/// <summary>
/// ElasticSearchConn
/// builder.Services.Configure<AppSetting>(builder.Configuration.GetSection("AppSetting"));
/// </summary>
/// <param name="options"></param>
public ElasticSearchHelper(IOptionsSnapshot<AppSetting> options)
{
if (options == null || options.Value == null)
throw new ArgumentNullException(nameof(options));
if (this.ESClient == null)
{
ElasticSetting elasticSetting = options.Value.ElasticSetting;
var settings = new ElasticsearchClientSettings(new Uri(elasticSetting.Uri))
.CertificateFingerprint(elasticSetting.Fingerprint)
.Authentication(new BasicAuthentication(elasticSetting.UserName, elasticSetting.Password));
//.Authentication(new ApiKey(elasticSetting.ApiKey));
this.ESClient = new ElasticsearchClient(settings);
}
}
}
简单示例
新建一个实体Tweet和控制器TweetController
namespace ES.NET.Client.Demo.Models;
public class Tweet
{
public int Id { get; set; }
public string User { get; set; }
public DateTime PostDate { get; set; }
public string Message { get; set; }
}
创建索引
/// <summary>
/// 创建索引
/// </summary>
/// <param name="tweet"></param>
/// <returns></returns>
[HttpPut]
public async Task<IActionResult> IndexAsync(Tweet entity)
{
if (entity == null || entity.Id <= 0) return NoContent();
var response = await esClient.IndexAsync(entity, request => request.Index(elasticSetting.IndexSetting.TweetIndex));
if (response.IsValid)
{
Console.WriteLine($"Index document with ID {response.Id} succeeded.");
}
return Ok(response);
}
获取文档
/// <summary>
/// 获取文档
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetAsync(string id)
{
var response = await esClient.GetAsync<Tweet>(id, idx => idx.Index(elasticSetting.IndexSetting.TweetIndex));
var entity = response.Source;
return Ok(entity);
}
使用弹性搜索 JSON 响应进行 1 对 1 映射。GetResponse 原始文档被反序列化为 Tweet 类的实例,可通过属性在响应上访问。Source
搜索文档
/// <summary>
/// 搜索文档(lambda方式)
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> SearchAsync(string userName)
{
var response = await esClient.SearchAsync<Tweet>(s => s
.Index(elasticSetting.IndexSetting.TweetIndex)
.From(0)
.Size(10)
.Query(q => q
.Term(t => t.User, userName)
)
);
if (response.IsValid)
{
var tweet = response.Documents.FirstOrDefault();
return Ok(tweet);
}
else
{
return NotFound();
}
}
泛型类型参数指定类,该类在反序列化响应中的命中时使用。Tweet 如果在 上配置了 索引,或者在映射此类型时配置了特定索引,则可以省略该索引。DefaultIndexElasticsearchClientSettings 针对该字段执行术语查询,搜索由用户 stevejgordon 撰写的推文。user 可通过 上的 collection 属性访问与查询匹配的文档。DocumentsSearchResponse
如果 lambda 不是您的对象,您可能更喜欢对请求使用对象初始值设定项语法
/// <summary>
/// 搜索文档(实体对象方式)
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> SearchRequestAsync(string userName)
{
var request = new SearchRequest(elasticSetting.IndexSetting.TweetIndex)
{
From = 0,
Size = 10,
Query = new TermQuery("user") { Value = userName }
};
var response = await esClient.SearchAsync<Tweet>(request);
if (response.IsValid)
{
var entity = response.Documents.FirstOrDefault();
return Ok(entity);
}
else
{
return NotFound();
}
}
更新文档
/// <summary>
/// 更新文档
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> UpdateAsync(Tweet entity)
{
if (entity == null || entity.Id <= 0) return NoContent();
var response = await esClient.UpdateAsync<Tweet, object>(elasticSetting.IndexSetting.TweetIndex, entity.Id.ToString(), u => u
.Doc(entity));
if (response.IsValid)
{
Console.WriteLine("Update document succeeded.");
}
return Ok(response);
}
更新现有推文实例上的属性。
在更新请求中发送更新的推文对象。
删除文档
可以通过提供要删除的文档的 ID 来删除文档。
/// <summary>
/// 删除文档
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> DeleteAsync(string id)
{
if (string.IsNullOrWhiteSpace(id)) return NoContent();
var response = await esClient.DeleteAsync(elasticSetting.IndexSetting.TweetIndex, id);
if (response.IsValid)
{
Console.WriteLine("Delete document succeeded.");
}
return Ok(response);
}
完整TweetController
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;
using ES.NET.Client.Demo.Models;
using ES.NET.Client.Demo.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
namespace ES.NET.Client.Demo.Controllers.ES;
/// <summary>
/// 测试索引控制器
/// </summary>
public class TweetController : AreaController
{
private readonly ElasticSetting elasticSetting;
private readonly ElasticsearchClient esClient;
public TweetController(IOptionsSnapshot<AppSetting> options,IElasticSearchHelper esClientConnHelp)
{
if (options == null || options.Value == null)
throw new ArgumentNullException(nameof(options));
if(esClientConnHelp.ESClient==null)
throw new ArgumentNullException(nameof(esClientConnHelp));
elasticSetting = options.Value.ElasticSetting;
esClient = esClientConnHelp.ESClient;
}
/// <summary>
/// 创建索引
/// </summary>
/// <param name="tweet"></param>
/// <returns></returns>
[HttpPut]
public async Task<IActionResult> IndexAsync(Tweet entity)
{
if (entity == null || entity.Id <= 0) return NoContent();
var response = await esClient.IndexAsync(entity, request => request.Index(elasticSetting.IndexSetting.TweetIndex));
if (response.IsValid)
{
Console.WriteLine($"Index document with ID {response.Id} succeeded.");
}
return Ok(response);
}
/// <summary>
/// 获取文档
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetAsync(string id)
{
var response = await esClient.GetAsync<Tweet>(id, idx => idx.Index(elasticSetting.IndexSetting.TweetIndex));
var entity = response.Source;
return Ok(entity);
}
/// <summary>
/// 搜索文档(lambda方式)
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> SearchAsync(string userName)
{
var response = await esClient.SearchAsync<Tweet>(s => s
.Index(elasticSetting.IndexSetting.TweetIndex)
.From(0)
.Size(10)
.Query(q => q
.Term(t => t.User, userName)
)
);
if (response.IsValid)
{
var tweet = response.Documents.FirstOrDefault();
return Ok(tweet);
}
else
{
return NotFound();
}
}
/// <summary>
/// 搜索文档(实体对象方式)
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> SearchRequestAsync(string userName)
{
var request = new SearchRequest(elasticSetting.IndexSetting.TweetIndex)
{
From = 0,
Size = 10,
Query = new TermQuery("user") { Value = userName }
};
var response = await esClient.SearchAsync<Tweet>(request);
if (response.IsValid)
{
var entity = response.Documents.FirstOrDefault();
return Ok(entity);
}
else
{
return NotFound();
}
}
/// <summary>
/// 更新文档
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> UpdateAsync(Tweet entity)
{
if (entity == null || entity.Id <= 0) return NoContent();
var response = await esClient.UpdateAsync<Tweet, object>(elasticSetting.IndexSetting.TweetIndex, entity.Id.ToString(), u => u
.Doc(entity));
if (response.IsValid)
{
Console.WriteLine("Update document succeeded.");
}
return Ok(response);
}
/// <summary>
/// 删除文档
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> DeleteAsync(string id)
{
if (string.IsNullOrWhiteSpace(id)) return NoContent();
var response = await esClient.DeleteAsync(elasticSetting.IndexSetting.TweetIndex, id);
if (response.IsValid)
{
Console.WriteLine("Delete document succeeded.");
}
return Ok(response);
}
}