Skip to main content

Reference

Reference​

Both overloads are in Vali_Flow.NoSql.CosmosDb.Extensions.ValiFlowCosmosDbExtensions.

ToCosmosDb<T>(this ValiFlow<T> flow, Func<object?, object?>? customConverter = null)​

Translates the conditions accumulated in a ValiFlow<T> builder into a CosmosFilterExpression.

ParameterTypeRequiredDescription
flowValiFlow<T>YesThe builder containing the conditions.
customConverterFunc<object?, object?>?NoHook for mapping CLR types not handled by the built-in switch into the raw value stored in the parameters dictionary. Return null to fall through to the default conversion.

Returns: CosmosFilterExpression — append WhereClause to your query text and bind Parameters to a QueryDefinition.

ToCosmosDb<T>(this Expression<Func<T, bool>> expression, Func<object?, object?>? customConverter = null)​

Same translation for a pre-built Expression<Func<T, bool>>.

Expression<Func<User, bool>> expr = u => u.IsActive && u.Age >= 21;
CosmosFilterExpression f = expr.ToCosmosDb();

CosmosFilterExpression​

CosmosFilterExpression is a sealed class that encapsulates the two values a Cosmos SQL query needs:

PropertyTypeDescription
WhereClausestringThe WHERE clause fragment, without the leading WHERE keyword, e.g. "c.Age > @p0".
ParametersIReadOnlyDictionary<string, object?>Maps placeholders (@p0, @p1, …) to their raw values.

Field references use the standard Cosmos SQL container alias c (e.g. c.Status), so there is no attribute-name placeholder layer like DynamoDB's #f0 — only value placeholders are generated.

Parameters is used directly with QueryDefinition.WithParameter(name, value) in a foreach loop — no .ToDictionary() call is required, since the SDK's WithParameter takes one key/value pair at a time rather than a dictionary constructor argument.


Supported Operations​

ValiFlow methodIR nodeCosmos SQL fragment
.EqualTo(x => x.Field, v)EqualNode(IsNegated: false)c.Field = @p0
.NotEqualTo(x => x.Field, v)EqualNode(IsNegated: true)c.Field != @p0
.GreaterThan(x => x.Field, v)ComparisonNode(GT)c.Field > @p0
.GreaterThanOrEqualTo(x => x.Field, v)ComparisonNode(GTE)c.Field >= @p0
.LessThan(x => x.Field, v)ComparisonNode(LT)c.Field < @p0
.LessThanOrEqualTo(x => x.Field, v)ComparisonNode(LTE)c.Field <= @p0
.Contains(x => x.Field, "txt")LikeNode(Contains)CONTAINS(c.Field, @p0)
.StartsWith(x => x.Field, "pre")LikeNode(StartsWith)STARTSWITH(c.Field, @p0)
.EndsWith(x => x.Field, "suf")LikeNode(EndsWith)ENDSWITH(c.Field, @p0)
.In(x => x.Field, list)InNodec.Field IN (@p0, @p1, …)
.IsNull(x => x.Field)NullNode(IsNull)IS_NULL(c.Field)
.IsNotNull(x => x.Field)NullNode(IsNotNull)NOT IS_NULL(c.Field)
.And(a, b)AndNode(a AND b)
.Or(a, b)OrNode(a OR b)
.Not(inner)NotNodeNOT (inner)

All three LikeNode operators map to native Cosmos SQL functions — unlike DynamoDB, Cosmos supports ENDSWITH directly, so there is no client-side fallback needed for that case. IS_NULL is also native — it is a true null check against the JSON document, not an attribute-existence check.


Type Mapping​

The built-in conversion switch handles these CLR types:

CLR typeParameter value produced
nullnull
boolb (as-is)
strings (as-is)
inti (as-is)
longl (as-is)
doubled (as-is)
floatf (as-is)
decimalm (as-is)
Guidg.ToString()
Enumunderlying value as long (InvariantCulture)
anything elsev.ToString()

Unlike DynamoDB's AttributeValue wrapper, Cosmos parameters are plain CLR values — QueryDefinition.WithParameter accepts them directly and the Cosmos SQL API handles JSON-native typing (numbers, strings, booleans) on its own.


Custom Value Converter​

Use customConverter when your domain types are not in the table above or when you need a different raw value than the default.

The converter is called before the built-in switch. Return null to let the default handle the value.

// Domain type
record Money(decimal Amount, string Currency);

// Converter: store Money as a number (amount only)
CosmosFilterExpression f = new ValiFlow<Order>()
.GreaterThan(x => x.Total, new Money(500m, "USD"))
.ToCosmosDb(value =>
{
if (value is Money m)
return m.Amount;
return null;
});

Storing a DateTimeOffset as an ISO-8601 string:

CosmosFilterExpression f = new ValiFlow<Event>()
.GreaterThan(x => x.StartsAt, DateTimeOffset.UtcNow)
.ToCosmosDb(value =>
{
if (value is DateTimeOffset dto)
return dto.ToString("O");
return null;
});

Advanced Example​

A realistic filter combining equality, range, membership, and null checks, applied through the Microsoft.Azure.Cosmos SDK:

using Microsoft.Azure.Cosmos;
using Vali_Flow.Core.Builder;
using Vali_Flow.NoSql.CosmosDb.Extensions;
using Vali_Flow.NoSql.CosmosDb.Models;

CosmosClient client = new CosmosClient(connectionString);
Container container = client.GetContainer("myDb", "Orders");

var filter = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Processing")
.GreaterThanOrEqualTo(x => x.Total, 100m)
.LessThan(x => x.Total, 5000m)
.In(x => x.RegionCode, new[] { "US", "CA", "MX" })
.IsNotNull(x => x.CustomerId);

CosmosFilterExpression f = filter.ToCosmosDb();

// f.WhereClause:
// "(((c.Status = @p0 AND c.Total >= @p1) AND c.Total < @p2) AND c.RegionCode IN (@p3, @p4, @p5)) AND NOT IS_NULL(c.CustomerId)"

var queryText = $"SELECT * FROM c WHERE {f.WhereClause}";
var query = new QueryDefinition(queryText);
foreach (var kv in f.Parameters)
query = query.WithParameter(kv.Key, kv.Value);

using FeedIterator<Order> iterator = container.GetItemQueryIterator<Order>(query);
var results = new List<Order>();
while (iterator.HasMoreResults)
{
FeedResponse<Order> page = await iterator.ReadNextAsync();
results.AddRange(page);
}

Handling the empty In list case (Cosmos SQL does not accept an empty IN (...)):

var regionCodes = GetRegionCodesFromRequest(); // might be empty

var builder = new ValiFlow<Order>().EqualTo(x => x.Status, "Active");

if (regionCodes.Count > 0)
builder = builder.And(builder, new ValiFlow<Order>().In(x => x.RegionCode, regionCodes));

CosmosFilterExpression f = builder.ToCosmosDb(); // safe — In() only called when the list is non-empty