Skip to main content

Quick Start

This guide walks you through installing and using the Vali-Flow ecosystem from scratch. You will build your first filter, execute it, and understand which package to reach for in each scenario.

loading...

Prerequisites

  • .NET 8 or .NET 9
  • A C# project (any type: web API, console, test project, etc.)

What is Vali-Flow?

Vali-Flow is a set of NuGet packages that let you express data access conditions as strongly-typed C# expression trees and then translate them to the persistence technology of your choice:

PackageWhen to use
Vali-FlowYou use Entity Framework Core (relational DB)
Vali-Flow.InMemoryYou need a synchronous in-memory evaluator (unit tests, caching)
Vali-Flow.SqlYou use Dapper, ADO.NET, or any raw SQL approach
Vali-Flow.NoSql.MongoDBYour store is MongoDB
Vali-Flow.NoSql.DynamoDBYour store is AWS DynamoDB
Vali-Flow.NoSql.ElasticsearchYour store is Elasticsearch
Vali-Flow.NoSql.RedisYour store is Redis (with RediSearch)

All packages share the same ValiFlow<T> expression builder from Vali-Flow.Core — the builder itself is a transitive dependency, you never install it directly.


1. EF Core — Quickstart

Install

dotnet add package Vali-Flow

Define an entity

public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public bool IsActive { get; set; }
public string Category { get; set; } = "";
}

Build a filter

using Vali_Flow.Core.Builder;

var filter = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m)
.LessThanOrEqualTo(x => x.Price, 500m);

ValiFlow<T> is a fluent builder — every method returns the same instance, so you chain conditions with .. All conditions are combined with logical AND by default.

Create an evaluator

using Vali_Flow.Classes.Evaluators;

// Assuming you have an EF Core DbContext
var evaluator = new ValiFlowEvaluator<Product>(context);

Query

using Vali_Flow.Classes.Specifications;

var spec = new QuerySpecification<Product>(filter);

// Get all matching products
IEnumerable<Product> products = await evaluator.EvaluateQueryAsync(spec);

// Get count
int count = await evaluator.EvaluateCountAsync(spec);

// Get first or null
Product? first = await evaluator.EvaluateGetFirstAsync(spec);

Add, update, delete

var newProduct = new Product { Name = "Widget", Price = 29.99m, IsActive = true };

// Add
await evaluator.AddAsync(newProduct);

// Update (entity already tracked)
newProduct.Price = 34.99m;
await evaluator.UpdateAsync(newProduct);

// Delete by condition — removes all matching without loading them first
await evaluator.DeleteByConditionAsync(filter);

2. In-Memory — Quickstart

Use Vali-Flow.InMemory when you want to filter an in-memory list with the exact same API:

dotnet add package Vali-Flow.InMemory
using Vali_Flow.InMemory.Classes.Evaluators;

var products = new List<Product>
{
new() { Id = 1, Name = "Widget", Price = 29.99m, IsActive = true },
new() { Id = 2, Name = "Gadget", Price = 149.99m, IsActive = true },
new() { Id = 3, Name = "Doohickey", Price = 9.99m, IsActive = false },
};

var filter = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 20m);

var evaluator = new ValiFlowEvaluator<Product, int>(
initialData: products,
filter: filter,
getId: p => p.Id
);

// Synchronous — no async/await needed
IEnumerable<Product> result = evaluator.EvaluateQuery();
int count = evaluator.EvaluateCount();

3. SQL — Quickstart

Use Vali-Flow.Sql to generate a parameterized WHERE clause for Dapper or ADO.NET:

dotnet add package Vali-Flow.Sql
using Vali_Flow.Core.Builder;
using Vali_Flow.Sql.Dialects;
using Vali_Flow.Sql.Extensions;

var filter = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m);

var sql = filter.ToSql(new SqlServerDialect());
// sql.Sql → "[IsActive] = @p0 AND [Price] > @p1"
// sql.Parameters → { "p0": true, "p1": 10 }

// Use with Dapper
var products = await connection.QueryAsync<Product>(
$"SELECT * FROM Products WHERE {sql.Sql}",
sql.Parameters
);

Available dialects: SqlServerDialect, PostgreSqlDialect, MySqlDialect, SqliteDialect, OracleDialect.


4. NoSQL — Quickstart

Each NoSQL package follows the same pattern: call .ToXxx() on the builder and pass the result to the driver.

MongoDB:

BsonDocument filter = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m)
.ToMongo();

var products = await collection.Find(filter).ToListAsync();

Elasticsearch:

Query filter = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m)
.ToElasticsearch();

var response = await client.SearchAsync<Product>(s => s.Query(filter));

DynamoDB:

DynamoFilterExpression f = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m)
.ToDynamoDB();

var request = new ScanRequest
{
TableName = "Products",
FilterExpression = f.FilterExpression,
ExpressionAttributeNames = f.ExpressionAttributeNames.ToDictionary(),
ExpressionAttributeValues = f.ExpressionAttributeValues.ToDictionary()
};

Redis (RediSearch):

string query = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m)
.ToRedisSearch();

SearchResult result = db.FT().Search("idx:products", new Query(query));

Logical operators

By default, conditions are AND-ed. Use .Or() and .Not() for other compositions:

var cheapOrPremium = new ValiFlow<Product>()
.Or(
new ValiFlow<Product>().LessThan(x => x.Price, 20m),
new ValiFlow<Product>().GreaterThan(x => x.Price, 1000m)
);

var notDiscontinued = new ValiFlow<Product>()
.Not(new ValiFlow<Product>().EqualTo(x => x.Category, "Discontinued"));

More Vali-Flow examples

1) Reuse one spec across stores

var spec = new ValiFlow<Product>()
.EqualTo(x => x.IsActive, true)
.GreaterThan(x => x.Price, 10m);

// SQL
var sql = spec.ToSql(new SqlServerDialect());

// MongoDB
var mongo = spec.ToMongo();

2) Query + Count with the same spec

var spec = new ValiFlow<Product>()
.NotNull(x => x.Name)
.LessThan(x => x.Price, 200m);

var query = new QuerySpecification<Product>(spec);
var list = await evaluator.EvaluateQueryAsync(query);
var total = await evaluator.EvaluateCountAsync(query);

Recipe Patterns

1) Reusable spec factory

static ValiFlow<Order> ActiveOrders() =>
new ValiFlow<Order>()
.EqualTo(o => o.IsActive, true)
.GreaterThan(o => o.Total, 0m);

2) Soft-delete filter

var spec = new ValiFlow<User>()
.EqualTo(u => u.IsDeleted, false);

3) Combine OR groups

var spec = new ValiFlow<Product>()
.Or(
new ValiFlow<Product>().LessThan(p => p.Price, 20m),
new ValiFlow<Product>().GreaterThan(p => p.Price, 1000m)
);

Next steps