How to use Scroll while passing raw json Query to ElasticSearch using NEST - nest

var query = #"
{
""query"": {
""match_all"": { }
}
}";
Func<SearchRequestParameters, SearchRequestParameters> requestParameters = a =>
a.SearchType(SearchType.Scan).Scroll(TimeSpan.FromSeconds(60));
var searchResult = await client.LowLevel.SearchAsync<SearchResponse<T>>(indexName, mappingName, query , requestParameters)
if (searchResult.Body.IsValid)
{
var scrollNo = 0;
var results = await client.ScrollAsync<T>("10s", searchResult.Body.ScrollId);
while (results.Documents.Any())
{
documents.AddRange(results.Documents);
scrollNo++;
results = await client.ScrollAsync<T>("10s", results.ScrollId);
return new Customresponse<T>
{
Documents = documents,
total = result.Body.Total
};
}
Would like to pull all data using scroll while passing raw json query. but scroll is not working properly while passing json raw query. Can anyone help on this ?.

Your example is nearly there but not quite; you're missing a closing brace for the while loop to collect all documents before returning the custom response.
Here's an example I just ran on the Stackoverflow data set, to return all questions tagged with nest
private IElasticClient _client;
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var defaultIndex = "default-index";
var connectionSettings = new ConnectionSettings(pool);
_client = new ElasticClient(connectionSettings);
var query = #"
{
""query"": {
""term"": {
""tags"": {
""value"": ""nest""
}
}
}
}";
var result = RunScrollAsync(query).Result.Dump();
}
private async Task<Customresponse<Question>> RunScrollAsync(string query)
{
var scrollTime = "10s";
// omit the .SearchType(Scan) which is deprecated. Not
// specifying means the first response contains the first set
// of documents
var esResponse = await _client.LowLevel.SearchAsync<SearchResponse<Question>>(
"posts",
"question",
query, r => r.Scroll(TimeSpan.FromSeconds(10))).ConfigureAwait(false);
if (esResponse.Body.IsValid && esResponse.Body.Documents.Any())
{
// assume you have less than 2,147,483,647 documents to return?
var documents = new List<Question>((int)esResponse.Body.Total);
documents.AddRange(esResponse.Body.Documents);
var scrollNo = 0;
var response = await _client.ScrollAsync<Question>(scrollTime, esResponse.Body.ScrollId).ConfigureAwait(false);;
// keep scrolling until no more documents are returned
while (response.Documents.Any())
{
documents.AddRange(response.Documents);
scrollNo++;
response = await _client.ScrollAsync<Question>(scrollTime, response.ScrollId).ConfigureAwait(false);;
}
return new Customresponse<Question>
{
Documents = documents,
total = response.Total
};
}
// return an empty result.
// Or throw an exception, or log - whatever you need to do
return new Customresponse<Question>
{
Documents = Enumerable.Empty<Question>(),
total = 0
};
}
public class Customresponse<T>
{
public IEnumerable<T> Documents { get; set; }
public long total { get; set; }
}
This returns all 342 questions, with a total of 342 (Data set is from June 2016).

Related

Is there a way to improve the code? .Net Core 6.0 and Cosmos Database with SQL API

It's my first time using .Net Core 6.0 WebAPI and Cosmos Database with SQL API, so I'm concerned that I've done things correctly.
I have used /address/zipcode as a partition key in the below sample
public class FamilyService : IFamilyService
{
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public Task<Family> GetFamilyDataAsync(string id, string zipcode)
{
return Task.FromResult(_container.GetItemLinqQueryable<Family>(allowSynchronousQueryExecution: true)
.Where(p => p.Id == id && p.Address.ZipCode == zipcode)
.ToList().First());
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipcode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipcode));
}
private async Task<bool> GetFamilyDataFromId(string id, string zipcode)
{
string query = $"select * from c where c.id=#familyId";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#familyId", id);
List<Family> familyResults = new List<Family>();
FeedIterator streamResultSet = _container.GetItemQueryStreamIterator(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipcode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<Family> familyResult = streamResponse.Documents.ToObject<List<Family>>();
familyResults.AddRange(familyResult);
}
else
{
return false;
}
}
}
if (familyResults != null && familyResults.Count > 0)
{
return true;
}
return familyResults;
}
Especially I want to focus more on those methods that retrieves the data from the database.
Update#1 : I have updated the code as suggested by #Mark Brown.
public class FamilyService : IFamilyService
{
private Container _container;
private static readonly JsonSerializer Serializer = new JsonSerializer();
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public async Task<Family> GetFamilyDataAsync(string id, string zipCode)
{
return await _container.ReadItemAsync<Family>(id, new PartitionKey(zipCode));
}
public async Task<List<Family>> GetAllFamilyDataAsync(string zipCode)
{
string query = $"SELECT * FROM Family";
QueryDefinition queryDefinition = new QueryDefinition(query);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataByLastNameAsync(string lastName, string zipCode)
{
string query = $"SELECT * FROM Family where Family.lastName = #lastName";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#lastName", lastName);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataPaginatedAsync(int pageNumber, string zipCode)
{
int pageSize = 2;
int itemsToSkip = (pageSize * pageNumber) - pageSize;
QueryRequestOptions options = new QueryRequestOptions { MaxItemCount = pageSize };
string continuationToken = null;
List<Family> familyResults = new List<Family>();
IQueryable<Family> query = this._container
.GetItemLinqQueryable<Family>(false, continuationToken, options)
.Where(i => i.Address.ZipCode == zipCode)
.Skip(itemsToSkip);
using (FeedIterator<Family> iterator = query.ToFeedIterator<Family>())
{
FeedResponse<Family> feedResponse = await iterator.ReadNextAsync();
foreach (Family item in feedResponse)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipCode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipCode));
}
}
If you are searching for something with the partition key and id then that can only return a single item as id must be unique within a pk. So GetFamilyDataAsync() can be implemented using ReadItemAsync() rather than a query. PS: Not sure why you would ever do this, allowSynchronousQueryExecution: true
GetFamilyDataFromId() also can be implemented using ReadItemAsync(). Even if this did require a query, it doesn't make sense to implement to return a stream, then deserialize into an object. Just use the variant that can deserialize directly into a POCO.
Can't say if this is completely optimal for everything, but you can go here to find more things as a place to start, https://learn.microsoft.com/en-us/azure/cosmos-db/sql/best-practice-dotnet
The other thing to call out is zip code as a partition key. This might work at small scale, but it could run into issues at larger scale depending on how many families and how many records for each family are stored within a single partition. Max size for a single partition key value is 20 GB. Fine for small workloads but possibly an issue at larger scale.

Using MQTT ManagedClient with ASP NET API, how to?

I'm currently working on a project that has to rely heavily on MQTT - one of the parts that needs to utilize MQTT is a ASP Net API, but I'm having difficulties receiving messages.
Here is my MQTTHandler:
public MQTTHandler()
{
_mqttUrl = Properties.Resources.mqttURL ?? "";
_mqttPort = Properties.Resources.mqttPort ?? "";
_mqttUsername = Properties.Resources.mqttUsername ?? "";
_mqttPassword = Properties.Resources.mqttUsername ?? "";
_mqttFactory = new MqttFactory();
_tls = false;
}
public async Task<IManagedMqttClient> ConnectClientAsync()
{
var clientID = Guid.NewGuid().ToString();
var messageBuilder = new MqttClientOptionsBuilder()
.WithClientId(clientID)
.WithCredentials(_mqttUsername, _mqttPassword)
.WithTcpServer(_mqttUrl, Convert.ToInt32(_mqttPort));
var options = _tls ? messageBuilder.WithTls().Build() : messageBuilder.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
_mqttClient = new MqttFactory().CreateManagedMqttClient();
await _mqttClient.StartAsync(managedOptions);
Console.WriteLine("Klient startet");
return _mqttClient;
}
public async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1)
{
await _mqttClient.EnqueueAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build());
Console.WriteLine("Besked published");
}
public async Task SubscribeAsync(string topic, int qos = 1)
{
var topicFilters = new List<MQTTnet.Packets.MqttTopicFilter>
{
new MqttTopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)(qos))
.Build()
};
await _mqttClient.SubscribeAsync(topicFilters);
}
public Status GetSystemStatus(MqttApplicationMessageReceivedEventArgs e)
{
try
{
var json = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
var status = JsonSerializer.Deserialize<Status>(json);
if (status != null)
{
return status;
}
else
{
return null;
}
}
catch (Exception)
{
throw;
}
}
The above has been tested with a console app and works as it should.
The reason I need MQTT in the APi is that a POST method has to act on the value of a topic;
In particular I need to check a systems status before allowing the post;
[HttpPost]
public async Task<ActionResult<Order>> PostOrder(Order order)
{
if (_lastStatus != null)
{
if (_lastStatus.OpStatus)
{
return StatusCode(400, "System is busy!");
}
else
{
var response = await _orderManager.AddOrder(order);
return StatusCode(response.StatusCode, response.Message);
}
}
return StatusCode(400, "Something went wrong");
}
So I will need to set up a subscriber for this controller, and set the value of _lastStatus on received messages:
private readonly MQTTHandler _mqttHandler;
private IManagedMqttClient _mqttClient;
private Status _lastStatus;
public OrdersController(OrderManager orderManager)
{
_orderManager = orderManager;
_mqttHandler = new MQTTHandler();
_mqttClient = _mqttHandler.ConnectClientAsync().Result;
_mqttHandler.SubscribeAsync("JSON/Status");
_mqttClient.ApplicationMessageReceivedAsync += e =>
{
_lastStatus = _mqttHandler.GetSystemStatus(e);
return Task.CompletedTask;
};
}
However, it's behaving a little odd and I'm not experienced enough to know why.
The first time I make a POST request, _lastStatus is null - every following POST request seem to have the last retained message.
I'm guessing that I am struggling due to stuff being asynchronous, but not sure, and every attempt I've attempted to make it synchronous have failed.
Anyone have a clue about what I'm doing wrong?

Upload CSV data into SQL database using ASP.NET Core MVC

I am trying to insert data from a .csv file into my database, but anytime I upload data, the record is empty.
This is my code so far:
[HttpPost]
public async Task<IActionResult> ImportFromExcel(IFormFile formFile)
{
var data = new MemoryStream();
await formFile.CopyToAsync(data);
data.Position = 0;
TextReader reader = new StreamReader(data);
var csvReader = new CsvReader(reader, new CsvConfiguration(System.Globalization.CultureInfo.CurrentCulture)
{
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null
});
var Name = csvReader.GetField(0).ToString();
var dep = "cccccccccc";
var pos = "bbbbbbbbbbb";
await dcx.Participants.AddAsync(new Participant
{
Name = Name,
Position = pos,
Department = dep,
});
dcx.SaveChanges();
return ViewComponent("ViewParticipants");
}
This is the sample data in my database table:
As long as the headers of your CSV match up to the names of the columns in your database, you should be able to do something like this. If the names don't match, you can use .Name("CsvColumnName") in ParticipantMap to add the name of the column in the CSV file. Example: Map(r => r.Description).Name("MyCsvDescription");.
[HttpPost]
public async Task<IActionResult> ImportFromExcel(IFormFile formFile)
{
var data = new MemoryStream();
await formFile.CopyToAsync(data);
data.Position = 0;
var conf = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null,
BadDataFound = context =>
{
bad.Add(context.RawRecord);
}
};
using (TextReader reader = new StreamReader(data))
using (var csvReader = new CsvReader(reader, config)) {
csvReader.Context.RegisterClassMap<ParticipantMap>();
var records = csvReader.GetRecords<Participant>().ToList();
var dep = "cccccccccc";
var pos = "bbbbbbbbbbb";
records.ForEach(r =>
{
r.Department = dep;
r.Position = pos;
});
await dcx.Participants.AddRangeAsync(records);
dcx.SaveChanges();
}
return ViewComponent("ViewParticipants");
}
public class ParticipantMap : ClassMap<Participant>
{
public ParticipantMap()
{
AutoMap(CultureInfo.InvariantCulture);
Map(r => r.Department).Ignore();
Map(r => r.Position).Ignore();
}
}
In my opinion, you should call csvReader.Read() to read the file row first.
You can refer to the following test code, it works fine.
[HttpPost]
public async Task<IActionResult> ImportFromExcel(IFormFile formFile)
{
var data = new MemoryStream();
await formFile.CopyToAsync(data);
data.Position = 0;
using (var reader = new StreamReader(data))
{
var bad = new List<string>();
var conf = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null,
BadDataFound = context =>
{
bad.Add(context.RawRecord);
}
};
using (var csvReader = new CsvReader(reader, conf))
{
while (csvReader.Read())
{
var Name = csvReader.GetField(0).ToString();
var pos = csvReader.GetField(1).ToString();
var dep = csvReader.GetField(2).ToString();
await dcx.Participants.AddAsync(new Participant
{
Name = Name,
Position = pos,
Department = dep,
});
dcx.SaveChanges();
}
}
}
return ViewComponent("ViewParticipants");
}
Test Result:

Passing list of object to Web API using RestSharp Client

I'm trying to send list of objects from MVC to WEBAPI using below methods. API is able to able receive the list from controller but, value of each item in the list is either empty/null on API side.
Can anyone please help me to fix this?
Controller Method:
private List<FCM.Models.Facility> GetFacilityDetails()
{
var url = "http://localhost:64664/";
var facilies = new List<Facility>();
facilies.Add( new Facility{ FCLT_ID = 100, FCLT_NM = "Facility 100" });
facilies.Add( new Facility{ FCLT_ID = 200, FCLT_NM = "Facility 200" });
facilies.Add( new Facility{ FCLT_ID = 300, FCLT_NM = "Facility 300" });
var json = JsonConvert.SerializeObject(facilies);
var _client = new RestClient(url);
var request = new RestRequest("api/facility/details", Method.GET) { RequestFormat = DataFormat.Json };
facilies.ForEach(fclt =>
request.AddParameter("facilites", fclt, ParameterType.GetOrPost));
var response = _client.Execute<List<FCM.Models.Facility>>(request);
if (response.Data == null)
{
throw new Exception(response.ErrorMessage);
}
return response.Data;
}
WebAPI method:
[Route("api/facility/details")]
public IEnumerable<Facility> GetFullAddress([FromUri] IEnumerable<Facility> facilities)
{
return null;
}
Like the comment suggested you maybe want to issue a POST request instead, but if you would like to send an array with a GETrequest you could do it like this (with System.Net.Http.HttpClient):
Add a Format method to you Facility class:
public class Facility
{
public int FCLT_ID { get; set; }
public string FCLT_NM { get; set; }
public string Format(int index)
{
return $"[{index}].FCLT_ID={FCLT_ID}&[{index}].FCLT_NM={FCLT_NM}";
}
}
Define a class which can format the array values:
public class FacilityList : List<Facility>
{
public string Format()
{
var builder = new StringBuilder();
for (var i = 0; i < Count; i++)
{
builder.Append(this[i].Format(i));
if(i != Count -1)
{
builder.Append("&");
}
}
return builder.ToString();
}
}
And then issue the request:
var client = new HttpClient()
{
BaseAddress = new Uri("http://localhost:64664/"),
DefaultRequestHeaders = {Accept = {new MediaTypeWithQualityHeaderValue("application/json")}}
};
var facilities = new FacilityList
{
new Facility {FCLT_ID = 100, FCLT_NM = "Facility 100"},
new Facility {FCLT_ID = 200, FCLT_NM = "Facility 200"},
new Facility {FCLT_ID = 300, FCLT_NM = "Facility 300"}
};
var format = facilities.Format();
var response = client.GetAsync("api/facility/details?" + format).GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<IEnumerable<Facility>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());
This will bind to your controller action:
[Route("api/facility/details")]
public IHttpActionResult Get([FromUri] IEnumerable<Facility> facilities)
{
// Do stuff..
return Ok(facilities);
}

RavenDB: Can't retrieve individual Ranged Facets

I have some Ranged Facets defined in a FacetSetup document. I like having the ability to retrieve individual Facets from a FacetSetup (by specifying them instead of the Id of the FacetSetup in my call to ToFacets()), so I tried to do that with these Ranged Facets but have been unsuccessful so far.
Here is my failing test. Any tips?
using Raven.Abstractions.Data;
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Embedded;
using Raven.Tests.Helpers;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace RavenDBTests
{
public class RangedFacetTests : RavenTestBase
{
[Fact]
public void RangedFacetTest()
{
using (EmbeddableDocumentStore documentStore = NewDocumentStore())
{
// create and store an index
Dictionary<string, string> analyzers = new Dictionary<string, string>();
analyzers.Add("MyProperty", "");
Dictionary<string, SortOptions> sortOptions = new Dictionary<string, SortOptions>();
sortOptions.Add("MyProperty", SortOptions.Long);
IndexDefinition indexDefinition = new IndexDefinition()
{
Analyzers = analyzers,
SortOptions = sortOptions,
Map = #"from d in docs
select new
{
MyProperty = d.MyProperty
}",
};
documentStore.DatabaseCommands.PutIndex("MyIndex", indexDefinition);
using (IDocumentSession documentSession = documentStore.OpenSession())
{
// store some sample documents
documentSession.Store(new { MyProperty = 10 });
documentSession.Store(new { MyProperty = 25 });
documentSession.Store(new { MyProperty = 100 });
// store a facetsetup with one ranged facet
documentSession.Store(new FacetSetup
{
Id = "facets/MyFacetSetup",
Facets = new List<Facet>()
{
new Facet()
{
Mode = FacetMode.Ranges,
Name = "MyProperty_Range",
Ranges = new List<string>()
{
"[0x0000000000000001 TO 0x0000000000000032]"
}
}
}
}, "facets/MyFacetSetup");
documentSession.SaveChanges();
}
// let that process
WaitForIndexing(documentStore);
using (IDocumentSession documentSession = documentStore.OpenSession())
{
// retrieve ALL facets
FacetResults facetResults1 = documentSession.Query<dynamic>("MyIndex").ToFacets("facets/MyFacetSetup");
Xunit.Assert.True(facetResults1.Results.Values.First().Values.First().Hits > 0);
// retrieve SPECIFIED facets
FacetResults facetResults2 = documentSession.Query<dynamic>("MyIndex").ToFacets(new List<Facet>()
{
new Facet()
{
Mode = FacetMode.Ranges,
Name = "MyProperty_Range"
}
},
0,
null);
// this fails: why can't I specify the ranged facet?
Xunit.Assert.True(facetResults2.Results.Values.First().Values.First().Hits > 0);
}
}
}
}
}
You don't specify what the actual ranges are in the code.
In RavenDB, you have two ways to create facets. One is to specify the facet doc id, and the second is to actually pass the facets.
In this case, you are passing a range facets without any ranges, so it returns no results.
Use this code:
FacetResults facetResults2 = documentSession.Query<dynamic>("MyIndex").ToFacets(new List<Facet>()
{
new Facet()
{
Mode = FacetMode.Ranges,
Name = "MyProperty_Range",
Ranges = new List<string>()
{
"[0x0000000000000001 TO 0x0000000000000032]"
}
}
},
0,
null);