Search code examples
c#linqfilterlambda

C# LinQ: Filter object[] with elements containing a string (or part of it) from a string[]


I have a "Licenses" query which returns a Licenses[] object as follows:

var licenses = await _licenseRepository.SearchAsync(ct); //=> CT = CancellationToken

and a request filter which allows to filer by Id or Name:

public sealed record Filter(LicenseTypeValues[] Id, string[] Name);

So I should be able to pass a string[] as "Name" filter (for example) and I should return every License which name contains any of the names in the string[] -or part of them-.

Let's consider the following return from the licenses query with no filter:

{
    "count": 5,
    "results": [
        {
            "id": 15005,
            "name": "Windows 2012",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15027,
            "name": "Windows 2016",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15030,
            "name": "Windows 2022",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-08-11T10:29:36Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 23",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 24",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        }
    ]
}

Then, if I pass a filter like the following:

["Windows 201", "Ubuntu 23"]

the response should be:

{
    "count": 3,
    "results": [
        {
            "id": 15005,
            "name": "Windows 2012",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15027,
            "name": "Windows 2016",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 23",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        }
    ]
}

My first attempt using just a simple LinQ with a lambda expression was:

var licenses = await _repository.FindAll().Where(x => filter.Name.Contains(x.Name)).ToArrayAsync();

The problem with this query is that only works with exact matches (ex: "Windows 2019" or "Ubuntu 23"), but not partial matches.

Of course I could just iterate with a foreach loop and filter every element one by one, but I'd like to find a more elegant solution using LinQ, if possible.

Any suggestions?

Edit 1: Explanation of the problem described above.

What I'm trying to say with "my example query only works with exact matches" is that if I search, for example, ["Windows 2016"], or, another example, ["Windows 2012", "Ubuntu 23"] it will work, but with ["Windows 20"] or ["Ubunbtu", "Windows"] won't, because the match is not exact, and this is what my example does, just works with exact matches, so it works partially.


Solution

  • I think using .Any() is helpful here.

    var licenses = await _repository.FindAll().Where(x => filter.Name.Any(f => x.Name.Contains(f)).ToArrayAsync();
    

    Since filter.Name is also a list, it can use LINQ expressions. Also, f should now be contained in x.Name.

    Hope that helps!