diff --git a/src/Meilisearch/Index.cs b/src/Meilisearch/Index.cs index 686459ae..3c5b5a41 100644 --- a/src/Meilisearch/Index.cs +++ b/src/Meilisearch/Index.cs @@ -133,6 +133,25 @@ public async Task AddDocuments(IEnumerable documents, string return await responseMessage.Content.ReadFromJsonAsync(); } + /// + /// Adds documents in batches with size specified with . + /// + /// Documents to add. + /// Size of documents batches while adding them. + /// Primary key for the documents. + /// Type of the document. Even though documents are schemaless in MeiliSearch, making it typed helps in compile time. + /// Returns the updateID of this async operation. + public async Task> AddDocumentsInBatches(IEnumerable documents, int batchSize = 1000, string primaryKey = default) + { + async Task AddAction(List items, List updates) + { + updates.Add(await this.AddDocuments(items, primaryKey)); + } + + var result = await BatchOperation(documents, batchSize, AddAction); + return result; + } + /// /// Update documents. /// @@ -155,6 +174,25 @@ public async Task UpdateDocuments(IEnumerable documents, str return await responseMessage.Content.ReadFromJsonAsync(); } + /// + /// Updates documents in batches with size specified with . + /// + /// Documents to update. + /// Size of documents batches while updating them. + /// Primary key for the documents. + /// Type of the document. Even though documents are schemaless in MeiliSearch, making it typed helps in compile time. + /// Returns the updateID of this async operation. + public async Task> UpdateDocumentsInBatches(IEnumerable documents, int batchSize = 1000, string primaryKey = default) + { + async Task UpdateAction(List items, List updates) + { + updates.Add(await this.UpdateDocuments(items, primaryKey)); + } + + var result = await BatchOperation(documents, batchSize, UpdateAction); + return result; + } + /// /// Get document by its ID. /// @@ -621,5 +659,19 @@ internal Index WithHttpClient(HttpRequest http) this.http = http; return this; } + + private static async Task> BatchOperation(IEnumerable items, int batchSize, Func, List, Task> action) + { + var itemsList = new List(items); + var numberOfBatches = Math.Ceiling((double)itemsList.Count / batchSize); + var result = new List(); + for (var i = 0; i < numberOfBatches; i++) + { + var batch = itemsList.GetRange(i * batchSize, batchSize); + await action.Invoke(batch, result); + } + + return result; + } } } diff --git a/tests/Meilisearch.Tests/DocumentTests.cs b/tests/Meilisearch.Tests/DocumentTests.cs index 25a9426b..647dc45b 100644 --- a/tests/Meilisearch.Tests/DocumentTests.cs +++ b/tests/Meilisearch.Tests/DocumentTests.cs @@ -36,6 +36,35 @@ public async Task BasicDocumentsAddition() docs.First().Genre.Should().BeNull(); } + [Fact] + public async Task BasicDocumentsAdditionInBatches() + { + var indexUID = "BasicDocumentsAdditionInBatchesTest"; + Index index = this.client.Index(indexUID); + + // Add the documents + Movie[] movies = + { + new Movie { Id = "1", Name = "Batman" }, + new Movie { Id = "2", Name = "Reservoir Dogs" }, + new Movie { Id = "3", Name = "Taxi Driver" }, + new Movie { Id = "4", Name = "Interstellar" }, + }; + var updates = await index.AddDocumentsInBatches(movies, 2); + foreach (var u in updates) + { + u.UpdateId.Should().BeGreaterOrEqualTo(0); + await index.WaitForPendingUpdate(u.UpdateId); + } + + // Check the documents have been added (one movie from each batch) + var docs = (await index.GetDocuments()).ToList(); + Assert.Equal("1", docs.ElementAt(0).Id); + Assert.Equal("Batman", docs.ElementAt(0).Name); + Assert.Equal("3", docs.ElementAt(2).Id); + Assert.Equal("Taxi Driver", docs.ElementAt(2).Name); + } + [Fact] public async Task BasicDocumentsAdditionWithCreateIndex() { @@ -124,6 +153,50 @@ public async Task BasicDocumentsUpdate() docs.ElementAt(1).Genre.Should().BeNull(); } + [Fact] + public async Task BasicDocumentsUpdateInBatches() + { + var indexUID = "BasicDocumentsUpdateInBatchesTest"; + Index index = this.client.Index(indexUID); + + // Add the documents + Movie[] movies = + { + new Movie { Id = "1", Name = "Batman" }, + new Movie { Id = "2", Name = "Reservoir Dogs" }, + new Movie { Id = "3", Name = "Taxi Driver" }, + new Movie { Id = "4", Name = "Interstellar" }, + }; + var updates = await index.AddDocumentsInBatches(movies, 2); + foreach (var u in updates) + { + u.UpdateId.Should().BeGreaterOrEqualTo(0); + await index.WaitForPendingUpdate(u.UpdateId); + } + + movies = new Movie[] + { + new Movie { Id = "1", Name = "Batman", Genre = "Action" }, + new Movie { Id = "2", Name = "Reservoir Dogs", Genre = "Drama" }, + new Movie { Id = "3", Name = "Taxi Driver", Genre = "Drama" }, + new Movie { Id = "4", Name = "Interstellar", Genre = "Sci-Fi" }, + }; + updates = await index.UpdateDocumentsInBatches(movies, 2); + foreach (var u in updates) + { + u.UpdateId.Should().BeGreaterOrEqualTo(0); + await index.WaitForPendingUpdate(u.UpdateId); + } + + // Assert movies have genre after update + var docs = (await index.GetDocuments()).ToList(); + foreach (var movie in docs) + { + movie.Genre.Should().NotBeNull(); + movie.Genre.Should().NotBeEmpty(); + } + } + [Fact] public async Task DocumentsUpdateWithPrimaryKey() { diff --git a/tests/Meilisearch.Tests/SearchTests.cs b/tests/Meilisearch.Tests/SearchTests.cs index 08ca8681..fa922afc 100644 --- a/tests/Meilisearch.Tests/SearchTests.cs +++ b/tests/Meilisearch.Tests/SearchTests.cs @@ -158,7 +158,7 @@ public async Task CustomSearchWithFilterWithSpaces() }); movies.Hits.Should().NotBeEmpty(); movies.FacetsDistribution.Should().BeNull(); - Assert.Equal(1, movies.Hits.Count()); + Assert.Single(movies.Hits); Assert.Equal("1344", movies.Hits.First().Id); Assert.Equal("The Hobbit", movies.Hits.First().Name); }