Vali-Flow
1.3.4
dotnet add package Vali-Flow --version 1.3.4
NuGet\Install-Package Vali-Flow -Version 1.3.4
<PackageReference Include="Vali-Flow" Version="1.3.4" />
<PackageVersion Include="Vali-Flow" Version="1.3.4" />
<PackageReference Include="Vali-Flow" />
paket add Vali-Flow --version 1.3.4
#r "nuget: Vali-Flow, 1.3.4"
#:package Vali-Flow@1.3.4
#addin nuget:?package=Vali-Flow&version=1.3.4
#tool nuget:?package=Vali-Flow&version=1.3.4
Vali-Flow
Entity Framework Core async evaluator with fluent specifications for building reusable query logic and write operations.
Part of the Vali-Flow ecosystem — write filters once, use them everywhere (EF Core, SQL, MongoDB, Elasticsearch, etc).
Supported platforms: .NET 8.0, .NET 9.0
What It Solves
Vali-Flow decouples filter logic from data access code. Instead of scattering LINQ predicates across repositories, write a single fluent filter definition and translate it to any data store:
// ✅ Single filter, used everywhere
var filter = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active")
.GreaterThan(x => x.Total, 100m)
.IsAfter(x => x.CreatedAt, DateTime.UtcNow.AddDays(-30));
// EF Core
var efOrders = await new ValiFlowEvaluator<Order>(dbContext)
.EvaluateQueryAsync(new QuerySpecification<Order>().WithFilter(filter));
// SQL (Dapper)
var sqlResult = filter.ToSql(new PostgreSqlDialect());
// In-memory (testing)
var memoryOrders = new ValiFlowEvaluator<Order, int>(orders, filter, x => x.Id).EvaluateAll();
Installation
dotnet add package Vali-Flow
Dependencies:
- Vali-Flow.Core (v2.0.0+) — expression builder
- Vali-Flow.Abstractions (v1.0.0+) — shared interfaces
- Microsoft.EntityFrameworkCore (v9.0+)
- EFCore.BulkExtensions (v8.1+) — for bulk operations
Quick Start
1. Create an evaluator
using Vali_Flow;
using Microsoft.EntityFrameworkCore;
var evaluator = new ValiFlowEvaluator<Order>(dbContext);
2. Build a specification with filter
using Vali_Flow.Core;
var spec = new QuerySpecification<Order>()
.WithFilter(new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active")
.GreaterThan(x => x.Total, 100m))
.AddInclude(x => x.Customer)
.WithAsNoTracking(true)
.WithOrderBy(x => x.CreatedAt)
.WithPagination(page: 1, pageSize: 20);
3. Execute the query
// Single result
var order = await evaluator.EvaluateAsync(spec);
// Multiple results
var orders = await evaluator.EvaluateQueryAsync(spec);
// Count
var total = await evaluator.EvaluateCountAsync(spec);
// Any / All
var hasActive = await evaluator.EvaluateAnyAsync(spec);
// Paginated
var paged = await evaluator.EvaluatePagedAsync(spec);
Core Features
Read Operations
| Method | Purpose |
|---|---|
EvaluateAsync(spec) |
Single entity by ID or condition |
EvaluateQueryAsync(spec) |
List of entities |
EvaluateAnyAsync(spec) |
Boolean exists check |
EvaluateCountAsync(spec) |
Entity count |
EvaluatePagedAsync(spec) |
Paginated results with metadata |
EvaluateDistinctAsync(selector) |
Unique values by selector |
EvaluateDuplicatesAsync(selector) |
Duplicated entities by selector |
Aggregates
// Sum, Min, Max, Average
var totalRevenue = await evaluator.EvaluateSumAsync(
spec, x => x.Total);
var avgPrice = await evaluator.EvaluateAverageAsync(
spec, x => x.Price);
var maxDate = await evaluator.EvaluateMaxAsync(
spec, x => x.CreatedAt);
Grouped Operations
// Group by status, count per group
var countByStatus = await evaluator.EvaluateCountByGroupAsync(
spec, x => x.Status);
// Top N per group
var top3OrdersByCustomer = await evaluator.EvaluateTopByGroupAsync(
spec,
groupKey: x => x.CustomerId,
pageSize: 3,
orderBy: x => x.Total);
// Aggregate by group
var sumByRegion = await evaluator.EvaluateSumByGroupAsync(
spec, x => x.Region, x => x.Revenue);
Write Operations
// Create
var newOrder = new Order { ... };
await evaluator.AddAsync(newOrder);
await evaluator.SaveChangesAsync();
// Update single
var order = await evaluator.EvaluateAsync(spec);
order.Status = "Shipped";
await evaluator.UpdateAsync(order);
// Batch operations (no entity load)
await evaluator.UpdateRangeAsync(
new[] { order1, order2, order3 });
// Bulk insert (via EFCore.BulkExtensions)
await evaluator.BulkInsertAsync(
largeOrderCollection,
new BulkConfig { BatchSize = 10000 });
// Upsert
var order = await evaluator.UpsertAsync(
new Order { Id = 123, Status = "Active" },
keySelector: x => x.Id);
// Delete by condition (no load)
await evaluator.DeleteByConditionAsync(spec);
// Transaction
await evaluator.ExecuteTransactionAsync(async () =>
{
await evaluator.AddAsync(order);
await evaluator.DeleteByConditionAsync(oldSpec);
});
Specifications
BasicSpecification<T>
Minimal spec: filter + includes + EF Core hints.
var spec = new BasicSpecification<Order>()
.WithFilter(filter)
.AddInclude(x => x.Customer)
.WithAsNoTracking(true)
.AsSplitQuery(true)
.IgnoreQueryFilters(true);
QuerySpecification<T>
Full spec: filter + includes + ordering + pagination + hints.
var spec = new QuerySpecification<Order>()
.WithFilter(filter)
.WithOrderBy(x => x.CreatedAt)
.AddThenBy(x => x.Id)
.WithPagination(page: 1, pageSize: 50)
.AddInclude(x => x.Customer)
.WithAsNoTracking(true);
EF Core Integration
Eager Loading
spec.AddInclude(x => x.Customer)
.AddInclude(x => x.LineItems)
.ThenInclude(x => x.Product);
Query Hints
spec
.WithAsNoTracking(true) // Disable change tracking
.AsSplitQuery(true) // Split includes into separate queries
.IgnoreQueryFilters(true); // Bypass global query filters (soft delete, etc)
Filter Negation
// Evaluate NOT (filter)
var inactiveOrders = await evaluator.EvaluateQueryAsync(
spec,
negateCondition: true);
Performance Tips
- Use
QuerySpecificationfor complex queries — ordering and pagination optimize LINQ-to-SQL translation - Enable
AsSplitQueryfor multiple includes — prevents cartesian explosion - Use
Upsertinstead of load-then-update for bulk operations - Bulk operations for large datasets —
BulkInsert,BulkUpdate,BulkDeletedelegate to EFCore.BulkExtensions (10-100x faster) - Deferred saves — pass
saveChanges: falseto batch operations, then callSaveChangesAsync()once
// Good: batch with single save
var orders = new[] { order1, order2, order3 };
await evaluator.AddRangeAsync(orders, saveChanges: false);
await evaluator.SaveChangesAsync();
// Better: bulk for 1000+
await evaluator.BulkInsertAsync(largeCollection);
Composable Filters
Filters are composable — build complex logic from simple pieces:
var baseFilter = new ValiFlow<Order>()
.EqualTo(x => x.Status, "Active");
var recentAndHighValue = new ValiFlow<Order>()
.IsAfter(x => x.CreatedAt, DateTime.UtcNow.AddDays(-30))
.GreaterThan(x => x.Total, 1000m);
// Combine in spec
var spec = new QuerySpecification<Order>()
.WithFilter(baseFilter)
.WithFilter(recentAndHighValue); // Both applied with AND logic
Testing & Caching
For unit tests and caching layers, use Vali-Flow.InMemory — same filter, synchronous evaluation against IEnumerable<T>:
// Same filter, no database
var inMemoryEvaluator = new ValiFlowEvaluator<Order, int>(
cachedOrders,
filter,
x => x.Id);
var filtered = inMemoryEvaluator.EvaluateAll<DateTime>(
orderBy: x => x.CreatedAt);
Full Ecosystem
- Vali-Flow.Core — expression builder (
ValiFlow<T>) - Vali-Flow — EF Core async evaluator (this package)
- Vali-Flow.InMemory — synchronous in-memory evaluator
- Vali-Flow.Sql — parameterized SQL translator (Dapper/ADO.NET)
- Vali-Flow.NoSql.* — MongoDB, Elasticsearch, Redis, DynamoDB adapters
See main repository for complete docs.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- EFCore.BulkExtensions (>= 8.1.3)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Vali-Flow.Abstractions (>= 1.0.0)
- Vali-Flow.Core (>= 2.0.2)
-
net9.0
- EFCore.BulkExtensions (>= 8.1.3)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Vali-Flow.Abstractions (>= 1.0.0)
- Vali-Flow.Core (>= 2.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.3.4 — Updated Vali-Flow.Core dependency to 2.0.2 (fixes IsNull/NotNull WHERE 0=1 bug with EF Core GlobalQueryFilter). No API changes.
v1.3.3 — Updated Vali-Flow.Core dependency to 2.0.1.
v1.3.0 — Made ValiFlowEvaluator<T> inheritable (removed sealed modifier). This allows repositories to extend ValiFlowEvaluator directly instead of using composition, enabling a cleaner repository pattern implementation. No breaking changes.