Vali-Flow.InMemory 1.1.5

dotnet add package Vali-Flow.InMemory --version 1.1.5
                    
NuGet\Install-Package Vali-Flow.InMemory -Version 1.1.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Vali-Flow.InMemory" Version="1.1.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Vali-Flow.InMemory" Version="1.1.5" />
                    
Directory.Packages.props
<PackageReference Include="Vali-Flow.InMemory" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Vali-Flow.InMemory --version 1.1.5
                    
#r "nuget: Vali-Flow.InMemory, 1.1.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Vali-Flow.InMemory@1.1.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Vali-Flow.InMemory&version=1.1.5
                    
Install as a Cake Addin
#tool nuget:?package=Vali-Flow.InMemory&version=1.1.5
                    
Install as a Cake Tool

Vali-Flow.InMemory

Synchronous in-memory evaluator for testing, caching, and offline evaluation.

Evaluate the same reusable ValiFlow<T> filters against IEnumerable<T> without a database — perfect for unit tests, caching layers, and offline scenarios.

Part of the Vali-Flow ecosystem — write filters once, use them everywhere (EF Core, SQL, in-memory, MongoDB, Elasticsearch, etc).

Supported platforms: .NET 8.0, .NET 9.0


When to Use

Use Vali-Flow.InMemory for:

  • Unit testing repositories without mocking EF Core
  • Caching layers with fresh evaluations
  • Testing business logic without database I/O
  • Offline evaluation against in-memory collections
  • Filtering cached collections in-process

Don't use for:

  • Real-time data processing of millions of rows (use Vali-Flow + EF Core)
  • Persistence beyond the application lifetime

Installation

dotnet add package Vali-Flow.InMemory

Dependencies:

  • Vali-Flow.Core (v2.0.0+) — expression builder
  • Vali-Flow.Abstractions (v1.0.0+) — shared interfaces
  • No external database dependencies ✨

Quick Start

1. Create an evaluator

using Vali_Flow.InMemory;

var orders = new List<Order>
{
    new Order { Id = 1, Status = "Active", Total = 150m, CreatedAt = DateTime.Now },
    new Order { Id = 2, Status = "Pending", Total = 80m, CreatedAt = DateTime.Now.AddDays(-5) },
    new Order { Id = 3, Status = "Active", Total = 220m, CreatedAt = DateTime.Now.AddDays(-10) },
};

var evaluator = new ValiFlowEvaluator<Order, int>(
    orders,
    null,  // optional: initial filter
    x => x.Id);  // key selector

2. Build a filter

using Vali_Flow.Core;

var filter = new ValiFlow<Order>()
    .EqualTo(x => x.Status, "Active")
    .GreaterThan(x => x.Total, 100m);

3. Evaluate synchronously

// Single result
var order = evaluator.Evaluate(filter);

// List of results
var activeHighValue = evaluator.EvaluateAll(valiFlow: filter);

// Count
var count = evaluator.EvaluateCount(filter);

// Boolean check
var hasActive = evaluator.EvaluateAny(filter);

// Paginated
var page1 = evaluator.EvaluatePaged(
    valiFlow: filter,
    pageIndex: 0,
    pageSize: 10);

Read Operations

Basic Queries

// Get first matching
var first = evaluator.GetFirst(filter);

// Get last matching
var last = evaluator.GetLast(filter);

// All matching
var all = evaluator.EvaluateAll(valiFlow: filter);

// With ordering
var sorted = evaluator.EvaluateAll(
    valiFlow: filter,
    orderBy: x => x.CreatedAt);

// Paginated with metadata
var result = evaluator.EvaluatePaged(
    valiFlow: filter,
    pageIndex: 0,
    pageSize: 20);
// result.Items, result.TotalCount, result.TotalPages, result.HasNextPage

Filtering & Selection

// Distinct values
var statuses = evaluator.EvaluateDistinct(
    valiFlow: filter,
    selector: x => x.Status);

// Duplicates (entities with duplicate keys)
var duplicates = evaluator.EvaluateDuplicates(
    selector: x => x.CustomerId);

// Top N
var top10 = evaluator.EvaluateTop(
    valiFlow: filter,
    pageSize: 10,
    orderBy: x => x.Total);

Aggregates

// Sum
var totalRevenue = evaluator.EvaluateSum(
    valiFlow: filter,
    selector: x => x.Total);

// Average
var avgPrice = evaluator.EvaluateAverage(
    valiFlow: filter,
    selector: x => x.Total);

// Min / Max
var minTotal = evaluator.EvaluateMin(filter, x => x.Total);
var maxDate = evaluator.EvaluateMax(filter, x => x.CreatedAt);

// Generic aggregate
var result = evaluator.EvaluateAggregate(
    valiFlow: filter,
    selector: x => x.Total,
    aggregator: (a, b) => a + b);

Grouped Operations

// Group by status, count per group
var countByStatus = evaluator.EvaluateCountByGroup(
    valiFlow: filter,
    groupKeySelector: x => x.Status);
// Returns: Dictionary<string, int> { "Active" => 2, "Pending" => 1 }

// Sum by region
var revenueByRegion = evaluator.EvaluateSumByGroup(
    valiFlow: filter,
    groupKeySelector: x => x.Region,
    selector: x => x.Total);

// Min / Max by group
var maxOrderPerCustomer = evaluator.EvaluateMaxByGroup(
    valiFlow: filter,
    groupKeySelector: x => x.CustomerId,
    selector: x => x.Total);

// Top N per group
var top3PerStatus = evaluator.EvaluateTopByGroup(
    valiFlow: filter,
    groupKeySelector: x => x.Status,
    pageSize: 3,
    orderBy: x => x.Total);

// Unique & Duplicates by group
var uniqueCustomers = evaluator.EvaluateUniquesByGroup(
    groupKeySelector: x => x.CustomerId);

var duplicateOrders = evaluator.EvaluateDuplicatesByGroup(
    groupKeySelector: x => x.CustomerId);

Write Operations

CRUD

// Create
var newOrder = new Order { Id = 4, Status = "Active", Total = 300m };
evaluator.Add(newOrder);
evaluator.SaveChanges();

// Read (via queries above)

// Update
var order = evaluator.Evaluate(filter);
order.Status = "Shipped";
evaluator.Update(order);

// Delete
evaluator.Delete(order);
evaluator.SaveChanges();

// Batch operations
evaluator.AddRange(new[] { order1, order2, order3 });
evaluator.DeleteRange(new[] { order1, order2 });
evaluator.SaveChanges();

Upsert

// Insert or update by key
var order = new Order { Id = 1, Status = "Archived" };
var upserted = evaluator.Upsert(order);
evaluator.SaveChanges();

// Batch upsert
var orders = new[] { order1, order2, order3 };
evaluator.UpsertRange(orders);
evaluator.SaveChanges();

Delete by Condition

// Delete all inactive orders older than 90 days
var oldInactiveFilter = new ValiFlow<Order>()
    .EqualTo(x => x.Status, "Inactive")
    .IsBefore(x => x.CreatedAt, DateTime.Now.AddDays(-90));

evaluator.DeleteByCondition(oldInactiveFilter);
evaluator.SaveChanges();

Testing Patterns

Unit Testing Repositories

[Fact]
public void GetActiveOrders_ReturnsOnlyActive()
{
    // Arrange
    var orders = new List<Order>
    {
        new Order { Id = 1, Status = "Active", Total = 150m },
        new Order { Id = 2, Status = "Inactive", Total = 80m },
    };

    var evaluator = new ValiFlowEvaluator<Order, int>(
        orders, null, x => x.Id);

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

    // Act
    var result = evaluator.EvaluateAll(valiFlow: filter);

    // Assert
    Assert.Single(result);
    Assert.Equal("Active", result[0].Status);
}

Mock Repository with In-Memory Evaluator

public class MockOrderRepository : IOrderRepository
{
    private readonly ValiFlowEvaluator<Order, int> _evaluator;

    public MockOrderRepository(List<Order> initialData)
    {
        _evaluator = new ValiFlowEvaluator<Order, int>(
            initialData, null, x => x.Id);
    }

    public Order GetById(int id)
    {
        var filter = new ValiFlow<Order>().EqualTo(x => x.Id, id);
        return _evaluator.Evaluate(filter);
    }

    public List<Order> GetActive()
    {
        var filter = new ValiFlow<Order>().EqualTo(x => x.Status, "Active");
        return _evaluator.EvaluateAll(valiFlow: filter);
    }

    public void Save(Order order) => _evaluator.Upsert(order);
}

// Usage in tests
[Fact]
public void OrderService_CompleteOrder_Updates()
{
    var initialOrders = new List<Order> { /* ... */ };
    var repo = new MockOrderRepository(initialOrders);
    var service = new OrderService(repo);

    service.CompleteOrder(1);

    var updated = repo.GetById(1);
    Assert.Equal("Completed", updated.Status);
}

Caching Layer Pattern

public class CachedOrderRepository
{
    private List<Order> _cache = new();
    private readonly ValiFlowEvaluator<Order, int> _evaluator;

    public CachedOrderRepository()
    {
        _evaluator = new ValiFlowEvaluator<Order, int>(
            _cache, null, x => x.Id);
    }

    public async void RefreshCache(IOrderRepository db)
    {
        _cache = await db.GetAllAsync();
        _evaluator.SetValiFlow(null); // Reset filter
    }

    public List<Order> Query(ValiFlow<Order> filter)
    {
        return _evaluator.EvaluateAll(valiFlow: filter);
    }

    public void InvalidateCache() => _cache.Clear();
}

Runtime Filter Updates

var evaluator = new ValiFlowEvaluator<Order, int>(orders, null, x => x.Id);

// Set initial filter
var filter1 = new ValiFlow<Order>().EqualTo(x => x.Status, "Active");
evaluator.SetValiFlow(filter1);

var result1 = evaluator.EvaluateAll(); // Uses filter1

// Change filter at runtime
var filter2 = new ValiFlow<Order>().GreaterThan(x => x.Total, 100m);
evaluator.SetValiFlow(filter2);

var result2 = evaluator.EvaluateAll(); // Uses filter2

Key Differences from Vali-Flow (EF Core)

Feature Vali-Flow Vali-Flow.InMemory
Async Yes (EvaluateAsync) No (synchronous)
Target DbContext (EF Core) IEnumerable<T>
Includes Yes (navigation properties) No (all data in memory)
Lazy loading Supported N/A (loaded upfront)
Best for Production databases Testing, caching, offline
Performance Database-dependent O(n) LINQ-to-Objects

Performance Characteristics

  • Filtering: O(n) — LINQ-to-Objects scans entire collection
  • Ordering: O(n log n) — LINQ sort
  • Pagination: O(n) — must enumerate to skip/take
  • Aggregates: O(n) — single pass (no grouping) or O(n log n) (with grouping)

For datasets > 100K rows, cache only what you need or use Vali-Flow + EF Core.


Combine with Vali-Flow

Same filter works with both evaluators:

var filter = new ValiFlow<Order>()
    .EqualTo(x => x.Status, "Active")
    .GreaterThan(x => x.Total, 100m);

// In production: EF Core
var efEvaluator = new ValiFlowEvaluator<Order>(dbContext);
var dbOrders = await efEvaluator.EvaluateQueryAsync(
    new QuerySpecification<Order>().WithFilter(filter));

// In tests: in-memory
var memEvaluator = new ValiFlowEvaluator<Order, int>(testData, filter, x => x.Id);
var testOrders = memEvaluator.EvaluateAll();

// Same business logic, different storage ✅

Full Ecosystem

  • Vali-Flow.Core — expression builder (ValiFlow<T>)
  • Vali-Flow — EF Core async evaluator
  • Vali-Flow.InMemory — synchronous in-memory evaluator (this package)
  • 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.5 99 4/15/2026
1.1.4 93 4/14/2026
1.1.3 92 4/14/2026
1.1.2 96 4/14/2026
1.1.1 101 4/14/2026
1.1.0 100 4/14/2026
1.0.0 99 4/12/2026

v1.1.5 — Updated Vali-Flow.Core dependency to 2.0.2 (fixes IsNull/NotNull WHERE 0=1 bug with EF Core GlobalQueryFilter). No API changes.

v1.1.4 — Remove sealed modifiers from all classes (ValiFlowEvaluator, AsyncInMemoryAdapter, InMemoryWriteStore, PagedResult). Now fully inheritable for custom implementations. No breaking changes.