Releases: quyvu01/OfX
v9.0.0
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 classSetMaxObjectSpawnTimes()— unclear what "object spawn" meansApplicationModels/— generic ASP.NET-style catch-all folder nameStatics/,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.0Step 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 testDesign 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:
- Clean break is better — deprecated names create confusion about "which one to use"
- All changes are mechanical — simple find-and-replace migration
- Major version signals intent — v9.0.0 clearly communicates breaking changes
- 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
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 libraryOfX-gRPC- gRPC transportOfX-Kafka- Kafka transportOfX-Nats- NATS transportOfX-RabbitMq- RabbitMQ transportOfX-Azure.ServiceBus- Azure Service Bus transportOfX-Aws.Sqs- AWS SQS transportOfX-EFCore- Entity Framework Core integrationOfX-MongoDb- MongoDB integrationOfX-HotChocolate- HotChocolate GraphQL integrationOfX-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
# ... etcStep 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.xStep 4: Test
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run tests
dotnet testNo 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:
- Same Functionality: All features work exactly as before
- Better Testing: Enhanced analyzer test coverage
- Modern Tooling: SLNX support in latest IDEs
- 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.slnxTesting & 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
.slnfile
dotnet CLI:
- Requires .NET 9 SDK or later
- Use
dotnet slncommands with.slnxfiles
Workaround: Legacy .sln file is maintained for backward compatibility.
.NET 10 Adoption
Availability:
- .NET...
OfX v8.2.6-Performance optimized!
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()andObjectProcessing()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
InvalidParameterTypeexception - Rejects IEnumerable types (except Dictionary and string) in parameter conversion
- Prevents accidental collection parameters
4. API Naming Improvements
OfXModelCache Enhancement:
- Renamed internal method from
GetModelAccessorto 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:
-
Update the package:
dotnet add package OfX --version 8.2.6
-
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:
- ✅ Update your OfX packages
- ✅ Run your existing performance tests
- ✅ Measure improvements in your production metrics
- ✅ Share performance gains with your team
- ✅ Consider profiling other bottlenecks...
OfX v8.25-Telemetry
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 contextofx.request.stop- Request completed with metricsofx.request.error- Request failed with exception
Database Lifecycle:
ofx.database.query.start- Query startedofx.database.query.stop- Query completedofx.database.query.error- Query failed
Messaging Events:
ofx.message.receive- Message received from brokerofx.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 namemessaging.message_id- Message correlation IDmessaging.operation- Operation type (publish, process, receive, call)
Database Attributes:
db.system- Database type (efcore, mongodb)db.name- Database/provider namedb.operation- Query operation (query, find)db.mongodb.collection- MongoDB collection name (MongoDB only)
OfX-Specific Attributes:
ofx.attribute- OfX attribute type nameofx.transport- Transport layer identifierofx.expression- Query expression stringofx.selector_ids- Selector identifiers arrayofx.item_count- Number of items returnedofx.version- OfX framework version
Configuration
Quick Start
Add OpenTelemetry packages:
dotnet add package OpenTelemetry.Exporter.Jaeger
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCoreConfigure 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:
-
Update the package:
dotnet add package OfX --version 8.0.25
-
(Optional) Add OpenTelemetry configuration to enable telemetry:
builder.Services.AddOpenTelemetry() .WithTracing(t => t.AddSource("OfX")) .WithMetrics(m => m.AddMeter("OfX"));
-
(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):
- Select "OfX" service
- Find your operation (e.g., "ofx.request")
- 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:
- ✅ Update your OfX packages
- ✅ Configure OpenTelemetry (optional)
- ✅ Set up observability infrastructure (optional)
- ✅ Create Grafana dashboards for your metrics
- ✅ Set up alerts based on SLOs
- ✅ 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
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.4OfX v8.2.3
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:
- Scans all types with
OfXConfigFor<TAttribute>attribute - Groups by attribute type
- Throws
OfXExceptionif 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:
-
Duplicate attribute configurations - You will get a startup exception. Fix by using separate attribute types for each entity.
-
Custom expression parsing - No changes needed; the new format is additive.
Update the NuGet package:
dotnet add package OfX --version 8.2.3Links
OfX v8.2.2
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
- Each request creates a new
IServiceScope - The scope provides a fresh DbContext instance
- The scope is disposed after the request completes
- 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.2No code changes required. The fix is automatic.
Links
OfX v8.2.1. EF Core performance improvement!
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
MemberAccessexpression instead ofConstantExpression - EF Core recognizes closure-like pattern and parameterizes the query
- Compiled factory delegate for fast instance creation (~10x faster than
Activator.CreateInstance) - Static cached
PropertyInfoper 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
FilterContextinstance 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 filesrc/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.1No code changes required. The parameterization is automatic.
Links
OfX v8.2.0
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
- Update NuGet packages to v8.2.0
- Rename response types:
OfXDataResponse->DataResponseOfXValueResponse->ValueResponse
- Remove references to
OfXConstantsif any - Optionally configure Supervisor for message-based transports
Contributors
- quyvu
Links
OfX v8.1.1 Release Notes
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
- Update all OfX packages to v8.1.1
- New syntax is backward compatible - existing expressions continue to work
- See full documentation at expression.md
Full Changelog
- Add Boolean Functions:
:anyand: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