MongoDB · RetrieveMultiple
Read many records
Read many documents with conditions and an order, in three scenarios: comparisons and an order, text patterns, and what null means on MongoDB.
Set up for this page
The scenarios build on the sample application: SampleContext.cs (the dbContext every sample is given); SampleOutput.cs (Step and Show, which print); SampleContext.Application.cs (dbContext.Service, the application's ARX service); SampleCatalog.cs (the tables' ids, keys, lookups and display names); SampleContext.Direct.cs (dbContext.Direct); MongoDirectHelper.cs (the checks behind dbContext.Direct).
dbContext.Direct is the samples' own checking code on the plain driver, not part of ARX: leave it out of your application.
Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:
var contacts = new List<ArxEntity>();
foreach (var (first, last, score) in new[] { ("Ana", "Silva", (int?)80), ("Bruno", "Costa", 55), ("Carla", "Sá", null), ("Diogo", "Silva (Jr.)", 91) })
{
var contact = new ArxEntity("contact");
contact["first_name"] = first;
contact["last_name"] = last;
contact["segment"] = "M96";
if (score != null)
{
contact["score"] = score;
}
contacts.Add(contact);
}
await service.CreateMultipleAsync(contacts);1 Comparisons and an order
A score of 60 or more, best first.
Already in scope: service (ArxMongoDbService)
var query = Segment();
query.Criteria.AddCondition("score", ArxConditionOperator.GreaterEqual, 60);
query.AddOrder("score", ArxOrderType.Descending);
var page = await service.RetrieveMultipleAsync(query);
Console.WriteLine(string.Join(", ", page.Entities.Select(x => $"{x["first_name"]} {x["score"]}")));Defined once in this sample.
private static ArxQueryExpression Segment()
{
var query = new ArxQueryExpression("contact");
query.ColumnSet.AddColumns("first_name", "last_name", "score");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M96");
return query;
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.contact.find({ segment: "M96", score: { $gte: 60 } }, { first_name: 1, score: 1 }).sort({ score: -1 })Expect: 2 documents: Diogo 91, Ana 80.
2 Text patterns
Last names containing "silva", any case; "(Jr.)" is plain text, not a pattern.
Already in scope: service (ArxMongoDbService)
var silva = Segment();
silva.Criteria.AddCondition("last_name", ArxConditionOperator.Contains, "silva");
var junior = Segment();
junior.Criteria.AddCondition("last_name", ArxConditionOperator.EndsWith, "(Jr.)");
Console.WriteLine($"contains silva: {(await service.RetrieveMultipleAsync(silva)).Entities.Count}; ends with (Jr.): {(await service.RetrieveMultipleAsync(junior)).Entities.Count}");Defined once in this sample.
private static ArxQueryExpression Segment()
{
var query = new ArxQueryExpression("contact");
query.ColumnSet.AddColumns("first_name", "last_name", "score");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M96");
return query;
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.contact.countDocuments({ segment: "M96", last_name: /silva/i })Expect: 2.
3 What null means
Carla has no score: Null finds her; NotEqual 80 doesn't.
Already in scope: service (ArxMongoDbService)
var noScore = Segment();
noScore.Criteria.AddCondition("score", ArxConditionOperator.Null);
var notEighty = Segment();
notEighty.Criteria.AddCondition("score", ArxConditionOperator.NotEqual, 80);
Console.WriteLine($"no score: {string.Join(", ", (await service.RetrieveMultipleAsync(noScore)).Entities.Select(x => x["first_name"]))}; "
+ $"score not 80: {string.Join(", ", (await service.RetrieveMultipleAsync(notEighty)).Entities.Select(x => x["first_name"]))}");Defined once in this sample.
private static ArxQueryExpression Segment()
{
var query = new ArxQueryExpression("contact");
query.ColumnSet.AddColumns("first_name", "last_name", "score");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M96");
return query;
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.contact.find({ segment: "M96", score: null }, { first_name: 1 })Expect: 1 document, Carla (her document has no score field).
Good to know
Contains, BeginsWith and EndsWith ignore case, and their text is never read as a regular expression. Null matches a stored null and a missing field; NotEqual and the other negations never match either, as in SQL.
Run the complete sample
The code above comes from one program, built in a fresh project and run against the database. To run it yourself, here is every file and step.
The program, its files and how to run it
Run it locally
- .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target
net10.0). - Create the project:
Terminal dotnet new console -n ArxSample -f net10.0 cd ArxSample - Install the packages:
Terminal dotnet add package ARX.SDK.MongoDb --version 1.0.0-rc.1 - Database: a MongoDB 7.0, a replica set (tested with 7.0.45, one node) database with the sample schema.
- Configuration: set
ARX_SAMPLES_MONGODBto its connection string. Nothing is hard-coded. - Source code: add the files of the sample application this sample uses (
SampleContext.cs,SampleOutput.cs,SampleContext.Application.cs,SampleCatalog.cs,SampleContext.Direct.cs,MongoDirectHelper.cs), then replaceProgram.csand add the other file below, with the file names shown. - Run it:
Terminal dotnet run - Expected output: below.
The files
Runs the sample. Generated for this page; it reads the connection string from the environment and first remakes the sample database from sample-schema.json.
using ArxSamples.MongoDb;
using ArxSamples.MongoDb.Operations.RetrieveMultiple.Basics;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_MONGODB")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_MONGODB environment variable to your MongoDB connection string.");
var dbContext = new SampleContext(connectionString);
// Every run starts from the sample data: this deletes and makes again what sample-schema.json lists
// (save the file in the project folder, see Get started).
await dbContext.Direct.ResetAsync();
await new Basic1_RetrieveManyRecords().RunAsync(dbContext);
The sample, as it is in the repository.
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MongoDb.SampleOutput;
namespace ArxSamples.MongoDb.Operations.RetrieveMultiple.Basics;
/// <summary>
/// Read many documents with conditions and an order, in three scenarios: comparisons and an order, text patterns, and
/// what null means on MongoDB.
/// </summary>
/// <remarks>
/// Contains, BeginsWith and EndsWith ignore case, and their text is never read as a regular expression. <c>Null</c>
/// matches a stored null and a missing field; NotEqual and the other negations never match either, as in SQL.
/// </remarks>
public sealed class Basic1_RetrieveManyRecords : ISample
{
public string Name => "retrieve-multiple-basics-1-retrieve-many-records";
public string Title => "Read many records";
public async Task RunAsync(SampleContext dbContext)
{
var contacts = new List<ArxEntity>();
foreach (var (first, last, score) in new[] { ("Ana", "Silva", (int?)80), ("Bruno", "Costa", 55), ("Carla", "Sá", null), ("Diogo", "Silva (Jr.)", 91) })
{
var contact = new ArxEntity("contact");
contact["first_name"] = first;
contact["last_name"] = last;
contact["segment"] = "M96";
if (score != null)
{
contact["score"] = score;
}
contacts.Add(contact);
}
await dbContext.Service.CreateMultipleAsync(contacts);
await Scenario1_ComparisonsAndAnOrderAsync(dbContext.Service);
await Scenario2_TextPatternsAsync(dbContext.Service);
await Scenario3_WhatNullMeansAsync(dbContext.Service);
}
private static ArxQueryExpression Segment()
{
var query = new ArxQueryExpression("contact");
query.ColumnSet.AddColumns("first_name", "last_name", "score");
query.Criteria.AddCondition("segment", ArxConditionOperator.Equal, "M96");
return query;
}
/// <summary>1. A score of 60 or more, best first.</summary>
private static async Task Scenario1_ComparisonsAndAnOrderAsync(ArxMongoDbService service)
{
Step("1. Comparisons and an order");
var query = Segment();
query.Criteria.AddCondition("score", ArxConditionOperator.GreaterEqual, 60);
query.AddOrder("score", ArxOrderType.Descending);
var page = await service.RetrieveMultipleAsync(query);
Show(string.Join(", ", page.Entities.Select(x => $"{x["first_name"]} {x["score"]}")));
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.contact.find({ segment: "M96", score: { $gte: 60 } }, { first_name: 1, score: 1 }).sort({ score: -1 })
* -- Expect: 2 documents: Diogo 91, Ana 80.
* ===========================================================
*/
}
/// <summary>2. Last names containing "silva", any case; "(Jr.)" is plain text, not a pattern.</summary>
private static async Task Scenario2_TextPatternsAsync(ArxMongoDbService service)
{
Step("2. Text patterns");
var silva = Segment();
silva.Criteria.AddCondition("last_name", ArxConditionOperator.Contains, "silva");
var junior = Segment();
junior.Criteria.AddCondition("last_name", ArxConditionOperator.EndsWith, "(Jr.)");
Show($"contains silva: {(await service.RetrieveMultipleAsync(silva)).Entities.Count}; ends with (Jr.): {(await service.RetrieveMultipleAsync(junior)).Entities.Count}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.contact.countDocuments({ segment: "M96", last_name: /silva/i })
* -- Expect: 2.
* ===========================================================
*/
}
/// <summary>3. Carla has no score: Null finds her; NotEqual 80 doesn't.</summary>
private static async Task Scenario3_WhatNullMeansAsync(ArxMongoDbService service)
{
Step("3. What null means");
var noScore = Segment();
noScore.Criteria.AddCondition("score", ArxConditionOperator.Null);
var notEighty = Segment();
notEighty.Criteria.AddCondition("score", ArxConditionOperator.NotEqual, 80);
Show($"no score: {string.Join(", ", (await service.RetrieveMultipleAsync(noScore)).Entities.Select(x => x["first_name"]))}; "
+ $"score not 80: {string.Join(", ", (await service.RetrieveMultipleAsync(notEighty)).Entities.Select(x => x["first_name"]))}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.contact.find({ segment: "M96", score: null }, { first_name: 1 })
* -- Expect: 1 document, Carla (her document has no score field).
* ===========================================================
*/
}
}
Expected output
What dotnet run printed when the sample was verified, on a freshly created sample schema. Ids differ when the tables already hold rows.
> 1. Comparisons and an order
Diogo 91, Ana 80
> 2. Text patterns
contains silva: 2; ends with (Jr.): 1
> 3. What null means
no score: Carla; score not 80: Bruno, Diogo