MySQL · RetrieveMultiple

A contact search screen

A contact search screen, in three scenarios: a free-text box (Contains, case-insensitive, on two columns with OR); filters the user ticked (an In list); and text with wildcards in it, searched literally.

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).

Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:

Set up
var contacts = new List<ArxEntity>();
foreach (var (first, last, title, channel) in new[]
{
    ("Maria", "Search-R601", "Sales Manager", "E"),
    ("Mario", "Search-R601", "Engineer", "P"),
    ("Anna", "Marino-R601", "100% remote engineer", "E"),
    ("Tom", "Search-R601", "Buyer", "S"),
})
{
    var contact = new ArxEntity("contact");
    contact["first_name"] = first;
    contact["last_name"] = last;
    contact["job_title"] = title;
    contact["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", channel[0]);
    contacts.Add(contact);
}
await service.CreateMultipleAsync(contacts);

1 A free-text box: first or last name contains "mari"

The user types "mari": first name or last name contains it, in any case.

Already in scope: service (ArxMySqlDbService)

C#
var query = Contacts();
var text = query.Criteria.AddFilter(ArxLogicalOperator.Or);
text.AddCondition("first_name", ArxConditionOperator.Contains, "mari");
text.AddCondition("last_name", ArxConditionOperator.Contains, "mari");

var found = await service.RetrieveMultipleAsync(query);
Console.WriteLine(string.Join(", ", found.Entities.Select(x => $"{x["first_name"]} {x["last_name"]}")));
Used above

Defined once in this sample.

private static ArxQueryExpression Contacts()
{
    var query = new ArxQueryExpression("contact");
    query.ColumnSet.AddColumns("first_name", "last_name", "job_title");
    var ours = query.Criteria.AddFilter(ArxLogicalOperator.Or);
    ours.AddCondition("last_name", ArxConditionOperator.EndsWith, "-R601");
    query.AddOrder("first_name", ArxOrderType.Ascending);
    return query;
}

Check it in the database

SQL

Run it in any SQL tool connected to the database, after the code has run.

SELECT first_name, last_name
FROM contact
WHERE last_name LIKE '%-R601' AND (first_name LIKE '%mari%' OR last_name LIKE '%mari%')
ORDER BY first_name;

Expect: 3 rows, Anna Marino-R601, Maria Search-R601, Mario Search-R601.

2 Ticked filters: preferred channel e-mail or phone

The user ticked "e-mail" and "phone" as preferred channels: one In list, one parameter a value.

Already in scope: service (ArxMySqlDbService)

C#
var query = Contacts();
query.Criteria.AddCondition("preferred_channel", ArxConditionOperator.In, "E", "P");

var found = await service.RetrieveMultipleAsync(query);
Console.WriteLine(string.Join(", ", found.Entities.Select(x => x["first_name"])));
Used above

Defined once in this sample.

private static ArxQueryExpression Contacts()
{
    var query = new ArxQueryExpression("contact");
    query.ColumnSet.AddColumns("first_name", "last_name", "job_title");
    var ours = query.Criteria.AddFilter(ArxLogicalOperator.Or);
    ours.AddCondition("last_name", ArxConditionOperator.EndsWith, "-R601");
    query.AddOrder("first_name", ArxOrderType.Ascending);
    return query;
}

Check it in the database

SQL

Run it in any SQL tool connected to the database, after the code has run.

SELECT first_name
FROM contact
WHERE last_name LIKE '%-R601' AND preferred_channel IN ('E', 'P')
ORDER BY first_name;

Expect: 3 rows, Anna, Maria, Mario.

3 "100%" is searched as text, not as a pattern

"100%" typed into the box: the % is a character to find, not a wildcard.

Already in scope: service (ArxMySqlDbService)

C#
var query = Contacts();
query.Criteria.AddCondition("job_title", ArxConditionOperator.Contains, "100%");

var found = await service.RetrieveMultipleAsync(query);
Console.WriteLine(string.Join(", ", found.Entities.Select(x => $"{x["first_name"]}: {x["job_title"]}")));
Used above

Defined once in this sample.

private static ArxQueryExpression Contacts()
{
    var query = new ArxQueryExpression("contact");
    query.ColumnSet.AddColumns("first_name", "last_name", "job_title");
    var ours = query.Criteria.AddFilter(ArxLogicalOperator.Or);
    ours.AddCondition("last_name", ArxConditionOperator.EndsWith, "-R601");
    query.AddOrder("first_name", ArxOrderType.Ascending);
    return query;
}

Check it in the database

SQL

Run it in any SQL tool connected to the database, after the code has run.

SELECT first_name, job_title
FROM contact
WHERE last_name LIKE '%-R601' AND LOCATE('100%', job_title) > 0;

Expect: 1 row, Anna, 100% remote engineer.

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

  1. .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target net10.0).
  2. Create the project:
    Terminal
    dotnet new console -n ArxSample -f net10.0
    cd ArxSample
  3. Install the packages:
    Terminal
    dotnet add package ARX.SDK.MySQL --version 1.0.0-rc.1
  4. Database: a MySQL 8.4 (tested with 8.4.13) database with the sample schema.
  5. Configuration: set ARX_SAMPLES_MYSQL to its connection string. Nothing is hard-coded.
  6. Source code: add the files of the sample application this sample uses (SampleContext.cs, SampleOutput.cs, SampleContext.Application.cs, SampleCatalog.cs), then replace Program.cs and add the other file below, with the file names shown.
  7. Run it:
    Terminal
    dotnet run
  8. Expected output: below.

The files

Program.cs

Runs the sample. Generated for this page; it reads the connection string from the environment.

using ArxSamples.MySQL;
using ArxSamples.MySQL.Operations.RetrieveMultiple.RealCases;

// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_MYSQL")
    ?? throw new InvalidOperationException("Set the ARX_SAMPLES_MYSQL environment variable to your MySQL connection string.");

await new RealCase1_SearchContacts().RunAsync(new SampleContext(connectionString));
RealCase1_SearchContacts.cs

The sample, as it is in the repository.

using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MySQL.SampleOutput;

namespace ArxSamples.MySQL.Operations.RetrieveMultiple.RealCases;

/// <summary>
/// A contact search screen, in three scenarios: a free-text box (Contains, case-insensitive, on two columns with OR);
/// filters the user ticked (an In list); and text with wildcards in it, searched literally.
/// </summary>
public sealed class RealCase1_SearchContacts : ISample
{
    public string Name => "retrieve-multiple-real-1-search-contacts";

    public string Title => "A contact search screen";

    public async Task RunAsync(SampleContext dbContext)
    {
        var contacts = new List<ArxEntity>();

        foreach (var (first, last, title, channel) in new[]
        {
            ("Maria", "Search-R601", "Sales Manager", "E"),
            ("Mario", "Search-R601", "Engineer", "P"),
            ("Anna", "Marino-R601", "100% remote engineer", "E"),
            ("Tom", "Search-R601", "Buyer", "S"),
        })
        {
            var contact = new ArxEntity("contact");
            contact["first_name"] = first;
            contact["last_name"] = last;
            contact["job_title"] = title;
            contact["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", channel[0]);
            contacts.Add(contact);
        }

        await dbContext.Service.CreateMultipleAsync(contacts);

        await Scenario1_AFreeTextBoxAsync(dbContext);

        await Scenario2_TickedFiltersAsync(dbContext);

        await Scenario3_WildcardsAreSearchedLiterallyAsync(dbContext);
    }

    /// <summary>The query every scenario starts from: this sample's contacts, by first name.</summary>
    private static ArxQueryExpression Contacts()
    {
        var query = new ArxQueryExpression("contact");
        query.ColumnSet.AddColumns("first_name", "last_name", "job_title");
        var ours = query.Criteria.AddFilter(ArxLogicalOperator.Or);
        ours.AddCondition("last_name", ArxConditionOperator.EndsWith, "-R601");
        query.AddOrder("first_name", ArxOrderType.Ascending);
        return query;
    }

    /// <summary>1. The user types "mari": first name or last name contains it, in any case.</summary>
    private static async Task Scenario1_AFreeTextBoxAsync(SampleContext dbContext)
    {
        Step("1. A free-text box: first or last name contains \"mari\"");

        var query = Contacts();
        var text = query.Criteria.AddFilter(ArxLogicalOperator.Or);
        text.AddCondition("first_name", ArxConditionOperator.Contains, "mari");
        text.AddCondition("last_name", ArxConditionOperator.Contains, "mari");

        var found = await dbContext.Service.RetrieveMultipleAsync(query);
        Show(string.Join(", ", found.Entities.Select(x => $"{x["first_name"]} {x["last_name"]}")));

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT first_name, last_name
         * FROM contact
         * WHERE last_name LIKE '%-R601' AND (first_name LIKE '%mari%' OR last_name LIKE '%mari%')
         * ORDER BY first_name;
         * -- Expect: 3 rows, Anna Marino-R601, Maria Search-R601, Mario Search-R601.
         * ===========================================================
         */
    }

    /// <summary>2. The user ticked "e-mail" and "phone" as preferred channels: one In list, one parameter a value.</summary>
    private static async Task Scenario2_TickedFiltersAsync(SampleContext dbContext)
    {
        Step("2. Ticked filters: preferred channel e-mail or phone");

        var query = Contacts();
        query.Criteria.AddCondition("preferred_channel", ArxConditionOperator.In, "E", "P");

        var found = await dbContext.Service.RetrieveMultipleAsync(query);
        Show(string.Join(", ", found.Entities.Select(x => x["first_name"])));

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT first_name
         * FROM contact
         * WHERE last_name LIKE '%-R601' AND preferred_channel IN ('E', 'P')
         * ORDER BY first_name;
         * -- Expect: 3 rows, Anna, Maria, Mario.
         * ===========================================================
         */
    }

    /// <summary>3. "100%" typed into the box: the % is a character to find, not a wildcard.</summary>
    private static async Task Scenario3_WildcardsAreSearchedLiterallyAsync(SampleContext dbContext)
    {
        Step("3. \"100%\" is searched as text, not as a pattern");

        var query = Contacts();
        query.Criteria.AddCondition("job_title", ArxConditionOperator.Contains, "100%");

        var found = await dbContext.Service.RetrieveMultipleAsync(query);
        Show(string.Join(", ", found.Entities.Select(x => $"{x["first_name"]}: {x["job_title"]}")));

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT first_name, job_title
         * FROM contact
         * WHERE last_name LIKE '%-R601' AND LOCATE('100%', job_title) > 0;
         * -- Expect: 1 row, Anna, 100% remote engineer.
         * ===========================================================
         */
    }
}

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.

Output
  > 1. A free-text box: first or last name contains "mari"
      Anna Marino-R601, Maria Search-R601, Mario Search-R601
  > 2. Ticked filters: preferred channel e-mail or phone
      Anna, Maria, Mario
  > 3. "100%" is searched as text, not as a pattern
      Anna: 100% remote engineer