Added .net clients (#833)
* Added .net clients Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> * Changed OSC to OpenSearch.Client, updated links Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> * Incorporated tech review comments Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> * Formatting and typo fix Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> * Implemented docs review comments Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> * Implemented editorial comments Signed-off-by: Fanit Kolchina <kolchfa@amazon.com> Signed-off-by: Fanit Kolchina <kolchfa@amazon.com>
This commit is contained in:
parent
b6bcce916c
commit
d8b383b53e
|
@ -0,0 +1,329 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Getting started with the high-level .NET client
|
||||||
|
nav_order: 10
|
||||||
|
has_children: false
|
||||||
|
parent: .NET clients
|
||||||
|
---
|
||||||
|
|
||||||
|
# Getting started with the high-level .NET client (OpenSearch.Client)
|
||||||
|
|
||||||
|
OpenSearch.Client is a high-level .NET client. It provides strongly typed requests and responses as well as Query DSL. It frees you from constructing raw JSON requests and parsing raw JSON responses by providing models that parse and serialize/deserialize requests and responses automatically. OpenSearch.Client also exposes the OpenSearch.Net low-level client if you need it.
|
||||||
|
|
||||||
|
This getting started guide illustrates how to connect to OpenSearch, index documents, and run queries.
|
||||||
|
|
||||||
|
## Installing OpenSearch.Client
|
||||||
|
|
||||||
|
To install OpenSearch.Client, download the [OpenSearch.Client NuGet package](https://www.nuget.org/packages/OpenSearch.Client) and add it to your project in an IDE of your choice. In Microsoft Visual Studio, follow the steps below:
|
||||||
|
- In the **Solution Explorer** panel, right-click on your solution or project and select **Manage NuGet Packages for Solution**.
|
||||||
|
- Search for the OpenSearch.Client NuGet package, and select **Install**.
|
||||||
|
|
||||||
|
Alternatively, you can add OpenSearch.Client to your .csproj file:
|
||||||
|
```xml
|
||||||
|
<Project>
|
||||||
|
...
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenSearch.Client" Version="1.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The following example illustrates connecting to OpenSearch, indexing documents, and sending queries on the data. It uses the Student class to represent one student, which is equivalent to one document in the index.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class Student
|
||||||
|
{
|
||||||
|
public int Id { get; init; }
|
||||||
|
public string FirstName { get; init; }
|
||||||
|
public string LastName { get; init; }
|
||||||
|
public int GradYear { get; init; }
|
||||||
|
public double Gpa { get; init; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, OpenSearch.Client uses camel case to convert property names to field names.
|
||||||
|
{: .note}
|
||||||
|
|
||||||
|
## Connecting to OpenSearch
|
||||||
|
|
||||||
|
Use the default constructor when creating an OpenSearchClient object to connect to the default OpenSearch host (`http://localhost:9200`).
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var client = new OpenSearchClient();
|
||||||
|
```
|
||||||
|
|
||||||
|
To connect to your OpenSearch cluster through a single node with a known address, specify this address when creating an instance of OpenSearch.Client:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var nodeAddress = new Uri("http://myserver:9200");
|
||||||
|
var client = new OpenSearchClient(nodeAddress);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also connect to OpenSearch through multiple nodes. Connecting to your OpenSearch cluster with a node pool provides advantages like load balancing and cluster failover support. To connect to your OpenSearch cluster using multiple nodes, specify their addresses and create a `ConnectionSettings` object for the OpenSearch.Client instance:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var nodes = new Uri[]
|
||||||
|
{
|
||||||
|
new Uri("http://myserver1:9200"),
|
||||||
|
new Uri("http://myserver2:9200"),
|
||||||
|
new Uri("http://myserver3:9200")
|
||||||
|
};
|
||||||
|
|
||||||
|
var pool = new StaticConnectionPool(nodes);
|
||||||
|
var settings = new ConnectionSettings(pool);
|
||||||
|
var client = new OpenSearchClient(settings);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using ConnectionSettings
|
||||||
|
|
||||||
|
`ConnectionConfiguration` is used to pass configuration options to the low-level OpenSearch.Net client. `ConnectionSettings` inherits from `ConnectionConfiguration` and provides additional configuration options.
|
||||||
|
To set the address of the node and the default index name for requests that don't specify the index name, create a `ConnectionSettings` object:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var node = new Uri("http://myserver:9200");
|
||||||
|
var config = new ConnectionSettings(node).DefaultIndex("students");
|
||||||
|
var client = new OpenSearchClient(config);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Indexing one document
|
||||||
|
|
||||||
|
Create one instance of Student:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var student = new Student { Id = 100, FirstName = "Paulo", LastName = "Santos", Gpa = 3.93, GradYear = 2021 };
|
||||||
|
```
|
||||||
|
|
||||||
|
To index one document, you can use either fluent lambda syntax or object initializer syntax.
|
||||||
|
|
||||||
|
Index this Student into the `students` index using fluent lambda syntax:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var response = client.Index(student, i => i.Index("students"));
|
||||||
|
```
|
||||||
|
Index this Student into the `students` index using object initializer syntax:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var response = client.Index(new IndexRequest<Student>(student, "students"));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Indexing many documents
|
||||||
|
|
||||||
|
You can index many documents from a collection at the same time by using the OpenSearch.Client's `IndexMany` method:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var studentArray = new Student[]
|
||||||
|
{
|
||||||
|
new() {Id = 200, FirstName = "Shirley", LastName = "Rodriguez", Gpa = 3.91, GradYear = 2019},
|
||||||
|
new() {Id = 300, FirstName = "Nikki", LastName = "Wolf", Gpa = 3.87, GradYear = 2020}
|
||||||
|
};
|
||||||
|
|
||||||
|
var manyResponse = client.IndexMany(studentArray, "students");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Searching for a document
|
||||||
|
|
||||||
|
To search for a student indexed above, you want to construct a query that is analogous to the following Query DSL query:
|
||||||
|
|
||||||
|
```json
|
||||||
|
GET students/_search
|
||||||
|
{
|
||||||
|
"query" : {
|
||||||
|
"match": {
|
||||||
|
"lastName": "Santos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The query above is a shorthand version of the following explicit query:
|
||||||
|
|
||||||
|
```json
|
||||||
|
GET students/_search
|
||||||
|
{
|
||||||
|
"query" : {
|
||||||
|
"match": {
|
||||||
|
"lastName": {
|
||||||
|
"query": "Santos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In OpenSearch.Client, this query looks like this:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var searchResponse = client.Search<Student>(s => s
|
||||||
|
.Index("students")
|
||||||
|
.Query(q => q
|
||||||
|
.Match(m => m
|
||||||
|
.Field(fld => fld.LastName)
|
||||||
|
.Query("Santos"))));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can print out the results by accessing the documents in the response:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
if (searchResponse.IsValid)
|
||||||
|
{
|
||||||
|
foreach (var s in searchResponse.Documents)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{s.Id} {s.LastName} {s.FirstName} {s.Gpa} {s.GradYear}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The response contains one document, which corresponds to the correct student:
|
||||||
|
|
||||||
|
`100 Santos Paulo 3.93 2021`
|
||||||
|
|
||||||
|
## Using OpenSearch.Client methods asynchronously
|
||||||
|
|
||||||
|
For applications that require asynchronous code, all method calls in OpenSearch.Client have asynchronous counterparts:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// synchronous method
|
||||||
|
var response = client.Index(student, i => i.Index("students"));
|
||||||
|
|
||||||
|
// asynchronous method
|
||||||
|
var response = await client.IndexAsync(student, i => i.Index("students"));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Falling back on the low-level OpenSearch.Net client
|
||||||
|
|
||||||
|
OpenSearch.Client exposes the low-level the OpenSearch.Net client you can use if anything is missing:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var lowLevelClient = client.LowLevel;
|
||||||
|
|
||||||
|
var searchResponseLow = lowLevelClient.Search<SearchResponse<Student>>("students",
|
||||||
|
PostData.Serializable(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
query = new
|
||||||
|
{
|
||||||
|
match = new
|
||||||
|
{
|
||||||
|
lastName = new
|
||||||
|
{
|
||||||
|
query = "Santos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (searchResponseLow.IsValid)
|
||||||
|
{
|
||||||
|
foreach (var s in searchResponseLow.Documents)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{s.Id} {s.LastName} {s.FirstName} {s.Gpa} {s.GradYear}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample program
|
||||||
|
|
||||||
|
The following is a complete sample program that illustrates all of the concepts described above. It uses the Student class defined above.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using OpenSearch.Client;
|
||||||
|
using OpenSearch.Net;
|
||||||
|
|
||||||
|
namespace NetClientProgram;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
private static IOpenSearchClient osClient = new OpenSearchClient();
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Indexing one student......");
|
||||||
|
var student = new Student { Id = 100,
|
||||||
|
FirstName = "Paulo",
|
||||||
|
LastName = "Santos",
|
||||||
|
Gpa = 3.93,
|
||||||
|
GradYear = 2021 };
|
||||||
|
var response = osClient.Index(student, i => i.Index("students"));
|
||||||
|
Console.WriteLine(response.IsValid ? "Response received" : "Error");
|
||||||
|
|
||||||
|
Console.WriteLine("Searching for one student......");
|
||||||
|
SearchForOneStudent();
|
||||||
|
|
||||||
|
Console.WriteLine("Searching using low-level client......");
|
||||||
|
SearchLowLevel();
|
||||||
|
|
||||||
|
Console.WriteLine("Indexing an array of Student objects......");
|
||||||
|
var studentArray = new Student[]
|
||||||
|
{
|
||||||
|
new() { Id = 200,
|
||||||
|
FirstName = "Shirley",
|
||||||
|
LastName = "Rodriguez",
|
||||||
|
Gpa = 3.91,
|
||||||
|
GradYear = 2019},
|
||||||
|
new() { Id = 300,
|
||||||
|
FirstName = "Nikki",
|
||||||
|
LastName = "Wolf",
|
||||||
|
Gpa = 3.87,
|
||||||
|
GradYear = 2020}
|
||||||
|
};
|
||||||
|
var manyResponse = osClient.IndexMany(studentArray, "students");
|
||||||
|
Console.WriteLine(manyResponse.IsValid ? "Response received" : "Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SearchForOneStudent()
|
||||||
|
{
|
||||||
|
var searchResponse = osClient.Search<Student>(s => s
|
||||||
|
.Index("students")
|
||||||
|
.Query(q => q
|
||||||
|
.Match(m => m
|
||||||
|
.Field(fld => fld.LastName)
|
||||||
|
.Query("Santos"))));
|
||||||
|
|
||||||
|
PrintResponse(searchResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SearchLowLevel()
|
||||||
|
{
|
||||||
|
// Search for the student using the low-level client
|
||||||
|
var lowLevelClient = osClient.LowLevel;
|
||||||
|
|
||||||
|
var searchResponseLow = lowLevelClient.Search<SearchResponse<Student>>
|
||||||
|
("students",
|
||||||
|
PostData.Serializable(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
query = new
|
||||||
|
{
|
||||||
|
match = new
|
||||||
|
{
|
||||||
|
lastName = new
|
||||||
|
{
|
||||||
|
query = "Santos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
PrintResponse(searchResponseLow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintResponse(ISearchResponse<Student> response)
|
||||||
|
{
|
||||||
|
if (response.IsValid)
|
||||||
|
{
|
||||||
|
foreach (var s in response.Documents)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{s.Id} {s.LastName} " +
|
||||||
|
$"{s.FirstName} {s.Gpa} {s.GradYear}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Student not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: More advanced features of the high-level .NET client
|
||||||
|
nav_order: 12
|
||||||
|
has_children: false
|
||||||
|
parent: .NET clients
|
||||||
|
---
|
||||||
|
|
||||||
|
# More advanced features of the high-level .NET client (OpenSearch.Client)
|
||||||
|
|
||||||
|
The following example illustrates more advanced features of OpenSearch.Client. For a simple example, see the [Getting started guide]({{site.url}}{{site.baseurl}}/clients/OSC-dot-net/). This example uses the following Student class.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class Student
|
||||||
|
{
|
||||||
|
public int Id { get; init; }
|
||||||
|
public string FirstName { get; init; }
|
||||||
|
public string LastName { get; init; }
|
||||||
|
public int GradYear { get; init; }
|
||||||
|
public double Gpa { get; init; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mappings
|
||||||
|
|
||||||
|
OpenSearch uses dynamic mapping to infer field types of the documents that are indexed. However, to have more control over the schema of your document, you can pass an explicit mapping to OpenSearch. You can define data types for some or all fields of your document in this mapping.
|
||||||
|
|
||||||
|
Similarly, OpenSearch.Client uses auto mapping to infer field data types based on the types of the class's properties. To use auto mapping, create a `students` index using the AutoMap's default constructor:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var createResponse = await osClient.Indices.CreateAsync("students",
|
||||||
|
c => c.Map(m => m.AutoMap<Student>()));
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use auto mapping, Id and GradYear are mapped as integers, Gpa is mapped as a double, and FirstName and LastName are mapped as text with a keyword subfield. If you want to search for FirstName and LastName and allow only case-sensitive full matches, you can suppress analyzing by mapping these fields as keyword only. In Query DSL, you can accomplish this using the following query:
|
||||||
|
|
||||||
|
```json
|
||||||
|
PUT students
|
||||||
|
{
|
||||||
|
"mappings" : {
|
||||||
|
"properties" : {
|
||||||
|
"firstName" : {
|
||||||
|
"type" : "keyword"
|
||||||
|
},
|
||||||
|
"lastName" : {
|
||||||
|
"type" : "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In OpenSearch.Client, you can use fluid lambda syntax to mark these fields as keywords:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var createResponse = await osClient.Indices.CreateAsync(index,
|
||||||
|
c => c.Map(m => m.AutoMap<Student>()
|
||||||
|
.Properties<Student>(p => p
|
||||||
|
.Keyword(k => k.Name(f => f.FirstName))
|
||||||
|
.Keyword(k => k.Name(f => f.LastName)))));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
In addition to mappings, you can specify settings like the number of primary and replica shards when creating an index. The following query sets the number of primary shards to 1 and the number of replica shards to 2:
|
||||||
|
|
||||||
|
```json
|
||||||
|
PUT students
|
||||||
|
{
|
||||||
|
"mappings" : {
|
||||||
|
"properties" : {
|
||||||
|
"firstName" : {
|
||||||
|
"type" : "keyword"
|
||||||
|
},
|
||||||
|
"lastName" : {
|
||||||
|
"type" : "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"number_of_shards": 1,
|
||||||
|
"number_of_replicas": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In OpenSearch.Client, the equivalent of the above query is the following:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var createResponse = await osClient.Indices.CreateAsync(index,
|
||||||
|
c => c.Map(m => m.AutoMap<Student>()
|
||||||
|
.Properties<Student>(p => p
|
||||||
|
.Keyword(k => k.Name(f => f.FirstName))
|
||||||
|
.Keyword(k => k.Name(f => f.LastName))))
|
||||||
|
.Settings(s => s.NumberOfShards(1).NumberOfReplicas(2)));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Indexing multiple documents using the Bulk API
|
||||||
|
|
||||||
|
In addition to indexing one document using `Index` and `IndexDocument` and indexing multiple documents using `IndexMany`, you can gain more control over document indexing by using `Bulk` or `BulkAll`. Indexing documents individually is inefficient because it creates an HTTP request for every document sent. The BulkAll helper frees you from handling retry, chunking or back off request functionality. It automatically retries if the request fails, backs off if the server is down, and controls how many documents are sent in one HTTP request.
|
||||||
|
|
||||||
|
In the following example, `BulkAll` is configured with the index name, number of back off retries, and back off time. Additionally, the maximum degrees of parallelism setting controls the number of parallel HTTP requests containing the data. Finally, the size parameter signals how many documents are sent in one HTTP request.
|
||||||
|
|
||||||
|
We recommend setting the size to 100–1000 documents in production.
|
||||||
|
{: .tip}
|
||||||
|
|
||||||
|
`BulkAll` takes a stream of data and returns an Observable that you can use to observe the background operation.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var bulkAll = osClient.BulkAll(ReadData(), r => r
|
||||||
|
.Index(index)
|
||||||
|
.BackOffRetries(2)
|
||||||
|
.BackOffTime("30s")
|
||||||
|
.MaxDegreeOfParallelism(4)
|
||||||
|
.Size(100));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Searching with Boolean query
|
||||||
|
|
||||||
|
OpenSearch.Client exposes full OpenSearch query capability. In addition to simple searches that use the match query, you can create a more complex Boolean query to search for students who graduated in 2022 and sort them by last name. In the example below, search is limited to 10 documents, and the scroll API is used to control the pagination of results.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var gradResponse = await osClient.SearchAsync<Student>(s => s
|
||||||
|
.Index(index)
|
||||||
|
.From(0)
|
||||||
|
.Size(10)
|
||||||
|
.Scroll("1m")
|
||||||
|
.Query(q => q
|
||||||
|
.Bool(b => b
|
||||||
|
.Filter(f => f
|
||||||
|
.Term(t => t.Field(fld => fld.GradYear).Value(2022)))))
|
||||||
|
.Sort(srt => srt.Ascending(f => f.LastName)));
|
||||||
|
```
|
||||||
|
|
||||||
|
The response contains the Documents property with matching documents from OpenSearch. The data is in the form of deserialized JSON objects of Student type, so you can access their properties in a strongly typed fashion. All serialization and deserialization is handled by OpenSearch.Client.
|
||||||
|
|
||||||
|
## Aggregations
|
||||||
|
|
||||||
|
OpenSearch.Client includes the full OpenSearch query functionality, including aggregations. In addition to grouping search results into buckets (for example, grouping students by GPA ranges), you can calculate metrics like sum or average. The following query calculates the average GPA of all students in the index.
|
||||||
|
|
||||||
|
Setting Size to 0 means OpenSearch will only return the aggregation, not the actual documents.
|
||||||
|
{: .tip}
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var aggResponse = await osClient.SearchAsync<Student>(s => s
|
||||||
|
.Index(index)
|
||||||
|
.Size(0)
|
||||||
|
.Aggregations(a => a
|
||||||
|
.Average("average gpa",
|
||||||
|
avg => avg.Field(fld => fld.Gpa))));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample program for creating an index and indexing data
|
||||||
|
|
||||||
|
The following program creates an index, reads a stream of student records from a comma-separated file and indexes this data into OpenSearch.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using OpenSearch.Client;
|
||||||
|
|
||||||
|
namespace NetClientProgram;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
private const string index = "students";
|
||||||
|
|
||||||
|
public static IOpenSearchClient osClient = new OpenSearchClient();
|
||||||
|
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
// Check if the index with the name "students" exists
|
||||||
|
var existResponse = await osClient.Indices.ExistsAsync(index);
|
||||||
|
|
||||||
|
if (!existResponse.Exists) // There is no index with this name
|
||||||
|
{
|
||||||
|
// Create an index "students"
|
||||||
|
// Map FirstName and LastName as keyword
|
||||||
|
var createResponse = await osClient.Indices.CreateAsync(index,
|
||||||
|
c => c.Map(m => m.AutoMap<Student>()
|
||||||
|
.Properties<Student>(p => p
|
||||||
|
.Keyword(k => k.Name(f => f.FirstName))
|
||||||
|
.Keyword(k => k.Name(f => f.LastName))))
|
||||||
|
.Settings(s => s.NumberOfShards(1).NumberOfReplicas(1)));
|
||||||
|
|
||||||
|
if (!createResponse.IsValid && !createResponse.Acknowledged)
|
||||||
|
{
|
||||||
|
throw new Exception("Create response is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a stream of data and send it to OpenSearch
|
||||||
|
var bulkAll = osClient.BulkAll(ReadData(), r => r
|
||||||
|
.Index(index)
|
||||||
|
.BackOffRetries(2)
|
||||||
|
.BackOffTime("20s")
|
||||||
|
.MaxDegreeOfParallelism(4)
|
||||||
|
.Size(10));
|
||||||
|
|
||||||
|
// Wait until the data upload is complete.
|
||||||
|
// FromMinutes specifies a timeout.
|
||||||
|
// r is a response object that is returned as the data is indexed.
|
||||||
|
bulkAll.Wait(TimeSpan.FromMinutes(10), r =>
|
||||||
|
Console.WriteLine("Data chunk indexed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads student data in the form "Id,FirsName,LastName,GradYear,Gpa"
|
||||||
|
public static IEnumerable<Student> ReadData()
|
||||||
|
{
|
||||||
|
var file = new StreamReader("C:\\search\\students.csv");
|
||||||
|
|
||||||
|
string s;
|
||||||
|
while ((s = file.ReadLine()) is not null)
|
||||||
|
{
|
||||||
|
yield return new Student(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample program for search
|
||||||
|
|
||||||
|
The following program searches students by name and graduation date and calculates the average GPA.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using OpenSearch.Client;
|
||||||
|
|
||||||
|
namespace NetClientProgram;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
private const string index = "students";
|
||||||
|
|
||||||
|
public static IOpenSearchClient osClient = new OpenSearchClient();
|
||||||
|
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
await SearchByName();
|
||||||
|
|
||||||
|
await SearchByGradDate();
|
||||||
|
|
||||||
|
await CalculateAverageGpa();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SearchByName()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Searching for name......");
|
||||||
|
|
||||||
|
var nameResponse = await osClient.SearchAsync<Student>(s => s
|
||||||
|
.Index(index)
|
||||||
|
.Query(q => q
|
||||||
|
.Match(m => m
|
||||||
|
.Field(fld => fld.FirstName)
|
||||||
|
.Query("Zhang"))));
|
||||||
|
|
||||||
|
if (!nameResponse.IsValid)
|
||||||
|
{
|
||||||
|
throw new Exception("Aggregation query response is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var s in nameResponse.Documents)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{s.Id} {s.LastName} " +
|
||||||
|
$"{s.FirstName} {s.Gpa} {s.GradYear}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SearchByGradDate()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Searching for grad date......");
|
||||||
|
|
||||||
|
// Search for all students who graduated in 2022
|
||||||
|
var gradResponse = await osClient.SearchAsync<Student>(s => s
|
||||||
|
.Index(index)
|
||||||
|
.From(0)
|
||||||
|
.Size(2)
|
||||||
|
.Scroll("1m")
|
||||||
|
.Query(q => q
|
||||||
|
.Bool(b => b
|
||||||
|
.Filter(f => f
|
||||||
|
.Term(t => t.Field(fld => fld.GradYear).Value(2022)))))
|
||||||
|
.Sort(srt => srt.Ascending(f => f.LastName))
|
||||||
|
.Size(10));
|
||||||
|
|
||||||
|
|
||||||
|
if (!gradResponse.IsValid)
|
||||||
|
{
|
||||||
|
throw new Exception("Grad date query response is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (gradResponse.Documents.Any())
|
||||||
|
{
|
||||||
|
foreach (var data in gradResponse.Documents)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{data.Id} {data.LastName} {data.FirstName} " +
|
||||||
|
$"{data.Gpa} {data.GradYear}");
|
||||||
|
}
|
||||||
|
gradResponse = osClient.Scroll<Student>("1m", gradResponse.ScrollId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task CalculateAverageGpa()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Calculating average GPA......");
|
||||||
|
|
||||||
|
// Search and aggregate
|
||||||
|
// Size 0 means documents are not returned, only aggregation is returned
|
||||||
|
var aggResponse = await osClient.SearchAsync<Student>(s => s
|
||||||
|
.Index(index)
|
||||||
|
.Size(0)
|
||||||
|
.Aggregations(a => a
|
||||||
|
.Average("average gpa",
|
||||||
|
avg => avg.Field(fld => fld.Gpa))));
|
||||||
|
|
||||||
|
if (!aggResponse.IsValid) throw new Exception("Aggregation response not valid");
|
||||||
|
|
||||||
|
var avg = aggResponse.Aggregations.Average("average gpa").Value;
|
||||||
|
Console.WriteLine($"Average GPA is {avg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,395 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: Low-level .NET client
|
||||||
|
nav_order: 30
|
||||||
|
has_children: false
|
||||||
|
parent: .NET clients
|
||||||
|
---
|
||||||
|
|
||||||
|
# Low-level .NET client (OpenSearch.Net)
|
||||||
|
|
||||||
|
OpenSearch.Net is a low-level .NET client that provides the foundational layer of communication with OpenSearch. It is dependency free, and it can handle round-robin load balancing, transport, and the basic request/response cycle. OpenSearch.Net contains all OpenSearch API endpoints as methods. When using OpenSearch.Net, you need to construct the queries yourself.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The following example illustrates connecting to OpenSearch, indexing documents, and sending queries on the data. It uses the Student class to represent one student, which is equivalent to one document in the index.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class Student
|
||||||
|
{
|
||||||
|
public int Id { get; init; }
|
||||||
|
public string FirstName { get; init; }
|
||||||
|
public string LastName { get; init; }
|
||||||
|
public int GradYear { get; init; }
|
||||||
|
public double Gpa { get; init; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing the Opensearch.Net client
|
||||||
|
|
||||||
|
To install Opensearch.Net, download the [Opensearch.Net NuGet package](https://www.nuget.org/packages/OpenSearch.Net) and add it to your project in an IDE of your choice. In Microsoft Visual Studio, follow the steps below:
|
||||||
|
- In the **Solution Explorer** panel, right-click on your solution or project and select **Manage NuGet Packages for Solution**.
|
||||||
|
- Search for the OpenSearch.Net NuGet package, and select **Install**.
|
||||||
|
|
||||||
|
Alternatively, you can add OpenSearch.Net to your .csproj file:
|
||||||
|
```xml
|
||||||
|
<Project>
|
||||||
|
...
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Opensearch.Net" Version="1.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connecting to OpenSearch
|
||||||
|
|
||||||
|
Use the default constructor when creating an OpenSearchLowLevelClient object to connect to the default OpenSearch host (`http://localhost:9200`).
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var client = new OpenSearchLowLevelClient();
|
||||||
|
```
|
||||||
|
|
||||||
|
To connect to your OpenSearch cluster through a single node with a known address, create a ConnectionConfiguration object with that address and pass it to the OpenSearch.Net constructor:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var nodeAddress = new Uri("http://myserver:9200");
|
||||||
|
var config = new ConnectionConfiguration(nodeAddress);
|
||||||
|
var client = new OpenSearchLowLevelClient(config);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use a [connection pool]({{site.url}}{{site.baseurl}}/clients/dot-net-conventions#connection-pools) to manage the nodes in the cluster. Additionally, you can set up a connection configuration to have OpenSearch return the response as formatted JSON.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var uri = new Uri("http://localhost:9200");
|
||||||
|
var connectionPool = new SingleNodeConnectionPool(uri);
|
||||||
|
var settings = new ConnectionConfiguration(connectionPool).PrettyJson();
|
||||||
|
var client = new OpenSearchLowLevelClient(settings);
|
||||||
|
```
|
||||||
|
|
||||||
|
To connect to your OpenSearch cluster using multiple nodes, create a connection pool with their addresses. In this example, a [`SniffingConnectionPool`]({{site.url}}{{site.baseurl}}/clients/dot-net-conventions#connection-pools) is used because it keeps track of nodes being removed or added to the cluster, so it works best for clusters that scale automatically.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var uris = new[]
|
||||||
|
{
|
||||||
|
new Uri("http://localhost:9200"),
|
||||||
|
new Uri("http://localhost:9201"),
|
||||||
|
new Uri("http://localhost:9202")
|
||||||
|
};
|
||||||
|
var connectionPool = new SniffingConnectionPool(uris);
|
||||||
|
var settings = new ConnectionConfiguration(connectionPool).PrettyJson();
|
||||||
|
var client = new OpenSearchLowLevelClient(settings);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using ConnectionSettings
|
||||||
|
|
||||||
|
`ConnectionConfiguration` is used to pass configuration options to the OpenSearch.Net client. `ConnectionSettings` inherits from `ConnectionConfiguration` and provides additional configuration options.
|
||||||
|
The following example uses `ConnectionSettings` to:
|
||||||
|
- Set the default index name for requests that don't specify the index name.
|
||||||
|
- Enable gzip-compressed requests and responses.
|
||||||
|
- Signal to OpenSearch to return formatted JSON.
|
||||||
|
- Make field names lowercase.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var uri = new Uri("http://localhost:9200");
|
||||||
|
var connectionPool = new SingleNodeConnectionPool(uri);
|
||||||
|
var settings = new ConnectionSettings(connectionPool)
|
||||||
|
.DefaultIndex("students")
|
||||||
|
.EnableHttpCompression()
|
||||||
|
.PrettyJson()
|
||||||
|
.DefaultFieldNameInferrer(f => f.ToLower());
|
||||||
|
|
||||||
|
var client = new OpenSearchLowLevelClient(settings);
|
||||||
|
```
|
||||||
|
## Indexing one document
|
||||||
|
|
||||||
|
To index a document, you first need to create an instance of the Student class:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var student = new Student {
|
||||||
|
Id = 100,
|
||||||
|
FirstName = "Paulo",
|
||||||
|
LastName = "Santos",
|
||||||
|
Gpa = 3.93,
|
||||||
|
GradYear = 2021
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can create an instance of Student using an anonymous type:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var student = new {
|
||||||
|
Id = 100,
|
||||||
|
FirstName = "Paulo",
|
||||||
|
LastName = "Santos",
|
||||||
|
Gpa = 3.93,
|
||||||
|
GradYear = 2021
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Next, upload this Student into the `students` index using the `Index` method:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var response = client.Index<StringResponse>("students", "100",
|
||||||
|
PostData.Serializable(student));
|
||||||
|
Console.WriteLine(response.Body);
|
||||||
|
```
|
||||||
|
|
||||||
|
The generic type parameter of the `Index` method specifies the response body type. In the example above, the response is a string.
|
||||||
|
|
||||||
|
## Indexing many documents using the Bulk API
|
||||||
|
|
||||||
|
To index many documents, use the Bulk API to bundle many operations into one request:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var studentArray = new object[]
|
||||||
|
{
|
||||||
|
new {index = new { _index = "students", _type = "_doc", _id = "200"}},
|
||||||
|
new { Id = 200,
|
||||||
|
FirstName = "Shirley",
|
||||||
|
LastName = "Rodriguez",
|
||||||
|
Gpa = 3.91,
|
||||||
|
GradYear = 2019
|
||||||
|
},
|
||||||
|
new {index = new { _index = "students", _type = "_doc", _id = "300"}},
|
||||||
|
new { Id = 300,
|
||||||
|
FirstName = "Nikki",
|
||||||
|
LastName = "Wolf",
|
||||||
|
Gpa = 3.87,
|
||||||
|
GradYear = 2020
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var manyResponse = client.Bulk<StringResponse>(PostData.MultiJson(studentArray));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can send the request body as an anonymous object, string, byte array, or stream in APIs that take a body. For APIs that take multiline JSON, you can send the body as a list of bytes or a list of objects, like in the example above. The `PostData` class has static methods to send the body in all of these forms.
|
||||||
|
|
||||||
|
## Searching for a document
|
||||||
|
|
||||||
|
To construct a Query DSL query, use anonymous types within the request body. The following query searches for all students who graduated in 2021:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var searchResponseLow = client.Search<StringResponse>("students",
|
||||||
|
PostData.Serializable(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
from = 0,
|
||||||
|
size = 20,
|
||||||
|
|
||||||
|
query = new
|
||||||
|
{
|
||||||
|
term = new
|
||||||
|
{
|
||||||
|
gradYear = new
|
||||||
|
{
|
||||||
|
value = 2019
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Console.WriteLine(searchResponseLow.Body);
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use strings to construct the request. When using strings, you have to escape the `"` character:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var searchResponse = client.Search<StringResponse>("students",
|
||||||
|
@" {
|
||||||
|
""query"":
|
||||||
|
{
|
||||||
|
""match"":
|
||||||
|
{
|
||||||
|
""lastName"":
|
||||||
|
{
|
||||||
|
""query"": ""Santos""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
Console.WriteLine(searchResponse.Body);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using OpenSearch.Net methods asynchronously
|
||||||
|
|
||||||
|
For applications that require asynchronous code, all method calls in OpenSearch.Client have asynchronous counterparts:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
// synchronous method
|
||||||
|
var response = client.Index<StringResponse>("students", "100",
|
||||||
|
PostData.Serializable(student));
|
||||||
|
|
||||||
|
// asynchronous method
|
||||||
|
var response = client.IndexAsync<StringResponse>("students", "100",
|
||||||
|
PostData.Serializable(student));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling exceptions
|
||||||
|
|
||||||
|
By default, OpenSearch.Net does not throw exceptions when an operation is unsuccessful. In particular, OpenSearch.Net does not throw exceptions if the response status code has one of the expected values for this request. For example, the following query searches for a document in an index that does not exist:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var searchResponse = client.Search<StringResponse>("students1",
|
||||||
|
@" {
|
||||||
|
""query"":
|
||||||
|
{
|
||||||
|
""match"":
|
||||||
|
{
|
||||||
|
""lastName"":
|
||||||
|
{
|
||||||
|
""query"": ""Santos""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
Console.WriteLine(searchResponse.Body);
|
||||||
|
```
|
||||||
|
|
||||||
|
The response contains an error status code 404, which is one of the expected error codes for search requests, so no exception is thrown. You can see the status code in the `status` field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error" : {
|
||||||
|
"root_cause" : [
|
||||||
|
{
|
||||||
|
"type" : "index_not_found_exception",
|
||||||
|
"reason" : "no such index [students1]",
|
||||||
|
"index" : "students1",
|
||||||
|
"resource.id" : "students1",
|
||||||
|
"resource.type" : "index_or_alias",
|
||||||
|
"index_uuid" : "_na_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type" : "index_not_found_exception",
|
||||||
|
"reason" : "no such index [students1]",
|
||||||
|
"index" : "students1",
|
||||||
|
"resource.id" : "students1",
|
||||||
|
"resource.type" : "index_or_alias",
|
||||||
|
"index_uuid" : "_na_"
|
||||||
|
},
|
||||||
|
"status" : 404
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To configure OpenSearch.Net to throw exceptions, turn on the `ThrowExceptions()` setting on `ConnectionConfiguration`:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var uri = new Uri("http://localhost:9200");
|
||||||
|
var connectionPool = new SingleNodeConnectionPool(uri);
|
||||||
|
var settings = new ConnectionConfiguration(connectionPool)
|
||||||
|
.PrettyJson().ThrowExceptions();
|
||||||
|
var client = new OpenSearchLowLevelClient(settings);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use the following properties of the response object to determine response success:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
Console.WriteLine("Success: " + searchResponse.Success);
|
||||||
|
Console.WriteLine("SuccessOrKnownError: " + searchResponse.SuccessOrKnownError);
|
||||||
|
Console.WriteLine("Original Exception: " + searchResponse.OriginalException);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Success` returns true if the response code is in the 2xx range or the response code has one of the expected values for this request.
|
||||||
|
- `SuccessOrKnownError` returns true if the response is successful or the response code is in the 400–501 or 505–599 ranges. If SuccessOrKnownError is true, the request is not retried.
|
||||||
|
- `OriginalException` holds the original exception for the unsuccessful responses.
|
||||||
|
|
||||||
|
## Sample program
|
||||||
|
|
||||||
|
The following program creates an index, indexes data, and searches for documents.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using OpenSearch.Net;
|
||||||
|
using OpenSearch.Client;
|
||||||
|
|
||||||
|
namespace NetClientProgram;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// Create a client with custom settings
|
||||||
|
var uri = new Uri("http://localhost:9200");
|
||||||
|
var connectionPool = new SingleNodeConnectionPool(uri);
|
||||||
|
var settings = new ConnectionSettings(connectionPool)
|
||||||
|
.PrettyJson();
|
||||||
|
var client = new OpenSearchLowLevelClient(settings);
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine("Indexing one student......");
|
||||||
|
var student = new Student {
|
||||||
|
Id = 100,
|
||||||
|
FirstName = "Paulo",
|
||||||
|
LastName = "Santos",
|
||||||
|
Gpa = 3.93,
|
||||||
|
GradYear = 2021 };v
|
||||||
|
var response = client.Index<StringResponse>("students", "100",
|
||||||
|
PostData.Serializable(student));
|
||||||
|
Console.WriteLine(response.Body);
|
||||||
|
|
||||||
|
Console.WriteLine("Indexing many students......");
|
||||||
|
var studentArray = new object[]
|
||||||
|
{
|
||||||
|
new { index = new { _index = "students", _type = "_doc", _id = "200"}},
|
||||||
|
new {
|
||||||
|
Id = 200,
|
||||||
|
FirstName = "Shirley",
|
||||||
|
LastName = "Rodriguez",
|
||||||
|
Gpa = 3.91,
|
||||||
|
GradYear = 2019},
|
||||||
|
new { index = new { _index = "students", _type = "_doc", _id = "300"}},
|
||||||
|
new {
|
||||||
|
Id = 300,
|
||||||
|
FirstName = "Nikki",
|
||||||
|
LastName = "Wolf",
|
||||||
|
Gpa = 3.87,
|
||||||
|
GradYear = 2020}
|
||||||
|
};
|
||||||
|
|
||||||
|
var manyResponse = client.Bulk<StringResponse>(PostData.MultiJson(studentArray));
|
||||||
|
|
||||||
|
Console.WriteLine(manyResponse.Body);
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine("Searching for students who graduated in 2019......");
|
||||||
|
var searchResponseLow = client.Search<StringResponse>("students",
|
||||||
|
PostData.Serializable(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
from = 0,
|
||||||
|
size = 20,
|
||||||
|
|
||||||
|
query = new
|
||||||
|
{
|
||||||
|
term = new
|
||||||
|
{
|
||||||
|
gradYear = new
|
||||||
|
{
|
||||||
|
value = 2019
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Console.WriteLine(searchResponseLow.Body);
|
||||||
|
|
||||||
|
Console.WriteLine("Searching for a student with the last name Santos......");
|
||||||
|
|
||||||
|
var searchResponse = client.Search<StringResponse>("students",
|
||||||
|
@" {
|
||||||
|
""query"":
|
||||||
|
{
|
||||||
|
""match"":
|
||||||
|
{
|
||||||
|
""lastName"":
|
||||||
|
{
|
||||||
|
""query"": ""Santos""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
Console.WriteLine(searchResponse.Body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: .NET client considerations
|
||||||
|
nav_order: 20
|
||||||
|
has_children: false
|
||||||
|
parent: .NET clients
|
||||||
|
---
|
||||||
|
|
||||||
|
# .NET client considerations and best practices
|
||||||
|
|
||||||
|
The following sections provide information regarding the considerations and best practices for using .NET clients.
|
||||||
|
|
||||||
|
## Registering OpenSearch.Client as a singleton
|
||||||
|
|
||||||
|
As a rule, you should set up your OpenSearch.Client as a singleton. OpenSearch.Client manages connections to the server and the states of the nodes in a cluster. Additionally, each client uses a lot of configuration for its setup. Therefore, it is beneficial to create an OpenSearch.Client instance once and reuse it for all OpenSearch operations. The client is thread safe, so the same instance can be shared by multiple threads.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
The following are the types of exceptions that may be thrown by .NET clients:
|
||||||
|
|
||||||
|
- `OpenSearchClientException` is a known exception that occurs either in the request pipeline (for example, timeout reached) or in OpenSearch (for example, malformed query). If it is an OpenSearch exception, the `ServerError` response property contains the error that OpenSearch returns.
|
||||||
|
- `UnexpectedOpenSearchClientException` is an unknown exception (for example, an error during deserialization) and is a subclass of OpenSearchClientException.
|
||||||
|
- System exceptions are thrown when the API is not used properly.
|
||||||
|
|
||||||
|
## Nodes
|
||||||
|
|
||||||
|
To create a node, pass a `Uri` object into its constructor:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var uri = new Uri("http://example.org/opensearch");
|
||||||
|
var node = new Node(uri);
|
||||||
|
```
|
||||||
|
|
||||||
|
When first created, a node is master eligible, and its `HoldsData` property is set to true.
|
||||||
|
The `AbsolutePath` property of the node created above is `"/opensearch/"`: A trailing forward slash is appended so that the paths can be easily combined. If not specified, the default `Port` is 80.
|
||||||
|
|
||||||
|
Nodes are considered equal if they have the same endpoint. Metadata is not taken into account when checking nodes for equality.
|
||||||
|
{: .note}
|
||||||
|
|
||||||
|
## Connection pools
|
||||||
|
|
||||||
|
Connection pools are instances of `IConnectionPool` and are responsible for managing the nodes in the OpenSearch cluster. We recommend creating a [singleton client](#registering-opensearchclient-as-a-singleton) with a single `ConnectionSettings` object. The lifetime of both the client and its `ConnectionSettings` is the lifetime of the application.
|
||||||
|
|
||||||
|
The following are connection pool types.
|
||||||
|
|
||||||
|
- **SingleNodeConnectionPool**
|
||||||
|
|
||||||
|
`SingleNodeConnectionPool` is the default connection pool that is used if no connection pool is passed to the `ConnectionSettings` constructor. Use `SingleNodeConnectionPool` if you have only one node in the cluster or if your cluster has a load balancer as an entry point. `SingleNodeConnectionPool` does not support sniffing or pinging and does not mark nodes as dead or alive.
|
||||||
|
|
||||||
|
- **CloudConnectionPool**
|
||||||
|
|
||||||
|
`CloudConnectionPool` is a subclass of `SingleNodeConnectionPool` that takes a Cloud ID and credentials. Like `SingleNodeConnectionPool`, `CloudConnectionPool` does not support sniffing or pinging.
|
||||||
|
|
||||||
|
- **StaticConnectionPool**
|
||||||
|
|
||||||
|
`StaticConnectionPool` is used for a small cluster when you do not want to turn on sniffing to learn about cluster topology. `StaticConnectionPool` does not support sniffing, but can support pinging.
|
||||||
|
|
||||||
|
- **SniffingConnectionPool**
|
||||||
|
|
||||||
|
`SniffingConnectionPool` is a subclass of `StaticConnectionPool`. It is thread safe and supports sniffing and pinging. `SniffingConnectionPool` can be reseeded at run time, and you can specify node roles when seeding.
|
||||||
|
|
||||||
|
- **StickyConnectionPool**
|
||||||
|
|
||||||
|
`StickyConnectionPool` is set up to return the first live node, which then persists between requests. It can be seeded using an enumerable of `Uri` or `Node` objects. `StickyConnectionPool` does not support sniffing but supports pinging.
|
||||||
|
|
||||||
|
- **StickySniffingConnectionPool**
|
||||||
|
|
||||||
|
`StickySniffingConnectionPool` is a subclass of `SniffingConnectionPool`. Like `StickyConnectionPool`, it returns the first live node2, which then persists between requests. `StickySniffingConnectionPool` supports sniffing and sorting so that each instance of your application can favor a different node. Nodes have weights associated with them and can be sorted by weight.
|
||||||
|
|
||||||
|
## Retries
|
||||||
|
|
||||||
|
If a request does not succeed, it is automatically retried. By default, the number of retries is the number of nodes known to OpenSearch.Client in your cluster. The number of retries is also limited by the timeout parameter, so OpenSearch.Client retries requests as many times as possible within the timeout period.
|
||||||
|
|
||||||
|
To set the maximum number of retries, specify the number in the `MaximumRetries` property on the `ConnectionSettings` object.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var settings = new ConnectionSettings(connectionPool).MaximumRetries(5);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also set a `RequestTimeout` that specifies a timeout for a single request and a `MaxRetryTimeout` that specifies the time limit for all retry attempts. In the example below, `RequestTimeout` is set to 4 seconds, and `MaxRetryTimeout` is set to 12 seconds, so the maximum number of attempts for a query is 3.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
var settings = new ConnectionSettings(connectionPool)
|
||||||
|
.RequestTimeout(TimeSpan.FromSeconds(4))
|
||||||
|
.MaxRetryTimeout(TimeSpan.FromSeconds(12));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Failover
|
||||||
|
|
||||||
|
If you are using a connection pool with multiple nodes, a request is retried if it returns a 502 (Bad Gateway), 503 (Service Unavailable), or 504 (Gateway Timeout) HTTP error response code. If the response code is an error code in the 400–501 or 505–599 ranges, the request is not retried.
|
||||||
|
|
||||||
|
A response is considered valid if the response code is in the 2xx range or the response code has one of the expected values for this request. For example, 404 (Not Found) is a valid response for a request that checks whether an index exists.
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: .NET clients
|
||||||
|
nav_order: 75
|
||||||
|
has_children: true
|
||||||
|
has_toc: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# .NET clients
|
||||||
|
|
||||||
|
OpenSearch has two .NET clients: a low-level [OpenSearch.Net]({{site.url}}{{site.baseurl}}/clients/OpenSearch-dot-net/) client and a high-level [OpenSearch.Client]({{site.url}}{{site.baseurl}}/clients/OSC-dot-net/) client.
|
||||||
|
|
||||||
|
[OpenSearch.Net]({{site.url}}{{site.baseurl}}/clients/OpenSearch-dot-net/) is a low-level .NET client that provides the foundational layer of communication with OpenSearch. It is dependency free, and it can handle round-robin load balancing, transport, and the basic request/response cycle. OpenSearch.Net contains methods for all OpenSearch API endpoints.
|
||||||
|
|
||||||
|
[OpenSearch.Client]({{site.url}}{{site.baseurl}}/clients/OSC-dot-net/) is a high-level .NET client on top of OpenSearch.Net. It provides strongly typed requests and responses as well as Query DSL. It frees you from constructing raw JSON requests and parsing raw JSON responses by supplying models that parse and serialize/deserialize requests and responses automatically. OpenSearch.Client also exposes the OpenSearch.Net low-level client if you need it. OpenSearch.Client includes the following advanced functionality:
|
||||||
|
|
||||||
|
- Automapping: Given a C# type, OpenSearch.Client can infer the correct mapping to send to OpenSearch.
|
||||||
|
- Operator overloading in queries.
|
||||||
|
- Type and index inference.
|
||||||
|
|
||||||
|
You can use both .NET clients in a console program, a .NET core, an ASP.NET core, or in worker services.
|
||||||
|
|
||||||
|
To get started with OpenSearch.Client, follow the instructions in [Getting started with the high-level .NET client]({{site.url}}{{site.baseurl}}/clients/OSC-dot-net#installing-opensearchclient) or in [More advanced geatures of the high-level .NET client]({{site.url}}{{site.baseurl}}/clients/osc-example), a slightly more advanced walkthrough.
|
|
@ -18,6 +18,7 @@ For example, a 1.0.0 client works with an OpenSearch 1.1.0 cluster, but might no
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
* [OpenSearch Python client]({{site.url}}{{site.baseurl}}/clients/python/)
|
* [OpenSearch Python client]({{site.url}}{{site.baseurl}}/clients/python/)
|
||||||
* [OpenSearch JavaScript (Node.js) client]({{site.url}}{{site.baseurl}}/clients/javascript/)
|
* [OpenSearch JavaScript (Node.js) client]({{site.url}}{{site.baseurl}}/clients/javascript/)
|
||||||
|
* [OpenSearch .NET clients]({{site.url}}{{site.baseurl}}/clients/dot-net/)
|
||||||
* [OpenSearch Go client]({{site.url}}{{site.baseurl}}/clients/go/)
|
* [OpenSearch Go client]({{site.url}}{{site.baseurl}}/clients/go/)
|
||||||
* [OpenSearch PHP client]({{site.url}}{{site.baseurl}}/clients/php/)
|
* [OpenSearch PHP client]({{site.url}}{{site.baseurl}}/clients/php/)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue