'Is there a more Functional way to aggregate a collection into set of Mongo Filters conjugated by OR?

We have a microservice which enables consumers (indirectly) to query a database, where we have our own logic to limit what consumers can do with the queries.

When consumers provide a list of filters which should be joined with AND logic, we can elegantly aggregate them like this:

public FilterDefinition<BsonDocument> AggregateAndFilters(ISObjectConfigurable config, JArray andFilters) =>
    andFilters.Aggregate(
    Builders<BsonDocument>.Filter.Empty,
    (accumulatedFilters, andFilter) =>
            {
                accumulatedFilters &= BuildFilters(config, andFilter);
                return accumulatedFilters;
            });

BuildFilters is a function which returns an instance of MongoDb's FilterDefinition<BsonDocument> which can be combined with other instances of FilterDefinition<BsonDocument>. &= (which seems to be overloaded) tells the system that the target of the filter should meet both conditions (i.e., the two conditions are joined by "and").

We need to do something similar with "or", for which we can use the |= operator.

This will NOT work:

    public FilterDefinition<BsonDocument> AggregateOrFilters(ISObjectConfigurable config, JArray andFilters) =>
        orFilters.Aggregate(
        Builders<BsonDocument>.Filter.Empty,
        (accumulatedFilters, andFilter) =>
                {
                    accumulatedFilters |= BuildFilters(config, andFilter);
                    return accumulatedFilters;
                });

The first problem: The seed can't be an empty filter. If we try seeding Aggregate() with an empty filter, the result is "empty filter or ...". Everything will meet the condition of the empty filter and thus nothing will be filtered.

The so, we would need to the seed to be the first filter. But this assumes there will be a first filter. So, we need to be able to pass back the empty filter if there are no filters. And if there is a first filter, we don't want to duplicate it as we iterate over conditions to join with "or" to this filter.

Thus, it seems we need to use control structures and the resulting code looks like this:

public FilterDefinition<BsonDocument> AggregateOrFilters(ISObjectConfigurable config, JArray orFilterArray)
{
    List<JToken> orFilters = orFilterArray.ToObject<List<JToken>>();
    if (orFilters.IsNullOrEmpty())
    {
        return Builders<BsonDocument>.Filter.Empty;
    }

    FilterDefinition<BsonDocument> accumulatedFilters = BuildFilters(config, orFilters[0]);
    if (orFilters.Count > 1)
    {
        for (int i = 1; i < orFilters.Count; i++)
        {
            accumulatedFilters |= BuildFilters(config, orFilters[i]);
        }
    }

    return accumulatedFilters;
}

This works, but I'm wondering if there is a more elegant (e.g. Functional) way to express this?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source