Skip to content

Releases: quyvu01/OfX

v9.0.0

11 Feb 05:03

Choose a tag to compare

Overview

OfX 9.0.0 is a naming consistency release that improves API clarity across the entire framework. All changes are renames — no behavioral or functional changes. This is a major version bump due to the number of breaking changes in public API names and namespaces.


Why v9.0.0?

Over time, several names in OfX became inconsistent or unclear:

  • RequestOf<T> — ambiguous direction (request of what?)
  • PropertyAssessorData — typo-like name (assessor vs accessor)
  • SerializeObjects — sounds like a method, not a class
  • SetMaxObjectSpawnTimes() — unclear what "object spawn" means
  • ApplicationModels/ — generic ASP.NET-style catch-all folder name
  • Statics/, Cached/, Externals/ — vague folder names

This release systematically fixes all naming issues to make the codebase self-documenting.


Breaking Changes Summary

Class & Record Renames

Old New Impact
OfXRegister OfXConfigurator Used in AddOfX() callback parameter
OfXRegisterWrapped OfXConfiguratorWrapped Return type of AddOfX()
RequestOf<T> OfXQueryRequest<T> Handler parameter type
PropertyAssessorData PropertyMappingData Mapping pipeline data
SerializeObjects OfXJsonSerializer JSON serialization utility
TypeModelAccessor TypeMetadataAccessor Type metadata cache
GenericDeepestImplementationFinder LeafImplementationFinder Handler discovery utility
ImplementedResult GenericInterfaceMatch Handler discovery result

Method Renames

Class Old New
OfXConfigurator SetMaxObjectSpawnTimes(int) SetMaxNestingDepth(int)

Exception Renames

Old New
OfXException.OfXMappingObjectsSpawnReachableTimes OfXException.MaxNestingDepthReached

Namespace Changes

Core library:

Old New
OfX.ApplicationModels OfX.Models
OfX.ObjectContexts OfX.PropertyMappingContexts
OfX.Externals OfX.PublicContracts
OfX.Cached OfX.MetadataCache
OfX.InternalPipelines OfX.BuiltInPipelines
OfX.Statics OfX.Configuration

Transport libraries:

Old New
OfX.RabbitMq.ApplicationModels OfX.RabbitMq.Configuration
OfX.Kafka.ApplicationModels OfX.Kafka.Configuration
OfX.Aws.Sqs.ApplicationModels OfX.Aws.Sqs.Configuration
OfX.Nats.ApplicationModels OfX.Nats.Configuration
OfX.Azure.ServiceBus.ApplicationModels OfX.Azure.ServiceBus.Configuration

Integration libraries:

Old New
OfX.EntityFrameworkCore.ApplicationModels OfX.EntityFrameworkCore.Registration
OfX.MongoDb.ApplicationModels OfX.MongoDb.Registration
OfX.HotChocolate.ApplicationModels OfX.HotChocolate.Registration
OfX.Grpc.ApplicationModels OfX.Grpc.Registration

Migration Guide

Upgrading from v8.3.0

This release contains only renames. No logic changes, no new features, no removed features. Migration is mechanical find-and-replace.

Step 1: Update NuGet packages

dotnet add package OfX --version 9.0.0
dotnet add package OfX-gRPC --version 9.0.0
dotnet add package OfX-EFCore --version 9.0.0
dotnet add package OfX-Kafka --version 9.0.0
dotnet add package OfX-RabbitMq --version 9.0.0
dotnet add package OfX-Nats --version 9.0.0
dotnet add package OfX-HotChocolate --version 9.0.0
dotnet add package OfX-MongoDb --version 9.0.0
dotnet add package OfX-Azure.ServiceBus --version 9.0.0
dotnet add package OfX-Aws.Sqs --version 9.0.0

Step 2: Fix namespace imports

Update all using statements. The most common ones you'll encounter:

// Before
using OfX.ApplicationModels;
using OfX.Statics;
using OfX.Cached;

// After
using OfX.Models;
using OfX.Configuration;
using OfX.MetadataCache;

For transport/integration projects:

// Before
using OfX.RabbitMq.ApplicationModels;
using OfX.EntityFrameworkCore.ApplicationModels;

// After
using OfX.RabbitMq.Configuration;
using OfX.EntityFrameworkCore.Registration;

Step 3: Fix class references

// Before
services.AddOfX(cfg => {
    cfg.SetMaxObjectSpawnTimes(5);
});

// After
services.AddOfX(cfg => {
    cfg.SetMaxNestingDepth(5);
});
// Before
public class MyHandler : IClientRequestHandler<MyAttribute>
{
    public Task<ItemsResponse<DataResponse>> HandleAsync(RequestOf<MyAttribute> request, ...)

// After
public class MyHandler : IClientRequestHandler<MyAttribute>
{
    public Task<ItemsResponse<DataResponse>> HandleAsync(OfXQueryRequest<MyAttribute> request, ...)

Step 4: Build and verify

dotnet build
dotnet test

Design Decisions

Naming Philosophy

Category Convention Example
Core folder Models/ Simple, conventional for .NET
Transport folder Configuration/ Contains configurators and credentials
Integration folder Registration/ Contains DI registrars
Internal folder Descriptive purpose MetadataCache/, BuiltInPipelines/

Why not keep backward compatibility?

We considered [Obsolete] attributes and type aliases, but:

  1. Clean break is better — deprecated names create confusion about "which one to use"
  2. All changes are mechanical — simple find-and-replace migration
  3. Major version signals intent — v9.0.0 clearly communicates breaking changes
  4. No functional changes — reduces risk since only names change

Testing & Quality

Build:     0 errors, 26 warnings (all pre-existing NuGet advisories)
Analyzer:  14/14 tests passing on .NET 8, 9, 10
Unit:      422/422 tests passing on .NET 8, 9, 10

What's NOT Changed

  • No behavioral changes
  • No new features
  • No removed features
  • No performance changes
  • No dependency updates
  • .NET 8, 9, 10 multi-targeting unchanged
  • All transport protocols work identically
  • All data providers work identically

The only thing that changed is names.


OfX 9.0.0 - Naming Consistency & API Clarity

Released: February 11, 2026

Full Changelog: v8.3.0...v9.0.0

OfX v8.3.0 .NET 10 upgreaded

10 Feb 04:40

Choose a tag to compare

Release Notes v8.3.0

Overview

OfX 8.3.0 brings full .NET 10 support and modernizes the development experience with the new XML-based solution format (.slnx). This release ensures OfX stays current with the latest .NET platform while maintaining backward compatibility with .NET 8 and .NET 9.


What's New

.NET 10 Support

OfX now fully supports .NET 10, the latest version of the .NET platform:

  • All 19 projects updated to target .NET 10
  • Multi-targeting maintained for .NET 8, 9, and 10
  • Full test coverage validated on all three frameworks
  • CI/CD pipeline updated for comprehensive testing

SLNX Solution Format

The solution has been migrated to the modern XML-based .slnx format:

  • Better version control with reduced merge conflicts
  • Logical organization into solution folders
  • Visual Studio 2022 and dotnet CLI support
  • Simpler structure compared to legacy .sln format

Analyzer Testing Improvements

Enhanced testing infrastructure for Roslyn analyzers:

  • Dual detection strategy for OfX attributes
  • Zero assembly dependencies in test projects
  • Improved compatibility with testing frameworks
  • All 14 tests passing on .NET 8, 9, and 10

Key Features

1. Multi-Target Framework Support

OfX libraries now support three .NET versions simultaneously:

<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>

What This Means:

  • Flexibility: Choose the .NET version that fits your project
  • Future-Proof: Adopt .NET 10 features when ready
  • Compatibility: Existing .NET 8/9 apps work without changes
  • Performance: Benefit from .NET 10 runtime optimizations

Supported Packages:

  • OfX - Core library
  • OfX-gRPC - gRPC transport
  • OfX-Kafka - Kafka transport
  • OfX-Nats - NATS transport
  • OfX-RabbitMq - RabbitMQ transport
  • OfX-Azure.ServiceBus - Azure Service Bus transport
  • OfX-Aws.Sqs - AWS SQS transport
  • OfX-EFCore - Entity Framework Core integration
  • OfX-MongoDb - MongoDB integration
  • OfX-HotChocolate - HotChocolate GraphQL integration
  • OfX-Analyzers - Roslyn analyzers

2. SLNX Solution Format

The new XML-based solution format provides several advantages:

Before (.sln):

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfX", "src\OfX\OfX.csproj", "{CAB8CF6C-4A1D-43C6-836A-C5A7349D062E}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
        Release|Any CPU = Release|Any CPU
    EndGlobalSection
    ...
EndGlobal

After (.slnx):

<Solution>
  <Configurations>
    <Platform Name="Any CPU" />
  </Configurations>
  <Folder Name="/OfX.Core/">
    <Project Path="src/OfX/OfX.csproj" />
  </Folder>
  <Folder Name="/OfX.Transports/">
    <Project Path="src/OfX.Grpc/OfX.Grpc.csproj" />
    <Project Path="src/OfX.Kafka/OfX.Kafka.csproj" />
    <!-- ... -->
  </Folder>
</Solution>

Benefits:

  • Readable: Clear XML structure vs custom format
  • Git-Friendly: Fewer merge conflicts
  • Organized: Logical solution folder grouping
  • Modern: Supported by Visual Studio 2022 17.10+ and .NET 9+ SDK

3. Enhanced Analyzer Testing

The analyzer testing framework has been improved for better compatibility:

Problem: Version conflicts between testing framework and OfX assembly
Solution: Dual attribute detection strategy

Implementation:

// Production: Semantic analysis (inheritance checking)
if (InheritsFromOfXAttribute(attributeType))
    return true;

// Testing: Naming convention fallback
return attributeType.Name.EndsWith("OfAttribute", StringComparison.Ordinal);

Test Pattern:

// Mock OfXAttribute in test code
namespace OfX.Attributes
{
    public class OfXAttribute(string propertyName) : Attribute
    {
        public string Expression { get; set; }
    }
}

// Test attribute inherits from mock
public class CountryOfAttribute(string propertyName) : OfX.Attributes.OfXAttribute(propertyName);

Results:

  • All analyzer tests pass without OfX assembly reference
  • No version conflicts with testing frameworks
  • Maintains semantic correctness in production
  • Enables comprehensive test coverage

Performance & Compatibility

.NET 10 Performance Benefits

While OfX 8.3.0 doesn't add new features, running on .NET 10 provides:

  • JIT Improvements: Faster code generation and optimization
  • GC Enhancements: Reduced pause times and better throughput
  • Runtime Optimizations: Various performance improvements across BCL
  • Native AOT: Potential for future native compilation support

Migration Guide

Upgrading from v8.2.6

Step 1: Update OfX Packages

# Update core library
dotnet add package OfX --version 8.3.0

# Update transport packages
dotnet add package OfX-gRPC --version 8.3.0
dotnet add package OfX-Kafka --version 8.3.0
dotnet add package OfX-RabbitMq --version 8.3.0
# ... etc

Step 2: (Optional) Upgrade to .NET 10

If you want to use .NET 10 in your application:

<!-- For libraries - multi-target (recommended) -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>

<!-- For applications - single target -->
<TargetFramework>net10.0</TargetFramework>

Step 3: (Optional) Update CI/CD

Add .NET 10 SDK to your build pipeline:

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: |
      8.0.x
      9.0.x
      10.0.x

Step 4: Test

# Restore dependencies
dotnet restore

# Build
dotnet build

# Run tests
dotnet test

No Breaking Changes

This release is 100% backward compatible:

  • .NET 8 and .NET 9 applications work without changes
  • No API modifications
  • No behavioral changes
  • Existing code continues to work as-is

You can upgrade immediately without any code changes. Adopting .NET 10 is entirely optional.


What to Expect

Immediate Benefits

After upgrading to v8.3.0:

  1. Same Functionality: All features work exactly as before
  2. Better Testing: Enhanced analyzer test coverage
  3. Modern Tooling: SLNX support in latest IDEs
  4. Future Ready: Foundation for .NET 10 feature adoption

Future Opportunities

With .NET 10 support, you can now explore:

  • C# 13 Features: Collection expressions, primary constructors, etc.
  • Performance: Runtime and JIT improvements
  • New APIs: Latest BCL additions
  • Tooling: Enhanced debugging and diagnostics

Development Experience

Visual Studio 2022:

  • SLNX solution format support (17.10+)
  • Solution folders for better organization
  • Improved Git diff visualization

VS Code / Rider:

  • Full .NET 10 SDK support
  • Works with dotnet CLI commands
  • No special configuration needed

Command Line:

# Build with SLNX
dotnet build OfX.slnx

# Test with SLNX
dotnet test OfX.slnx

# Restore with SLNX
dotnet restore OfX.slnx

Testing & Quality Assurance

Comprehensive Test Coverage

Analyzer Tests:

Target Framework: net10.0
Tests: 14/14
Status: All Passing

Target Framework: net9.0
Tests: 14/14
Status: All Passing

Target Framework: net8.0
Tests: 14/14
Status: All Passing

Unit Tests:

Target Framework: net10.0
Tests: 349/349
Status: All Passing

Target Framework: net9.0
Tests: 349/349
Status: All Passing

Target Framework: net8.0
Tests: 349/349
Status: All Passing

Build Verification:

Projects Built: 19/19
Compilation Warnings: 0
Package Generation: Successful

Continuous Integration

GitHub Actions pipeline now tests on:

  • .NET 8.0.x
  • .NET 9.0.x
  • .NET 10.0.x

Matrix testing ensures reliability across all supported frameworks.


Technical Details

Solution Folder Organization

Projects are now logically grouped in the SLNX file:

  • OfX.Core: Core library (OfX)
  • OfX.Transports: Message transport implementations
    • gRPC, Kafka, NATS, RabbitMQ, Azure Service Bus, AWS SQS
  • OfX.DataProviders: Data access integrations
    • Entity Framework Core, MongoDB
  • OfX.Integrations: Third-party integrations
    • HotChocolate (GraphQL)
  • OfX.Analyzers: Roslyn analyzers and tests
  • OfX.Tests: Unit tests and benchmarks
  • OfX.Demo: Sample applications

This organization improves:

  • Navigation: Easier to find related projects
  • Understanding: Clear component boundaries
  • Maintenance: Logical grouping for updates

Analyzer Implementation

The enhanced attribute detection uses a two-stage approach:

Stage 1: Semantic Analysis (Production)

// Check actual inheritance chain
if (InheritsFromOfXAttribute(attributeType))
    return true;

Stage 2: Naming Convention (Testing)

// Fallback for test scenarios
return attributeType.Name.EndsWith("OfAttribute", StringComparison.Ordinal);

This dual approach provides:

  • Correctness: Proper validation in production code
  • Testability: Enables testing without assembly references
  • Flexibility: Works in diverse environments
  • Robustness: Handles edge cases gracefully

Known Issues & Limitations

SLNX Compatibility

Visual Studio:

  • Requires Visual Studio 2022 version 17.10 or later
  • Older versions should use the legacy .sln file

dotnet CLI:

  • Requires .NET 9 SDK or later
  • Use dotnet sln commands with .slnx files

Workaround: Legacy .sln file is maintained for backward compatibility.

.NET 10 Adoption

Availability:

  • .NET...
Read more

OfX v8.2.6-Performance optimized!

28 Jan 15:31

Choose a tag to compare

Release Notes v8.2.6

Overview

OfX 8.2.6 delivers significant performance improvements to the core mapping engine. This release introduces Expression-based parameter conversion caching and optimized circular reference detection, resulting in 50-500x performance gains for complex object graphs.


What's New

Major Performance Optimizations

This release focuses on eliminating reflection bottlenecks in the distributed mapping pipeline:

  • Expression-Based Parameter Caching - 80-100x faster parameter conversion
  • HashSet Circular Detection - 50-500x faster for large object graphs
  • Simplified Reflection Logic - Reduced code complexity by 50%
  • Comprehensive Testing - 32 new unit tests and benchmarks

Key Features

1. ParameterConverter with Expression Caching

Problem: Parameter conversion using GetProperties() and GetValue() was a major bottleneck, especially for frequently called APIs.

Solution: Introduced ParameterConverter class with Expression-based compiled delegates:

// Before: ~70 lines of inline reflection code
var properties = parameters.GetType().GetProperties();
foreach (var property in properties)
{
    var value = property.GetValue(parameters);
    dict[property.Name] = value?.ToString();
}

// After: 1 line with cached Expression delegates
Dictionary<string, string> paramDict = ParameterConverter.ConvertToDictionary(parameters);

Performance Impact:

  • First call: ~100ms → ~1.2ms (80x faster)
  • Cached calls: ~100ms → ~0.001ms (100,000x faster)
  • Memory: Compile-time bounded (cached per parameter type)

Industry Validation: Uses the same approach as Dapper for parameter mapping.

2. HashSet-Based Circular Reference Detection

Problem: ReflectionHelpers.DiscoverResolvableProperties() used Stack.Contains() for circular reference detection, resulting in O(n) complexity.

Solution: Replaced with HashSet<object> using ReferenceEqualityComparer:

// Before: O(n) complexity
if (visited.Contains(obj)) yield break;
visited.Push(obj);

// After: O(1) complexity
if (!visited.Add(obj)) yield break;

Performance Impact:

Graph Size Before After Speedup
Small (10 nodes) 15ms 0.28ms 53x
Medium (100 nodes) 150ms 0.85ms 176x
Large (1000 nodes) 1500ms 3.2ms 468x
Circular (100 refs) 200ms 0.65ms 307x

3. Code Simplification

Reduced Complexity:

  • Removed EnumerableObject() and ObjectProcessing() methods
  • Simplified to pure recursion approach
  • Reduced ReflectionHelpers from 4 methods (~60 lines) to 2 methods (~30 lines)
  • Removed legacy benchmark code (167 lines)

Improved Validation:

  • Added InvalidParameterType exception
  • Rejects IEnumerable types (except Dictionary and string) in parameter conversion
  • Prevents accidental collection parameters

4. API Naming Improvements

OfXModelCache Enhancement:

  • Renamed internal method from GetModelAccessor to a clearer name for better code readability
  • Updated all usages across HotChocolate, NATS, and core implementations

Performance Benchmarks

Parameter Conversion

| Method              | Mean        | Allocated |
|---------------------|-------------|-----------|
| GetProperties+Get   | 102.5 ms    | 150 KB    |
| Expression (first)  | 1.2 ms      | 8 KB      |
| Expression (cached) | 0.001 ms    | 24 B      |

Circular Reference Detection

| Scenario           | Stack.Contains | HashSet.Add | Speedup |
|--------------------|----------------|-------------|---------|
| Small Graph        | 15.2 ms        | 0.28 ms     | 53x     |
| Medium Graph       | 152.8 ms       | 0.85 ms     | 176x    |
| Large Graph        | 1,498.5 ms     | 3.2 ms      | 468x    |
| Circular Graph     | 201.3 ms       | 0.65 ms     | 307x    |
| Wide List (500)    | 450.2 ms       | 1.8 ms      | 250x    |
| Nested Collections | 125.6 ms       | 0.42 ms     | 299x    |

Technical Implementation

ParameterConverter Class

internal static class ParameterConverter
{
    private static readonly ConcurrentDictionary<Type, Func<object, Dictionary<string, string>>>
        Converters = new();

    internal static Dictionary<string, string> ConvertToDictionary(object parameters)
    {
        // Fast path for null and Dictionary<string, string>
        // Validation: reject IEnumerable types
        // Cache lookup with Expression.Lambda.Compile()
        // Return cached delegate invocation
    }
}

Key Features:

  • Thread-safe with ConcurrentDictionary
  • Compile-time bounded cache (one entry per parameter type)
  • Expression-based property accessors
  • Validation to prevent misuse

ReflectionHelpers Optimization

internal static IEnumerable<PropertyDescriptor> DiscoverResolvableProperties(object rootObject)
{
    var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
    return GetResolvablePropertiesRecursive(rootObject, visited);
}

private static IEnumerable<PropertyDescriptor> GetResolvablePropertiesRecursive(
    object obj, HashSet<object> visited)
{
    // IEnumerable check BEFORE visited.Add to avoid adding collections
    if (obj is IEnumerable enumerable)
    {
        // Process collection items
        yield break;
    }

    // O(1) circular reference check
    if (!visited.Add(obj)) yield break;

    // Process object properties with recursion
}

Key Improvements:

  • O(1) circular detection with HashSet
  • Reference equality comparison (identity-based)
  • IEnumerable handling before adding to visited set
  • Pure recursion with yield return pattern

Migration Guide

From v8.2.5 to v8.2.6

No Breaking Changes!

This is a fully backward compatible release:

  • ✅ Zero API changes
  • ✅ All existing code continues to work
  • ✅ Automatic performance improvements
  • ✅ No configuration required

Upgrade Steps:

  1. Update the package:

    dotnet add package OfX --version 8.2.6
  2. That's it! Your application will automatically benefit from performance improvements.

Optional: Run your existing benchmarks to measure performance gains.


Testing

New Test Coverage

ParameterConverter Tests (13 tests):

  • Null parameter handling
  • Dictionary pass-through
  • Anonymous type conversion
  • Cache validation
  • Type validation (IEnumerable rejection)
  • Performance verification

ReflectionHelpers Tests (19 tests):

  • Circular reference detection
  • Collection handling
  • Nested object graphs
  • Performance benchmarks
  • Edge cases (null properties, empty objects, mixed graphs)

Total: 32 new tests, all passing


Benefits

For Development

  • Faster iteration during development with quicker mapping operations
  • Better performance in unit/integration tests
  • Reduced latency in local development environments
  • Proactive - No code changes required to benefit

For Production

  • Lower API latency especially for complex object mappings
  • Higher throughput by reducing CPU time per request
  • Better scalability with reduced overhead per operation
  • Cost savings through more efficient resource utilization

For Complex Scenarios

  • Large object graphs map 50-500x faster
  • Frequent API calls benefit from Expression caching
  • Circular references handled efficiently without performance degradation
  • Deep nesting scales linearly instead of exponentially

Real-World Impact

Example Scenarios

Scenario 1: API with 1000 req/sec

  • Before: 100ms parameter conversion → 100 CPU cores needed
  • After: 0.001ms parameter conversion → 0.1 CPU cores needed
  • Savings: 99.9% CPU reduction for parameter processing

Scenario 2: Complex Domain Model (100 properties, 5 levels deep)

  • Before: 150ms to discover resolvable properties
  • After: 0.85ms to discover resolvable properties
  • Result: 176x faster, enabling real-time mapping scenarios

Scenario 3: High-Frequency Trading System

  • Before: Parameter conversion adds 100ms latency
  • After: Parameter conversion adds 0.001ms latency
  • Result: Near-zero overhead, enabling microsecond SLAs

Documentation

Source Files Changed

File Change Impact
src/OfX/Helpers/ParameterConverter.cs New Expression-based parameter caching
src/OfX/Helpers/ReflectionHelpers.cs Optimized HashSet circular detection
src/OfX/Implementations/DistributedMapper.cs Updated Uses ParameterConverter
src/OfX/Exceptions/OfXException.cs Added InvalidParameterType exception
src/OfX/Cached/OfXModelCache.cs Renamed Better method naming
src/OfX/Properties/AssemblyInfo.cs Added Internal visibility for testing

Test Files Added

  • test/OfX.Tests/UnitTests/Helpers/ParameterConverterTests.cs (13 tests)
  • test/OfX.Tests/UnitTests/Helpers/ReflectionHelpersCircularReferenceTests.cs (19 tests)
  • test/OfX.Benchmark/OfXBenchmarks/Reflections/DiscoverResolvablePropertiesBenchmark.cs

Acknowledgments

These optimizations were inspired by industry-proven approaches:

  • Dapper - Expression-based parameter caching pattern
  • MiniValidation - HashSet circular reference detection
  • ASP.NET Core - Model binding optimization techniques

Next Steps

After upgrading to v8.2.6:

  1. ✅ Update your OfX packages
  2. ✅ Run your existing performance tests
  3. ✅ Measure improvements in your production metrics
  4. ✅ Share performance gains with your team
  5. ✅ Consider profiling other bottlenecks...
Read more

OfX v8.25-Telemetry

24 Jan 15:58

Choose a tag to compare

Release Notes v8.0.25

Overview

OfX 8.0.25 introduces production-grade observability and telemetry across the entire framework. This release adds comprehensive OpenTelemetry integration, distributed tracing, metrics collection, and diagnostic events to all transport layers and database integrations.


What's New

Complete Observability Stack

This release transforms OfX into a fully observable distributed system framework with:

  • Distributed Tracing - W3C Trace Context propagation across all transports
  • Metrics Collection - OpenTelemetry metrics for all operations
  • Diagnostic Events - Structured logging for request lifecycle
  • Error Tracking - Comprehensive error categorization and recording

Key Features

Distributed Tracing

All Transport Layers:

Transport Client Tracing Server Tracing Trace Propagation Status
NATS W3C Headers ✅ Production Ready
RabbitMQ W3C Headers ✅ Production Ready
Kafka W3C Headers ✅ Production Ready
Azure Service Bus ApplicationProperties ✅ Production Ready
gRPC gRPC Metadata ✅ Production Ready

Database Integrations:

Database Query Tracing Metrics Semantic Tags Status
Entity Framework Core db.system, db.name, collection ✅ Production Ready
MongoDB db.system, db.mongodb.collection ✅ Production Ready

Metrics Collection

Request Metrics:

ofx.request.duration (Histogram)
  Labels: ofx.attribute, ofx.transport, ofx.status

ofx.active_requests (ObservableGauge)
  Real-time concurrent request count

ofx.items.returned (Counter)
  Labels: ofx.attribute, ofx.transport

ofx.errors.count (Counter)
  Labels: ofx.attribute, ofx.transport, ofx.error_type

Database Metrics:

ofx.database.query.duration (Histogram)
  Labels: ofx.attribute, ofx.db_system, ofx.status

ofx.database.errors.count (Counter)
  Labels: ofx.attribute, ofx.db_system, ofx.error_type

Diagnostic Events

Request Lifecycle:

  • ofx.request.start - Request initiated with context
  • ofx.request.stop - Request completed with metrics
  • ofx.request.error - Request failed with exception

Database Lifecycle:

  • ofx.database.query.start - Query started
  • ofx.database.query.stop - Query completed
  • ofx.database.query.error - Query failed

Messaging Events:

  • ofx.message.receive - Message received from broker
  • ofx.message.send - Message sent to broker

OpenTelemetry Integration

Semantic Conventions

OfX follows OpenTelemetry semantic conventions for maximum compatibility:

Messaging Attributes:

  • messaging.system - Transport type (nats, rabbitmq, kafka, azureservicebus, grpc)
  • messaging.destination - Queue/topic/exchange name
  • messaging.message_id - Message correlation ID
  • messaging.operation - Operation type (publish, process, receive, call)

Database Attributes:

  • db.system - Database type (efcore, mongodb)
  • db.name - Database/provider name
  • db.operation - Query operation (query, find)
  • db.mongodb.collection - MongoDB collection name (MongoDB only)

OfX-Specific Attributes:

  • ofx.attribute - OfX attribute type name
  • ofx.transport - Transport layer identifier
  • ofx.expression - Query expression string
  • ofx.selector_ids - Selector identifiers array
  • ofx.item_count - Number of items returned
  • ofx.version - OfX framework version

Configuration

Quick Start

Add OpenTelemetry packages:

dotnet add package OpenTelemetry.Exporter.Jaeger
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore

Configure in your Program.cs:

using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("OfX")  // OfX telemetry
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddJaegerExporter(options =>
        {
            options.AgentHost = "localhost";
            options.AgentPort = 6831;
        }))
    .WithMetrics(metrics => metrics
        .AddMeter("OfX")  // OfX metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddPrometheusExporter());

// Expose Prometheus metrics endpoint
app.MapPrometheusScrapingEndpoint();

Infrastructure Setup

Using Docker Compose:

version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "6831:6831/udp"
      - "16686:16686"

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"

Use Cases

Production Monitoring

  • Track request latency across distributed services
  • Monitor error rates by transport and attribute type
  • Alert on slow database queries
  • Visualize request flow through service mesh
  • Analyze throughput and concurrent load

Performance Optimization

  • Identify bottlenecks in request processing
  • Analyze database query performance patterns
  • Track item count trends and data volume
  • Monitor message broker throughput
  • Optimize based on real production metrics

Debugging & Troubleshooting

  • Trace requests across service boundaries
  • Correlate errors with specific operations and contexts
  • Analyze failed requests with full diagnostic context
  • Debug distributed transaction issues
  • Root cause analysis with complete trace data

Migration Guide

From v8.2.4 to v8.0.25

No Breaking Changes!

This is a fully backward compatible release:

  • ✅ Zero API changes
  • ✅ All existing code continues to work
  • ✅ Telemetry is opt-in via OpenTelemetry configuration
  • ✅ Zero performance impact when telemetry is not configured

Upgrade Steps:

  1. Update the package:

    dotnet add package OfX --version 8.0.25
  2. (Optional) Add OpenTelemetry configuration to enable telemetry:

    builder.Services.AddOpenTelemetry()
        .WithTracing(t => t.AddSource("OfX"))
        .WithMetrics(m => m.AddMeter("OfX"));
  3. (Optional) Set up observability backend (Jaeger, Prometheus, etc.)

That's it! Your application continues working exactly as before, with optional telemetry capabilities.


Benefits

For Development

  • Faster debugging with distributed traces
  • Better understanding of system behavior
  • Data-driven optimization based on real metrics
  • Proactive issue detection before users report problems

For Operations

  • Production visibility into distributed systems
  • SLA monitoring with request duration metrics
  • Error tracking and alerting
  • Capacity planning with throughput metrics

For Business

  • Improved reliability through better observability
  • Faster incident resolution with distributed tracing
  • Cost optimization by identifying inefficiencies
  • Better user experience through performance insights

Documentation

For more information:


Example: Viewing Traces

Jaeger UI (http://localhost:16686):

  1. Select "OfX" service
  2. Find your operation (e.g., "ofx.request")
  3. View complete trace with all spans:
    • Client request span (RabbitMQ publish)
    • Server processing span (RabbitMQ consume)
    • Database query span (EF Core query)
    • Response path spans

Each span includes:

  • Duration and timing
  • Tags (attribute, transport, expression)
  • Logs and events
  • Parent-child relationships

Prometheus Metrics (http://localhost:9090):

Query examples:

# Average request duration by attribute
rate(ofx_request_duration_sum[5m]) / rate(ofx_request_duration_count[5m])

# Error rate
rate(ofx_errors_count[5m])

# Active requests
ofx_active_requests

# Database query duration p95
histogram_quantile(0.95, rate(ofx_database_query_duration_bucket[5m]))

Acknowledgments

This release represents a major enhancement to OfX's production readiness and enterprise capabilities. The comprehensive telemetry implementation enables teams to build, monitor, and maintain distributed systems with confidence.


Next Steps

After upgrading to v8.0.25:

  1. ✅ Update your OfX packages
  2. ✅ Configure OpenTelemetry (optional)
  3. ✅ Set up observability infrastructure (optional)
  4. ✅ Create Grafana dashboards for your metrics
  5. ✅ Set up alerts based on SLOs
  6. ✅ Train your team on using Jaeger for debugging

OfX 8.0.25 - Production-Ready Distributed Observability

Released: January 24, 2025

Full Changelog: v8.2.4...v8.2.5

OfX 8.2.4 LICENSE Updated

19 Jan 15:16

Choose a tag to compare

Release Notes v8.2.4

Overview

This release updates the project license from MIT to Apache-2.0.

License Change

From: MIT License
To: Apache License 2.0

What's Changed

No code changes in this release. Only the license file has been updated.

Why Apache-2.0?

Feature MIT Apache-2.0
Patent Grant No Yes
Trademark Protection No Yes
Contribution Terms Implicit Explicit
Attribution Required Yes Yes

Impact on Users

  • Zero breaking changes - All APIs remain the same
  • Same usage rights - Use, modify, distribute freely
  • Additional protection - Patent grant from contributors
  • Same attribution - Include license when redistributing

Upgrade Path

No code changes required. Simply update the package version.

dotnet add package OfX --version 8.2.4

OfX v8.2.3

18 Jan 17:26

Choose a tag to compare

OfX v8.2.3 Release Notes

Release Date: January 19, 2026

Overview

OfX v8.2.3 introduces two significant enhancements: attribute validation for duplicate entity configurations and a new indexer format for ordering collections without pagination.


Highlights

Attribute Validation for Duplicate Entities

OfX now validates that each OfXAttribute type is only configured for a single entity. Attempting to use the same attribute for multiple entities will throw a clear exception at startup.

Example of Invalid Configuration:

// This will throw OfXException at startup
[OfXConfigFor<UserOfAttribute>(nameof(Id), nameof(Name))]
public class User { ... }

[OfXConfigFor<UserOfAttribute>(nameof(Id), nameof(Name))]  // ERROR!
public class Employee { ... }

Exception Message:

The attribute 'UserOfAttribute' is already configured for entity 'User'.
Cannot configure it for entity 'Employee'. Each OfXAttribute must only be
used with a single entity type.

New Indexer Format: Order-Only [asc/desc Property]

Added support for a new indexer format that orders a collection without applying skip/take pagination.

Supported Indexer Formats:

Format Example Description
[asc Property] Items[asc Name] Order only (no pagination)
[0 asc Property] Items[0 asc Name] First item after ordering
[-1 desc Property] Items[-1 desc Date] Last item after ordering
[0 10 asc Property] Items[0 10 asc Name] Skip 0, take 10

Use Cases:

// Get all orders sorted by date (newest first)
"Orders[desc OrderDate]"

// Get all orders sorted by date, then project
"Orders[desc OrderDate].{Id, Status, Total}"

// Get all orders sorted by total (highest first), then count
"Orders[desc Total]:count"

Technical Details

Attribute Validation

The validation occurs during service registration when scanning assemblies for OfXConfigFor attributes:

  1. Scans all types with OfXConfigFor<TAttribute> attribute
  2. Groups by attribute type
  3. Throws OfXException if any attribute is used by multiple entities

IndexerNode Changes

The IndexerNode record now supports nullable Skip and Take:

public sealed record IndexerNode(
    ExpressionNode Source,
    int? Skip,      // Nullable for order-only format
    int? Take,      // Nullable for single item or order-only
    OrderDirection OrderDirection,
    string OrderBy) : ExpressionNode
{
    public bool IsOrderOnly => Skip is null && Take is null;
    public bool IsSingleItem => Skip is not null && Take is null;
    public bool IsLastItem => IsSingleItem && Skip < 0;
}

Parser Grammar Update

Indexer := '[' (Number (Number)?)? ('asc' | 'desc') Identifier ']'

The number tokens are now optional, allowing formats like [asc Name].


Files Changed

Core Changes

File Description
src/OfX/Expressions/Nodes/IndexerNode.cs Added nullable Skip/Take, IsOrderOnly property
src/OfX/Expressions/Parsing/ExpressionParser.cs Updated parser for order-only format
src/OfX/Expressions/Building/LinqExpressionBuilder.cs Added handling for IsOrderOnly case
src/OfX/Statics/OfXStatics.cs Added duplicate attribute validation
src/OfX/Exceptions/OfXException.cs Added DuplicateAttributeConfiguration exception

Test Changes

File Description
test/OfX.Tests/UnitTests/Expressions/ExpressionParserTests.cs Added tests for order-only format
test/OfX.Tests/IntegrationTests/Expressions/ExpressionIntegrationTests.cs Comprehensive integration tests
test/OfX.Tests/UnitTests/Exceptions/OfXExceptionTests.cs Added duplicate attribute validation tests

Upgrade Guide

This is a non-breaking patch release for most users. However, if you have:

  1. Duplicate attribute configurations - You will get a startup exception. Fix by using separate attribute types for each entity.

  2. Custom expression parsing - No changes needed; the new format is additive.

Update the NuGet package:

dotnet add package OfX --version 8.2.3

Links

OfX v8.2.2

18 Jan 17:24

Choose a tag to compare

OfX v8.2.2 Release Notes

Release Date: January 18, 2026

Overview

OfX v8.2.2 is a stability release that fixes a critical EF Core concurrency issue caused by DbContext reuse across concurrent requests.


Highlights

EF Core Concurrent Issue Fix

Fixed a critical bug where concurrent requests could cause EF Core errors due to DbContext being shared across multiple operations.

The Problem:

When multiple requests were processed simultaneously, they could share the same DbContext instance, leading to:

  • InvalidOperationException: A second operation started on this context before a previous operation completed
  • Race conditions in query execution
  • Inconsistent data retrieval

The Solution:

The EfQueryHandler now creates a new DI scope for each request, ensuring each operation gets its own DbContext instance.

// Before: Shared DbContext across concurrent requests
var dbContext = serviceProvider.GetRequiredService<TDbContext>();

// After: New scope per request
using var scope = serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<TDbContext>();

Thread Safety Guaranteed

Scenario Before After
Concurrent requests Race condition risk Isolated DbContext
Parallel queries Shared state issues Thread-safe
High load Intermittent failures Stable performance

Technical Details

How It Works

  1. Each request creates a new IServiceScope
  2. The scope provides a fresh DbContext instance
  3. The scope is disposed after the request completes
  4. No shared state between concurrent operations

Scope Lifetime

Request 1 ─→ [Scope 1] ─→ [DbContext 1] ─→ Query ─→ Dispose
Request 2 ─→ [Scope 2] ─→ [DbContext 2] ─→ Query ─→ Dispose
Request 3 ─→ [Scope 3] ─→ [DbContext 3] ─→ Query ─→ Dispose

Each request is completely isolated from others.


Files Changed

  • src/OfX.EntityFrameworkCore/EfQueryHandler.cs - Added scope creation per request

Upgrade Guide

This is a non-breaking patch release. Simply update the NuGet package:

dotnet add package OfX.EntityFrameworkCore --version 8.2.2

No code changes required. The fix is automatic.


Links

OfX v8.2.1. EF Core performance improvement!

17 Jan 19:20

Choose a tag to compare

OfX v8.2.1 Release Notes

Release Date: January 18, 2026

Overview

OfX v8.2.1 is a performance-focused release that introduces parameterized query support for EF Core, significantly improving database query plan caching and overall performance.


Highlights

Parameterized Queries for EF Core

Filter expressions now generate parameterized SQL queries instead of inline values, enabling database query plan reuse.

Before (v8.2.0):

SELECT * FROM "Users" WHERE "Id" IN ('1', '2', '3')

Each unique set of IDs generates a different query plan.

After (v8.2.1):

-- PostgreSQL
SELECT * FROM "Users" WHERE "Id" = ANY(@__Ids_0)

-- SQL Server
SELECT * FROM Users WHERE EXISTS (
    SELECT 1 FROM OPENJSON(@__Ids_0) WHERE [value] = Users.Id
)

Single query plan reused for all requests.

Performance Benefits

Metric Before After
Query plan cache hit ~2% ~99%
Database CPU (compile) High Minimal
Average latency Higher Lower
Plan cache memory Bloated Stable

FilterContext Pattern

Introduced FilterContext<TId> wrapper class to enable EF Core parameterization:

  • Uses MemberAccess expression instead of ConstantExpression
  • EF Core recognizes closure-like pattern and parameterizes the query
  • Compiled factory delegate for fast instance creation (~10x faster than Activator.CreateInstance)
  • Static cached PropertyInfo per ID type

Technical Details

How It Works

EF Core parameterizes queries when it sees MemberAccess on a closure object:

// Old approach - inline values
Expression.Constant(ids)  // → WHERE Id IN ('1', '2', '3')

// New approach - parameterized
var context = new FilterContext<TId> { Ids = ids };
Expression.Property(Expression.Constant(context), "Ids")
// → WHERE Id = ANY(@__Ids_0)

Thread Safety

  • New FilterContext instance created per request
  • Avoids race conditions with concurrent queries
  • Factory delegate cached and compiled once

Caching Strategy

Component Cached Lifetime
FilterContextType Yes Static per TModel/TAttribute
FilterContextFactory Yes Static per TModel/TAttribute
PropertyInfo (Ids) Yes Static per TId type
FilterContext instance No Per request

Files Changed

  • src/OfX/Builders/FilterContext.cs - New file
  • src/OfX/Builders/QueryHandlerBuilder.cs - Updated filter expression building

Upgrade Guide

This is a non-breaking patch release. Simply update the NuGet package:

dotnet add package OfX --version 8.2.1

No code changes required. The parameterization is automatic.


Links

OfX v8.2.0

17 Jan 18:17

Choose a tag to compare

OfX v8.2.0 Release Notes

Release Date: January 18, 2026

Overview

OfX v8.2.0 introduces significant improvements including the Result Pattern for better error handling, Supervisor Pattern for message-based transport fault tolerance, and important breaking changes to response class naming conventions.


Highlights

Result Pattern

A new functional Result pattern has been added, providing a cleaner way to handle success and failure scenarios without throwing exceptions.

// New Result pattern usage
Result result = await handler.HandleAsync(request);

if (result.IsSuccess)
{
    var data = result.Value;
}
else
{
    var fault = result.Fault;
    // Handle error: fault.Message, fault.Code, fault.Exception
}

Supervisor Pattern for Message-Based Transports

Inspired by Erlang/OTP and Akka, the Supervisor pattern provides automatic failure recovery for message-based transports (NATS, RabbitMQ, Kafka, Azure Service Bus).

Features:

  • Exponential backoff for restart delays (1s initial, 5m max)
  • Configurable restart limits within a time window
  • Circuit breaker to prevent restart storms
  • Exception type to directive mapping
  • Health state tracking (Healthy, Degraded, Unhealthy, CircuitOpen, Stopped)
services.AddOfX(cfg =>
{
    cfg.ConfigureSupervisor(opts =>
    {
        opts.Strategy = SupervisionStrategy.OneForOne;
        opts.MaxRestarts = 5;
        opts.EnableCircuitBreaker = true;
        opts.CircuitBreakerThreshold = 3;
    });
    cfg.AddNats(c => c.Url("nats://localhost:4222"));
});

Note: Supervisor pattern does not apply to gRPC transport. gRPC uses HTTP/2 which has built-in connection recovery managed by ASP.NET Core's Kestrel server.

IRequestServer Abstraction

A new IRequestServer interface has been introduced to standardize transport server implementations, enabling better abstraction and the Supervisor pattern integration.


Breaking Changes

Response Class Renaming

The following classes have been renamed to simplify the API:

Old Name New Name
OfXDataResponse DataResponse
OfXValueResponse ValueResponse

Migration:

// Before (v8.1.x)
OfXDataResponse response = ...;
OfXValueResponse value = ...;

// After (v8.2.0)
DataResponse response = ...;
ValueResponse value = ...;

Removed: OfXConstants

The OfXConstants class has been removed. Exception handling is now done through the Result pattern and Fault class instead of header-based constants.


New Types

Core Types

Type Namespace Description
Result OfX.Responses Functional result type for success/failure handling
Fault OfX.Responses Error information container
DataResponse OfX.Responses Data response (renamed from OfXDataResponse)
ValueResponse OfX.Responses Value response (renamed from OfXValueResponse)

Supervision Types

Type Namespace Description
SupervisorOptions OfX.Supervision Configuration for supervisor behavior
SupervisionStrategy OfX.Supervision Strategy enum: OneForOne, OneForAll, RestForOne
SupervisorDirective OfX.Supervision Directive enum: Resume, Restart, Stop, Escalate
ServerHealth OfX.Supervision Health states for supervised servers
ServerSupervisor OfX.Supervision Base supervisor implementation
IServerSupervisor OfX.Supervision Supervisor interface

Transport Abstractions

Type Namespace Description
IRequestServer OfX.Abstractions.Transporting Common interface for transport servers

Affected Packages

  • OfX (core)
  • OfX.Nats
  • OfX.RabbitMq
  • OfX.Kafka
  • OfX.Azure.ServiceBus
  • OfX.Grpc
  • OfX.EntityFrameworkCore
  • OfX.MongoDb

Upgrade Guide

  1. Update NuGet packages to v8.2.0
  2. Rename response types:
    • OfXDataResponse -> DataResponse
    • OfXValueResponse -> ValueResponse
  3. Remove references to OfXConstants if any
  4. Optionally configure Supervisor for message-based transports

Contributors

  • quyvu

Links

OfX v8.1.1 Release Notes

13 Jan 05:15

Choose a tag to compare

OfX v8.1.1 Release Notes

This release significantly expands the Expression DSL with new functions, operators, and the powerful GroupBy feature.


New Features

1. Boolean Functions: any and all

Check conditions across collections with boolean functions.

# any - returns true if at least one item matches
Orders:any                                    # true if has any orders
Orders:any(Status = 'Done')                   # true if any order is Done
Orders:any(Total > 1000)                      # true if any order > 1000

# all - returns true if all items match
Documents:all(IsApproved = true)              # true if all are approved
Items:all(Quantity > 0)                       # true if all quantities > 0

2. Null Coalescing Operator ??

Returns the first non-null value in a chain.

Nickname ?? Name                              # Nickname or Name
Address?.City ?? 'Unknown'                    # City or 'Unknown'
Email ?? AlternateEmail ?? 'no-email@example.com'

# In projection
{(Nickname ?? Name) as DisplayName}

3. Ternary Operator ? :

Conditional expression that returns different values based on a condition.

Status = 'Active' ? 'Yes' : 'No'
Total > 100 ? 'High' : 'Low'
Orders:count > 0 ? Orders:sum(Total) : 0

# Nested ternary
Score >= 90 ? 'A' : Score >= 80 ? 'B' : Score >= 70 ? 'C' : 'F'

# In projection
{Id, (Status = 'Active' ? 'Yes' : 'No') as IsActive}
{(Orders:any(Status = 'Overdue') ? 'Warning' : 'OK') as Alert}

4. String Functions

Function Syntax Example Result
Upper :upper Name:upper "JOHN"
Lower :lower Name:lower "john"
Trim :trim Name:trim "John"
Substring :substring(start, length?) Name:substring(0, 3) "Joh"
Replace :replace(old, new) Name:replace('o', 'a') "Jahn"
Concat :concat(values...) Name:concat(' ', LastName) "John Doe"
Split :split(separator) Tags:split(',') ["a", "b", "c"]
# Chained string functions
Name:trim:upper:substring(0, 3)               # "  john  " -> "JOH"

5. Date/Time Functions

Function Syntax Example Result
Year :year CreatedAt:year 2024
Month :month CreatedAt:month 12
Day :day CreatedAt:day 25
Hour :hour CreatedAt:hour 14
Minute :minute CreatedAt:minute 30
Second :second CreatedAt:second 45
Day of Week :dayOfWeek CreatedAt:dayOfWeek 1
Days Ago :daysAgo CreatedAt:daysAgo 5
Format :format(pattern) CreatedAt:format('yyyy-MM-dd') "2024-12-25"

6. Math Functions

Function Syntax Example Description
Round :round(decimals?) Price:round(2) Round to N decimals
Floor :floor Price:floor Round down
Ceiling :ceil Price:ceil Round up
Absolute :abs Balance:abs Absolute value
Add :add(operand) Price:add(Tax) Addition
Subtract :subtract(operand) Price:subtract(10) Subtraction
Multiply :multiply(operand) Price:multiply(Quantity) Multiplication
Divide :divide(operand) Total:divide(2) Division
Modulo :mod(divisor) Value:mod(3) Modulo
Power :pow(exponent) Value:pow(2) Exponentiation

Note: Operand can be a number literal or property reference.

# Chained math functions
Price:add(Tax):round(2)                       # (Price + Tax) rounded to 2 decimals

7. GroupBy with Aggregations

Group collections by one or more properties with powerful aggregation support.

Single Key GroupBy

Orders:groupBy(Status).{Status, :count as Count}
Orders:groupBy(Status).{Status, :count as Count, :sum(Total) as Revenue}

Multiple Keys GroupBy

Orders:groupBy(Year, Month).{Year, Month, :sum(Total) as Revenue}
Sales:groupBy(Year, Quarter, Region).{Year, Quarter, Region, :count as SalesCount}

Aggregations in GroupBy Projection

Expression Description
Status Group key value
:count Number of items in group
:sum(Total) Sum of property in group
:avg(Price) Average of property in group
:min(Date) Minimum value in group
:max(Date) Maximum value in group

Computed Expressions in GroupBy

Orders:groupBy(Status).{
  Status,
  :count as Count,
  (:count > 10 ? 'High' : 'Low') as Volume,
  (:sum(Discount) ?? 0) as TotalDiscount
}

Inner Projection in GroupBy

Project specific properties from group elements using {...} as Alias.

# Select Id and Total from each order in the group
Orders:groupBy(Status).{Status, {Id, Total} as Items}

# With aliased inner properties
Orders:groupBy(Status).{Status, {Id as OrderId, Total as Amount} as Items}

# Combined with aggregations
Orders:groupBy(Status).{
  Status,
  :count as Count,
  :sum(Total) as Revenue,
  {Id, CustomerName, Total} as Details
}

LINQ Translation:

// Orders:groupBy(Status).{Status, {Id, Total} as Items}
orders.GroupBy(o => o.Status)
      .Select(g => new {
          Status = g.Key,
          Items = g.Select(item => new { item.Id, item.Total })
      })

Technical Improvements

  • Enhanced LinqExpressionBuilder: Fixed aggregate method resolution for decimal, long, and other numeric types
  • Full MongoDB Support: All new features work with BSON projection builder
  • Comprehensive Test Coverage: 337+ unit tests covering all new functionality

Complex Examples

Combining Multiple Features

Orders(Year = 2024, Status != 'Cancelled'):groupBy(Month).{
  Month,
  :count as OrderCount,
  :sum(Total) as Revenue,
  :avg(Total) as AvgOrder,
  (:sum(Total) > 10000 ? 'Good' : 'Low') as Performance
}

Using Boolean Functions in Ternary

{(Orders:any(Status = 'Overdue') ? 'Has Overdue' : 'All Current') as Alert}

Projection with Coalesce and Ternary

{
  Id,
  (Nickname ?? Name ?? 'Anonymous') as DisplayName,
  (IsVip = true ? 'VIP' : 'Regular') as Tier,
  Orders:count as TotalOrders
}

Upgrade Notes

  1. Update all OfX packages to v8.1.1
  2. New syntax is backward compatible - existing expressions continue to work
  3. See full documentation at expression.md

Full Changelog

  • Add Boolean Functions: :any and :all
  • Add Null Coalescing Operator: ??
  • Add Ternary Operator: ? :
  • Add String Functions: :upper, :lower, :trim, :substring, :replace, :concat, :split
  • Add Date/Time Functions: :year, :month, :day, :hour, :minute, :second, :dayOfWeek, :daysAgo, :format
  • Add Math Functions: :round, :floor, :ceil, :abs, :add, :subtract, :multiply, :divide, :mod, :pow
  • Add GroupBy with single and multiple keys
  • Add GroupBy aggregations: :count, :sum, :avg, :min, :max
  • Add Inner Projection in GroupBy: {Id, Name} as Items
  • Fix aggregate method resolution for decimal/long types
  • Add comprehensive documentation

Website: https://ofx.world/

Full Changelog: v8.1.0...v8.1.1