diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 0747a7a..c76a51d 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -418,8 +418,8 @@ applyTo: '**/*'
- **Communication Rules:**
- All communication must go through Contract interfaces and proxy implementations
- Use fastest appropriate protocol: in-process DI β gRPC β HTTP/2 β message queues
- - Prevent direct sibling communication; use message bus or mediator patterns
- - Implement circuit breakers and retry policies in all external communications
+ - Prevent direct sibling communication; Use parent component; If manager use message bus;
+ - Implement circuit breakers and retry policies in all communications
- **Composition over Inheritance:** Compose behaviors to keep components focused and testable
### Cross-Cutting Concerns
@@ -462,7 +462,8 @@ applyTo: '**/*'
- **Production:** Live production environment
- **Configuration Management:** Use environment-specific `AppSettings.json` files:
- `AppSettings.json` (base configuration)
- - `AppSettings.Development.json` (development overrides)
+ - `AppSettings.Local.json` (local environment)
+ - `AppSettings.Development.json` (development environment)
- `AppSettings.Testing.json` (testing environment)
- `AppSettings.Staging.json` (staging environment)
- `AppSettings.Production.json` (production environment)
diff --git a/.github/csharp.instructions.md b/.github/csharp.instructions.md
index 2f9d3cf..84cb592 100644
--- a/.github/csharp.instructions.md
+++ b/.github/csharp.instructions.md
@@ -1,4 +1,4 @@
-ο»Ώ---
+---
# π§ Copilot Instruction Metadata
version: 1.0.0
schema: 1
@@ -36,10 +36,10 @@ Applies to `.cs`, `.razor`, `.csproj`, and `.sln` files.
- Address nullable reference warnings (CS8602) with proper null checks.
## Project Structure
-- **Central Package Management**: Currently disabled (`ManagePackageVersionsCentrally=false`).
+- **Central Package Management**: Currently enabled (`ManagePackageVersionsCentrally=true`).
- **Solution structure**: Standard .sln file excludes WinUI packaging project (.wapproj).
- **Dependencies**: `Directory.Build.props` defines common properties and version variables.
-- **Package versions**: Orleans 9.2.1, Aspire 9.5.2, MSTest 4.0.1, FluentAssertions 8.8.0.
+- **Package versions**: Orleans (latest), Aspire (latest), MSTest (latest), FluentAssertions (latest).
## Extension Methods Pattern
- Create static extension classes in dedicated `Model.Extensions/` project.
@@ -49,9 +49,9 @@ Applies to `.cs`, `.razor`, `.csproj`, and `.sln` files.
- Extension methods enable testability without modifying core models.
## Testing
-- Use MSTest 4.0.1 for all unit and integration tests.
+- Use MSTest (latest) for all unit and integration tests.
- Use `[TestMethod]` with `[DataRow(...)]` for data-driven tests.
-- Use FluentAssertions 8.8.0 for readable assertions.
+- Use FluentAssertions (latest) for readable assertions.
- Use `[TestInitialize]` and `[TestCleanup]` for setup/teardown.
- Follow Arrange-Act-Assert structure.
- Organize tests with `#region` blocks for logical grouping.
@@ -59,4 +59,8 @@ Applies to `.cs`, `.razor`, `.csproj`, and `.sln` files.
## π Changelog
### 1.0.0 (2025-10-03)
-- Added metadata header (initial versioning schema).
\ No newline at end of file
+- Added metadata header (initial versioning schema).
+
+### 1.1.0 (2025-10-10)
+- Established C#/.NET coding standards and best practices.
+- Improved project structure and organization.
diff --git a/.nuget/NuGet/NuGet.config b/.nuget/NuGet/NuGet.config
index 7fc174c..6690f5a 100644
--- a/.nuget/NuGet/NuGet.config
+++ b/.nuget/NuGet/NuGet.config
@@ -5,13 +5,13 @@
-
+
-
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 44b1ec2..fa50d6a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
-
+ net8.0
Library
latest
enable
@@ -13,8 +13,8 @@
true
- Ivan Jones
- Visionary Coder LLC
+ Visionary Coder
+ Visionary Coder
Copyright Β© $([System.DateTime]::Now.Year)
MIT
true
@@ -60,8 +60,4 @@
false
-
-
-
-
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 84854bf..3a24f7e 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -8,16 +8,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
index bffcca4..1d1ec48 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,40 +4,43 @@
true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index 692acdc..fb6331d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://www.nuget.org/packages/VisionaryCoder.Framework.Core)
[](https://opensource.org/licenses/MIT)
-A modular, enterprise-grade framework starting with a single foundational library (`VisionaryCoder.Framework`) and accompanying test project. The repository emphasizes **clean, reproducible, and automated development** with .NET 8 (ready for forward compatibility to .NET 10). Future packages will evolve incrementally via ADRs.
+A modular, enterprise-grade framework starting from a single foundational library. This repository contains the source, documentation, samples and tests for the VisionaryCoder Framework ecosystem.
---
@@ -12,8 +12,8 @@ A modular, enterprise-grade framework starting with a single foundational librar
```bash
# Clone
-git clone https://github.com/visionarycoder/vc.git
-cd vc
+git clone https://github.com/visionarycoder/Framework.git
+cd Framework
# Restore
dotnet restore VisionaryCoder.Framework.sln
@@ -25,64 +25,59 @@ dotnet test VisionaryCoder.Framework.sln --configuration Release
---
-## π¦ Current Solution Contents
+## π¦ Solution Overview
-| Project | Type | Description |
-|---------|------|-------------|
-| `VisionaryCoder.Framework` | Library | Core framework primitives (configuration, results, options, providers, proxy abstractions). |
-| `VisionaryCoder.Framework.Tests` | Test Project | Unit tests validating core behaviors (results, request/correlation IDs, options). |
+This repository currently contains a single main library that aggregates foundational capabilities. The intent is to progressively decompose this monolith into smaller packages (see ADRs and roadmap in `docs/`), and this README serves as the top-level index linking to module-level READMEs and developer guidance to make that process easier.
-Planned future packages (tracked via ADRs) will be introduced gradually rather than pre-listed. See ADR index for roadmap context.
+### Projects
-## ποΈ Repository Structure (High-Level)
+- `src/VisionaryCoder.Framework` β Core library and shared utilities (see `src/VisionaryCoder.Framework/README.md`)
+- `tests/VisionaryCoder.Framework.Tests` β Unit tests validating framework behaviors
-```text
-/.copilot # Modular AI assistant instruction set (base + C# + patterns + standards)
-/docs # Documentation (ADRs, best-practices capsules, diagrams, reviews, onboarding)
-/src/VisionaryCoder.Framework # Core library source
-/tests/VisionaryCoder.Framework.Tests # Unit tests
-/.github # Global Copilot instructions & workflows
-```
+### Module READMEs (entry points)
----
+- Core project README: `src/VisionaryCoder.Framework/README.md`
+- Filtering subsystem: `src/VisionaryCoder.Framework/Filtering/README.md`
+- Querying serialization & helpers: `src/VisionaryCoder.Framework/Querying/README.md`
+- Documentation and architecture decisions: `docs/` (ADRs, best-practices, diagrams)
-## ποΈ Architecture Overview
+Use these module READMEs as the canonical documentation when splitting the project into multiple packages.
-The framework follows **Volatility-Based Decomposition (VBD)** principles. While the current library aggregates foundational concerns, future decomposition will create distinct Manager, Engine, and Accessor component packages as volatility boundaries emerge.
+## ποΈ Repository Structure (High-Level)
-Core library already enforces:
+```text
+/.copilot
+/docs
+/src/VisionaryCoder.Framework
+ ββ Filtering/
+ ββ Querying/
+ ββ VisionaryCoder.Framework.csproj
+/tests/VisionaryCoder.Framework.Tests
+/.github
+```
-- Contract-first abstractions for providers & proxies
-- Structured result + request/correlation context handling
-- Dependency injection integration & options binding
-- Early extensibility points for caching, security, querying
+## π Documentation & Roadmap
----
+- Architectural Decision Records (ADRs): `docs/adr/index.md`
+- Design diagrams and best-practice capsules: `docs/*`
+- Roadmap notes: `docs/reviews/*`
-## π Documentation & Guidance
+## π§ How to subdivide this repo (next steps)
-- **ADRs**: `docs/adr/index.md` (recent: ADR-0004 modular Copilot instructions)
-- **Best Practices Capsules**: `docs/best-practices/*/readme.md` (architecture, security, observability, etc.)
-- **Copilot Instructions**: `.github/copilot-instructions.md` (enterprise baseline) and `.copilot/copilot-instructions.md` (modular hub)
-- **Design Patterns Guidance**: `.copilot/design-patterns.instructions.md`
-- **C# Generation Heuristics**: `.copilot/csharp.instructions.md`
-- **Repository Standards**: `.copilot/repo-standards.md`
+If you plan to split this repository into multiple packages, follow these high-level steps:
-## π€ Contributing
+1. Identify volatility boundaries using VBD (Volatility-Based Decomposition). Good candidates: Filtering, Querying/Serialization, Execution Strategies, POCO helpers, EFCore adapters.
+2. Create new projects under `src/` for each package and move code with one-class-per-file, preserving namespaces (e.g., `VisionaryCoder.Framework.Filtering.Abstractions`).
+3. Keep `IFilterExecutionStrategy` and other small provider-agnostic interfaces in their own `*.Abstractions` package to avoid circular references.
+4. Introduce `VisionaryCoder.Framework.*.csproj` projects with clear dependencies and update solution file.
+5. Add module README files (use those in this repo as templates) and ADRs to justify the split.
-Contributions are welcomeβplease open an issue or ADR proposal before large architectural changes. Align new code with:
+## π€ Contributing
-1. Naming & layering rules (see global Copilot instructions)
-2. Volatility boundaries (introduce new packages only when volatility justifies extraction)
-3. Modular instruction consistency (update domain index + ADR when extending guidance)
+Contributions are welcome. Please open an issue or ADR proposal for large architectural changes. Keep PRs focused and update module READMEs when moving code.
---
-## π License
+This document is the canonical solution-level index. See module READMEs for implementation details and examples.
-MIT License β see [LICENSE](LICENSE).
-
-Copyright (c) 2025 VisionaryCoder
-
----
Last synchronized with solution structure: 2025-11-14
diff --git a/VisionaryCoder.Framework.README.md b/VisionaryCoder.Framework.README.md
deleted file mode 100644
index 965e1cf..0000000
--- a/VisionaryCoder.Framework.README.md
+++ /dev/null
@@ -1,270 +0,0 @@
-# VisionaryCoder.Framework - Production-Ready .NET Framework
-
-## Overview
-
-VisionaryCoder.Framework is a comprehensive, production-ready .NET framework that follows Microsoft best practices and enterprise architecture patterns. Built with .NET 8 and C# 12, it provides strongly-typed abstractions, service patterns, and data access layers for building scalable applications.
-
-## Framework Architecture
-
-The framework follows a modular architecture organized by functional concerns:
-
-### Core Projects
-
-#### ποΈ VisionaryCoder.Framework.Abstractions
-
-Foundation layer providing core base classes and abstractions
-
-- **ServiceBase<T>** - Base class for services with integrated logging and dependency injection patterns
-- **EntityBase** - Base entity class with audit fields, soft delete support, and optimistic concurrency
-- **StronglyTypedId<TValue, TId>** - Generic strongly-typed identifier pattern to prevent primitive obsession
-
-```csharp
-// Example: Strongly-typed ID
-public sealed record UserId : StronglyTypedId
-{
- public UserId(Guid value) : base(value) { }
-}
-
-// Example: Entity with audit fields
-public class User : EntityBase
-{
- public UserId Id { get; set; }
- public string Name { get; set; } = string.Empty;
- public string Email { get; set; } = string.Empty;
- // Inherits: CreatedAt, ModifiedAt, CreatedBy, ModifiedBy, IsDeleted, RowVersion
-}
-```
-
-#### π VisionaryCoder.Framework.Abstractions.Services
-
-##### Service contract definitions following Microsoft dependency injection patterns
-
-- **IFileSystem** - Unified interface for all file and directory operations with async support
-- Clean, testable interface that consolidates file system operations in one place
-- Follows Microsoft System.IO.Abstractions patterns for better testability
-
-```csharp
-// Example: File system service usage
-public class DocumentProcessor : ServiceBase
-{
- private readonly IFileSystem _fileSystem;
-
- public DocumentProcessor(IFileSystem fileSystem, ILogger logger)
- : base(logger)
- {
- _fileSystem = fileSystem;
- }
-
- public async Task ProcessAsync(string filePath, CancellationToken cancellationToken = default)
- {
- var content = await _fileSystem.ReadAllTextAsync(filePath, cancellationToken);
- // Process content...
- }
-}
-```
-
-#### πΎ VisionaryCoder.Framework.Data.Abstractions
-
-##### Repository and Unit of Work patterns for data access
-
-- **IRepository<TEntity, TKey>** - Generic repository with expression-based querying
-- **IUnitOfWork** - Transaction management and coordinated persistence
-- **IQueryBuilder<T>** - Fluent query construction with LINQ expressions
-
-```csharp
-// Example: Repository pattern
-public class UserService : ServiceBase
-{
- private readonly IRepository _userRepository;
- private readonly IUnitOfWork _unitOfWork;
-
- public async Task GetUserAsync(UserId id)
- {
- return await _userRepository.GetByIdAsync(id);
- }
-
- public async Task CreateUserAsync(User user)
- {
- await _userRepository.AddAsync(user);
- await _unitOfWork.CommitAsync();
- }
-}
-```
-
-#### π VisionaryCoder.Framework.Services.FileSystem
-
-##### Production-ready file system service implementations
-
-- **FileSystemService** - Unified implementation of IFileSystem with comprehensive logging and error handling
-- Async-first operations with proper cancellation token support
-- Structured logging with correlation IDs for tracking operations
-- Microsoft I/O patterns and System.IO.Abstractions compatibility
-
-## Key Features
-
-### β¨ **Microsoft Best Practices**
-
-- PascalCase naming conventions throughout
-- **NO underscore prefixes** - follows Microsoft guidelines strictly
-- Async/await patterns for all I/O operations
-- Proper dependency injection with IServiceCollection integration
-- Comprehensive XML documentation
-
-### π‘οΈ **Type Safety**
-
-- Strongly-typed identifiers prevent primitive obsession
-- Generic repository patterns with type constraints
-- Nullable reference types enabled throughout
-- Expression-based querying for compile-time safety
-
-### π **Enterprise Patterns**
-
-- Repository and Unit of Work for data access
-- Service layer abstractions for business logic
-- Base classes for common functionality
-- Audit fields and soft delete support built-in
-
-### π **Performance & Scalability**
-
-- Async/await throughout for non-blocking operations
-- Cancellation token support for responsive applications
-- Optimistic concurrency with row versioning
-- Minimal allocations with record types and spans
-
-### π **Observability**
-
-- Structured logging with Microsoft.Extensions.Logging
-- ServiceBase<T> provides built-in logging capabilities
-- Correlation ID support for request tracking
-- Performance monitoring hooks
-
-## Getting Started
-
-### Installation
-
-Add the framework projects to your solution:
-
-```bash
-dotnet sln add src/VisionaryCoder.Framework.Abstractions/VisionaryCoder.Framework.Abstractions.csproj
-dotnet sln add src/VisionaryCoder.Framework.Services.Abstractions/VisionaryCoder.Framework.Services.Abstractions.csproj
-dotnet sln add src/VisionaryCoder.Framework.Data.Abstractions/VisionaryCoder.Framework.Data.Abstractions.csproj
-dotnet sln add src/VisionaryCoder.Framework.Services.FileSystem/VisionaryCoder.Framework.Services.FileSystem.csproj
-```
-
-### Basic Usage
-
-```csharp
-using Microsoft.Extensions.DependencyInjection;
-using VisionaryCoder.Framework.Abstractions;
-using VisionaryCoder.Framework.Abstractions.Services;
-using VisionaryCoder.Framework.Services.FileSystem;
-
-// Configure dependency injection
-services.AddFileSystemServices();
-services.AddScoped();
-
-// Define strongly-typed entities
-public sealed record DocumentId : StronglyTypedId
-{
- public DocumentId(Guid value) : base(value) { }
-}
-
-public class Document : EntityBase
-{
- public DocumentId Id { get; set; }
- public string Title { get; set; } = string.Empty;
- public string Content { get; set; } = string.Empty;
-}
-
-// Implement services using framework patterns
-public class DocumentService : ServiceBase
-{
- private readonly IFileSystem _fileSystem;
-
- public DocumentService(IFileSystem fileSystem, ILogger logger)
- : base(logger)
- {
- _fileSystem = fileSystem;
- }
-
- public async Task LoadDocumentAsync(string filePath)
- {
- Logger.LogInformation("Loading document from {FilePath}", filePath);
-
- if (!_fileService.Exists(filePath))
- {
- Logger.LogWarning("Document not found at {FilePath}", filePath);
- return null;
- }
-
- var content = await _fileService.ReadAllTextAsync(filePath);
-
- return new Document
- {
- Id = new DocumentId(Guid.NewGuid()),
- Title = Path.GetFileNameWithoutExtension(filePath),
- Content = content
- };
- }
-}
-```
-
-## Framework Validation
-
-All framework projects build successfully and demonstrate proper Microsoft patterns:
-
-```bash
-β VisionaryCoder.Framework.Abstractions - Build succeeded
-β VisionaryCoder.Framework.Services.Abstractions - Build succeeded
-β VisionaryCoder.Framework.Data.Abstractions - Build succeeded
-β VisionaryCoder.Framework.Services.FileSystem - Build succeeded
-β VisionaryCoder.Framework.Example - Build succeeded and runs correctly
-```
-
-## Project Structure
-
-```text
-src/
-βββ VisionaryCoder.Framework.Abstractions/ # Core abstractions and base classes
-β βββ ServiceBase.cs # Base service with logging
-β βββ EntityBase.cs # Base entity with audit fields
-β βββ StronglyTypedId.cs # Strongly-typed identifier pattern
-βββ VisionaryCoder.Framework.Abstractions.Services/ # Service contracts
-β βββ IFileSystem.cs # Unified file system operations
-βββ VisionaryCoder.Framework.Data.Abstractions/ # Data access patterns
-β βββ IRepository.cs # Generic repository pattern
-β βββ IUnitOfWork.cs # Transaction coordination
-β βββ IQueryBuilder.cs # Fluent query construction
-βββ VisionaryCoder.Framework.Services.FileSystem/ # File system implementations
-β βββ FileService.cs # Production-ready file service
-βββ VisionaryCoder.Framework.Example/ # Working demonstration
- βββ Program.cs # Framework usage example
-```
-
-## Standards Compliance
-
-- β
**C# 12** - Uses latest language features (records, pattern matching, required members)
-- β
**.NET 8** - Targets modern .NET for best performance and features
-- β
**Microsoft Naming** - Strict adherence to Microsoft naming conventions
-- β
**Async/Await** - Async patterns throughout for scalable applications
-- β
**Nullable References** - Full nullable reference type support
-- β
**XML Documentation** - Comprehensive API documentation
-- β
**Enterprise Patterns** - Repository, Unit of Work, Service Layer patterns
-- β
**Production Ready** - Error handling, logging, cancellation support
-
-## Next Steps
-
-1. **Add Entity Framework Integration** - Create EF Core implementations of data abstractions
-2. **Add Caching Layer** - Implement distributed caching abstractions and Redis integration
-3. **Add Validation Framework** - FluentValidation integration with framework patterns
-4. **Add Testing Utilities** - Test helpers and mocking utilities for framework consumers
-5. **Add Configuration Management** - Strongly-typed configuration patterns
-6. **Add Health Checks** - ASP.NET Core health check integration
-7. **Add OpenTelemetry Integration** - Distributed tracing and metrics
-
----
-
-**Version:** 1.0.0
-**Target Framework:** .NET 8
-**Language:** C# 12
-**Status:** β
Production Ready
diff --git a/VisionaryCoder.Framework.sln b/VisionaryCoder.Framework.sln
index 177d894..df1acdd 100644
--- a/VisionaryCoder.Framework.sln
+++ b/VisionaryCoder.Framework.sln
@@ -1,7 +1,7 @@
ο»Ώ
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 18
-VisualStudioVersion = 18.0.11205.157
+# Visual Studio Version 17
+VisualStudioVersion = 17
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF68B68C-8A91-4020-AA05-C6862858DAB7}"
ProjectSection(SolutionItems) = preProject
@@ -11,11 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
- global.json = global.json
LICENSE = LICENSE
README.md = README.md
- version.json = version.json
- VisionaryCoder.Framework.README.md = VisionaryCoder.Framework.README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{94FEF38A-DA45-4CF1-A0DD-EA337586A1AF}"
@@ -120,7 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "best-practices", "best-prac
docs\best-practices\radar.md = docs\best-practices\radar.md
EndProjectSection
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "architecture-decision-records", "architecture-decision-records", "{D179AF1B-D641-4436-BF3F-5E394D3955D0}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "adr", "adr", "{D179AF1B-D641-4436-BF3F-5E394D3955D0}"
ProjectSection(SolutionItems) = preProject
docs\adr\adr-0001.md = docs\adr\adr-0001.md
docs\adr\adr-0002.md = docs\adr\adr-0002.md
diff --git a/docs/adr/adr-0005.md b/docs/adr/adr-0005.md
new file mode 100644
index 0000000..f6046ef
--- /dev/null
+++ b/docs/adr/adr-0005.md
@@ -0,0 +1,77 @@
+# ADR-0005: Add `IN` Membership Operator to Filtering Model and EF Core Translation
+
+## Status
+
+Accepted
+
+## Date
+
+2025-11-18
+
+## Context
+
+The filtering subsystem uses a portable, provider-agnostic AST (`FilterNode` and related records/enums) to represent predicate logic that can be serialized and applied across different execution strategies (POCO in-memory and EF Core query translation).
+
+Common query patterns include membership tests (SQL `IN`) expressed in code as constant-collection `.Contains(member)` or array-literal `.Contains(member)`. Previously these were translated into OR-chains or not recognized uniformly across strategies which reduced portability and caused inefficient SQL generation.
+
+Goals:
+- Provide an explicit membership (`IN`) operator in the filter model so serialized filters are unambiguous.
+- Ensure EF Core translation emits expressions that providers translate into efficient SQL `IN (...)` clauses.
+- Keep POCO execution semantics equivalent (evaluate membership in-memory).
+
+## Decision
+
+1. Extend the filter model with a membership operator:
+ - Add `FilterOperation.In` to `VisionaryCoder.Framework.Filtering.Abstractions.FilterOperation`.
+
+2. Expression translation:
+ - Detect patterns of constant-collection `Contains` (both static and instance forms) and translate them to `FilterCondition` with `Operator = FilterOperation.In` and `Value` set to a compact JSON array of element string representations.
+
+3. EF Core execution optimization:
+ - When translating `FilterCondition` with `Operator.In` for EF Core, deserialize the JSON array, convert items to the CLR element type, construct a typed array constant (e.g., `new int[] { 1, 2 }`) and generate `Enumerable.Contains(array, member)` in the expression tree. This expression is recognized and translated by EF Core providers into SQL `IN` clauses.
+
+4. POCO execution:
+ - For in-memory/POCO execution, deserialize the JSON array and evaluate membership via an OR-chain of equality comparisons (semantically equivalent) or using `Enumerable.Contains` on the deserialized collection when appropriate.
+
+5. Samples and documentation:
+ - Update samples to show variable-backed and literal collection `Contains` usage and confirm behavior for both POCO and EF Core.
+ - Add ADR and README updates documenting the change and migration guidance.
+
+## Consequences
+
+- Positive:
+ - Filters that express membership become first-class, serializable, and portable across execution strategies.
+ - EF Core queries are optimized to produce `IN` clauses where supported, improving SQL readability and performance for moderate-sized lists.
+ - Serialized filters are explicit and compact (JSON array payload) and can be stored or transmitted.
+
+- Negative / Tradeoffs:
+ - Using JSON payload inside `FilterCondition.Value` is a pragmatic serialization approach; it couples the value string format to a JSON array representation.
+ - Very large IN lists may not be optimal as array constants; future work should consider parameterization, TVPs, or provider-specific optimizations for large membership sets.
+
+## Alternatives Considered
+
+- Represent membership with a dedicated `FilterCollectionCondition` subtype for membership instead of serializing to JSON in `FilterCondition.Value`.
+ - Rejected for now because it would require larger API surface changes and more migration surface for existing serialization.
+
+- Always translate to OR-chains for EF Core and POCO.
+ - Rejected because EF Core can translate `Enumerable.Contains(array, member)` into `IN`, which produces better SQL and parameters handling in many providers.
+
+- Provider-specific extension methods or annotations to signal `IN`.
+ - Deferred: prefer a simple model-first approach that keeps the Abstractions small and portable.
+
+## Implementation Notes
+
+- The translator detects both `Enumerable.Contains(collection, value)` and `collection.Contains(value)` where `collection` is a constant-like expression (array, list literal, or captured variable). When found, the translator evaluates the collection at translation time and emits `FilterOperation.In` with JSON-serialized values.
+- EF Core builder constructs a typed constant array and emits `Enumerable.Contains(array, member)`. POCO builder deserializes and evaluates in-memory.
+- Unit tests should be added to cover translation and execution paths for both POCO and EF Core strategies. Consider tests verifying SQL generation when running against an EF Core provider.
+
+## References
+
+- ADR-0001: Architecture playbook and documentation conventions
+- docs/filtering/collection-operations.md
+- EF Core docs on how `Enumerable.Contains` is translated to `IN` by query providers
+
+```text
+Status: Accepted
+Date: 2025-11-18
+```
diff --git a/docs/filtering/collection-operations.md b/docs/filtering/collection-operations.md
index 2c8ced8..a65b67d 100644
--- a/docs/filtering/collection-operations.md
+++ b/docs/filtering/collection-operations.md
@@ -17,7 +17,7 @@ Expression> expr = c => c.Orders.Any();
// Translates to
FilterCollectionCondition(
Path: "Orders",
- Operator: FilterOperator.HasElements,
+ Operator: FilterOperation.HasElements,
Predicate: null
)
```
@@ -33,10 +33,10 @@ Expression> expr = c => c.Orders.Any(o => o.Total > 1000);
// Translates to
FilterCollectionCondition(
Path: "Orders",
- Operator: FilterOperator.Any,
+ Operator: FilterOperation.Any,
Predicate: FilterCondition(
Path: "Total",
- Operator: FilterOperator.GreaterThan,
+ Operator: FilterOperation.GreaterThan,
Value: "1000"
)
)
@@ -53,10 +53,10 @@ Expression> expr = c => c.Orders.All(o => o.IsPaid);
// Translates to
FilterCollectionCondition(
Path: "Orders",
- Operator: FilterOperator.All,
+ Operator: FilterOperation.All,
Predicate: FilterCondition(
Path: "IsPaid",
- Operator: FilterOperator.Equals,
+ Operator: FilterOperation.Equals,
Value: "True"
)
)
@@ -73,7 +73,7 @@ Expression> expr = p => p.Tags.Contains("electronics");
// Translates to
FilterCondition(
Path: "Tags",
- Operator: FilterOperator.Contains,
+ Operator: FilterOperation.Contains,
Value: "electronics"
)
```
@@ -90,7 +90,7 @@ Expression> expr = c =>
// Translates to
FilterCollectionCondition(
Path: "Orders",
- Operator: FilterOperator.Any,
+ Operator: FilterOperation.Any,
Predicate: FilterGroup(
Combination: FilterCombination.And,
Children: [
@@ -131,18 +131,18 @@ A new `FilterNode` type that represents operations on collection properties.
```csharp
public sealed record FilterCollectionCondition(
string Path, // Collection property path
- FilterOperator Operator, // Any, All, or HasElements
+ FilterOperation Operator, // Any, All, or HasElements
FilterNode? Predicate // Nested filter for collection elements
) : FilterNode;
```
-### New FilterOperator Values
+### New FilterOperation Values
Three new operators have been added to support collection operations:
-- `FilterOperator.Any` - At least one element matches the predicate
-- `FilterOperator.All` - All elements match the predicate
-- `FilterOperator.HasElements` - Collection is not empty
+- `FilterOperation.Any` - At least one element matches the predicate
+- `FilterOperation.All` - All elements match the predicate
+- `FilterOperation.HasElements` - Collection is not empty
## Extensibility for Custom Methods
@@ -160,7 +160,7 @@ static FilterNode? TranslateMethodCall(MethodCallExpression call)
var targetMember = GetMember(call.Object);
var path = GetMemberPath(targetMember);
// Create appropriate FilterNode based on method semantics
- return new FilterCondition(path, FilterOperator.Equals, "special");
+ return new FilterCondition(path, FilterOperation.Equals, "special");
}
return null;
diff --git a/global.json b/global.json
deleted file mode 100644
index 762ec68..0000000
--- a/global.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "sdk": {
- "version": "9.0.301"
- },
- "projects": [
- "src", "tests", "tools"
- ]
-}
\ No newline at end of file
diff --git a/src/VisionaryCoder.Framework/Authentication/SOLID_USAGE_EXAMPLES.md b/src/VisionaryCoder.Framework/Authentication/SOLID_USAGE_EXAMPLES.md
deleted file mode 100644
index f23bcd8..0000000
--- a/src/VisionaryCoder.Framework/Authentication/SOLID_USAGE_EXAMPLES.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# SOLID Principles Authentication Usage Examples
-
-## Overview
-
-The VisionaryCoder.Framework.Authentication namespace now follows SOLID principles, specifically the **Dependency Inversion Principle** and **Null Object Pattern**. This ensures explicit intent in provider registration and safe fallback behavior.
-
-## Core SOLID Principle Applied
-
-### Dependency Inversion Principle (DIP)
-
-- **High-level modules should not depend on low-level modules; both should depend on abstractions**
-- **Abstractions should not depend on details; details should depend on abstractions**
-
-### Implementation Strategy
-
-1. **Null Object Pattern**: Safe fallbacks without implicit defaults
-2. **Explicit Registration**: No automatic provider assumptions
-3. **Interface-Based Design**: All dependencies through contracts
-
-## Basic JWT Authentication Setup
-
-```csharp
-public void ConfigureServices(IServiceCollection services)
-{
- // Step 1: Register JWT authentication with null object fallbacks
- services.AddJwtAuthentication(options =>
- {
- options.Authority = "https://your-identity-provider";
- options.Audience = "your-api-audience";
- options.ClientId = "your-client-id";
- });
-
- // At this point, all providers are NULL OBJECTS that provide safe fallback behavior
- // No implicit defaults are registered - this follows SOLID DIP principles
-}
-```
-
-## Explicit Provider Registration (Recommended)
-
-```csharp
-public void ConfigureServices(IServiceCollection services)
-{
- // Step 1: Register JWT authentication infrastructure
- services.AddJwtAuthentication(options =>
- {
- options.Authority = "https://your-identity-provider";
- options.Audience = "your-api-audience";
- options.ClientId = "your-client-id";
- });
-
- // Step 2: EXPLICITLY register your providers (SOLID principle)
- services.ReplaceUserContextProvider();
- services.ReplaceTenantContextProvider();
- services.ReplaceTokenProvider();
-
- // OR use convenience method for defaults:
- // services.UseDefaultAuthenticationProviders();
-}
-```
-
-## Custom Provider Implementation
-
-```csharp
-// Step 1: Implement your custom provider
-public class CustomUserContextProvider : IUserContextProvider
-{
- public Task GetCurrentUserAsync()
- {
- // Your custom implementation
- return Task.FromResult(new UserContext
- {
- UserId = "custom-user-id",
- UserName = "custom-user"
- });
- }
-}
-
-// Step 2: Register your custom provider
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJwtAuthentication(options => { /* ... */ });
-
- // Replace null object with your custom implementation
- services.ReplaceUserContextProvider();
-}
-```
-
-## Null Object Pattern Benefits
-
-### Safe Fallback Behavior
-
-```csharp
-// If no explicit provider is registered, null objects provide safe behavior:
-
-// NullUserContextProvider returns null (no exceptions)
-var userContext = await userContextProvider.GetCurrentUserAsync(); // returns null
-
-// NullTenantContextProvider returns null (no exceptions)
-var tenantContext = await tenantContextProvider.GetCurrentTenantAsync(); // returns null
-
-// NullTokenProvider returns failed results or throws meaningful exceptions
-var tokenResult = await tokenProvider.GetTokenAsync(request); // returns failed TokenResult
-```
-
-### SOLID Principle Compliance
-
-1. **Single Responsibility**: Each provider has one clear purpose
-2. **Open/Closed**: Easily extend with new providers without modifying existing code
-3. **Liskov Substitution**: All implementations are substitutable through interfaces
-4. **Interface Segregation**: Focused interfaces with specific responsibilities
-5. **Dependency Inversion**: Depend on abstractions, not concrete implementations
-
-## Anti-Patterns to Avoid
-
-### β Don't rely on implicit defaults
-
-```csharp
-// BAD: Assuming providers are automatically registered
-services.AddJwtAuthentication(options => { /* ... */ });
-// User expects DefaultUserContextProvider but gets NullUserContextProvider
-```
-
-### β Don't register concrete dependencies directly
-
-```csharp
-// BAD: Bypassing the framework's registration methods
-services.AddScoped();
-// This doesn't replace the null object and violates explicit intent
-```
-
-## β
Best Practices
-
-### Explicit Intent Required
-
-```csharp
-// GOOD: Clear, explicit provider registration
-services.AddJwtAuthentication(options => { /* ... */ });
-services.UseDefaultAuthenticationProviders(); // Explicit intent to use defaults
-```
-
-### Custom Implementation with Validation
-
-```csharp
-public class ValidatedUserContextProvider : IUserContextProvider
-{
- private readonly ILogger logger;
-
- public ValidatedUserContextProvider(ILogger logger)
- {
- this.logger = logger;
- }
-
- public async Task GetCurrentUserAsync()
- {
- try
- {
- // Your validation logic
- var context = await GetUserFromToken();
- logger.LogInformation("User context retrieved: {UserId}", context?.UserId);
- return context;
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Failed to retrieve user context");
- return null; // Safe fallback
- }
- }
-}
-```
-
-## Testing with SOLID Principles
-
-```csharp
-[TestMethod]
-public async Task ShouldUseNullObjectWhenNoProviderRegistered()
-{
- // Arrange
- var services = new ServiceCollection();
- services.AddJwtAuthentication(options => { /* valid options */ });
- var provider = services.BuildServiceProvider();
-
- // Act
- var userContextProvider = provider.GetRequiredService();
- var userContext = await userContextProvider.GetCurrentUserAsync();
-
- // Assert
- Assert.IsNull(userContext); // Null object returns null safely
- Assert.IsInstanceOfType(userContextProvider, typeof(NullUserContextProvider));
-}
-
-[TestMethod]
-public async Task ShouldUseExplicitProviderWhenRegistered()
-{
- // Arrange
- var services = new ServiceCollection();
- services.AddJwtAuthentication(options => { /* valid options */ });
- services.ReplaceUserContextProvider();
- var provider = services.BuildServiceProvider();
-
- // Act
- var userContextProvider = provider.GetRequiredService();
-
- // Assert
- Assert.IsInstanceOfType(userContextProvider, typeof(DefaultUserContextProvider));
-}
-```
-
-This approach ensures that:
-
-1. **No implicit behavior** - everything must be explicitly registered
-2. **Safe fallbacks** - null objects prevent runtime errors
-3. **Clear intent** - developers must explicitly choose their providers
-4. **Testable design** - easy to mock and verify behavior
-5. **SOLID compliance** - follows dependency inversion and interface segregation principles
diff --git a/src/VisionaryCoder.Framework/Configuration/AppConfigurationHelper.cs b/src/VisionaryCoder.Framework/Configuration/AppConfigurationHelper.cs
deleted file mode 100644
index 13aff31..0000000
--- a/src/VisionaryCoder.Framework/Configuration/AppConfigurationHelper.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Text.Json;
-
-namespace VisionaryCoder.Framework.AppConfiguration;
-
-internal static class AppConfigurationHelper
-{
-
- public static T ConvertValue(string stringValue, T defaultValue)
- {
- try
- {
- if (typeof(T) == typeof(string))
- return (T)(object)stringValue;
-
- if (typeof(T) == typeof(int))
- return (T)(object)int.Parse(stringValue);
-
- if (typeof(T) == typeof(long))
- return (T)(object)long.Parse(stringValue);
-
- if (typeof(T) == typeof(double))
- return (T)(object)double.Parse(stringValue);
-
- if (typeof(T) == typeof(decimal))
- return (T)(object)decimal.Parse(stringValue);
-
- if (typeof(T) == typeof(bool))
- return (T)(object)bool.Parse(stringValue);
-
- if (typeof(T) == typeof(DateTime))
- return (T)(object)DateTime.Parse(stringValue);
-
- if (typeof(T) == typeof(DateTimeOffset))
- return (T)(object)DateTimeOffset.Parse(stringValue);
-
- if (typeof(T) == typeof(TimeSpan))
- return (T)(object)TimeSpan.Parse(stringValue);
-
- if (typeof(T) == typeof(Guid))
- return (T)(object)Guid.Parse(stringValue);
-
- // For complex types, try JSON deserialization
- return JsonSerializer.Deserialize(stringValue) ?? defaultValue;
- }
- catch
- {
- return defaultValue;
- }
- }
-}
diff --git a/src/VisionaryCoder.Framework/Configuration/Azure/AzureAppConfigurationProviderOptions.cs b/src/VisionaryCoder.Framework/Configuration/Azure/AzureAppConfigurationProviderOptions.cs
deleted file mode 100644
index a5b3ed0..0000000
--- a/src/VisionaryCoder.Framework/Configuration/Azure/AzureAppConfigurationProviderOptions.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-namespace VisionaryCoder.Framework.AppConfiguration.Azure;
-
-///
-/// Configuration options for Azure App Configuration provider.
-///
-public sealed class AzureAppConfigurationProviderOptions
-{
- ///
- /// The endpoint URI for the Azure App Configuration service.
- ///
- /// https://your-config.azconfig.io
- public Uri? Endpoint { get; init; }
-
- ///
- /// The label to use for environment-specific configuration (e.g., "Development", "Testing", "Staging", "Production").
- ///
- public string Label { get; init; } = "Production";
-
- ///
- /// The sentinel key used to trigger configuration refresh.
- ///
- public string SentinelKey { get; init; } = "App:Sentinel";
-
- ///
- /// The cache expiration time for configuration values.
- ///
- public TimeSpan CacheExpiration { get; init; } = TimeSpan.FromSeconds(30);
-
- ///
- /// Whether to use connection string authentication instead of managed identity.
- ///
- public bool UseConnectionString { get; init; } = false;
-
- ///
- /// The connection string for Azure App Configuration (when UseConnectionString is true).
- ///
- public string? ConnectionString { get; init; }
-
- ///
- /// Whether to enable automatic refresh of configuration values.
- ///
- public bool EnableRefresh { get; init; } = true;
-
- ///
- /// The prefix to filter configuration keys (optional).
- ///
- public string? KeyPrefix { get; init; }
-
- ///
- /// Validates the configuration options.
- ///
- internal void Validate()
- {
- if (UseConnectionString)
- {
- if (string.IsNullOrWhiteSpace(ConnectionString))
- throw new InvalidOperationException("ConnectionString must be provided when UseConnectionString is true.");
- }
- else
- {
- if (Endpoint is null)
- throw new InvalidOperationException("Endpoint must be provided when not using connection string authentication.");
- }
-
- if (string.IsNullOrWhiteSpace(Label))
- throw new InvalidOperationException("Label cannot be null or empty.");
-
- if (string.IsNullOrWhiteSpace(SentinelKey))
- throw new InvalidOperationException("SentinelKey cannot be null or empty.");
-
- if (CacheExpiration <= TimeSpan.Zero)
- throw new InvalidOperationException("CacheExpiration must be greater than zero.");
- }
-}
\ No newline at end of file
diff --git a/src/VisionaryCoder.Framework/Configuration/IAppConfigurationProvider.cs b/src/VisionaryCoder.Framework/Configuration/IAppConfigurationProvider.cs
deleted file mode 100644
index 4844568..0000000
--- a/src/VisionaryCoder.Framework/Configuration/IAppConfigurationProvider.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace VisionaryCoder.Framework.AppConfiguration;
-
-///
-/// Defines the contract for application configuration providers.
-/// Supports async operations, caching, refresh capabilities, and type-safe configuration access.
-///
-public interface IAppConfigurationProvider
-{
- ///
- /// Retrieves a strongly-typed configuration value by key.
- ///
- /// The type to deserialize the configuration value to
- /// The configuration key
- /// The default value to return if the key is not found
- /// Cancellation token
- /// The configuration value or default value if not found
- Task GetValueAsync(string key, T defaultValue = default!, CancellationToken cancellationToken = default);
-
- ///
- /// Retrieves a strongly-typed configuration section by name.
- ///
- /// The type to deserialize the configuration section to
- /// The name of the configuration section
- /// Cancellation token
- /// The deserialized configuration section
- Task GetSectionAsync(string sectionName, CancellationToken cancellationToken = default) where T : class, new();
-
- ///
- /// Retrieves all configuration values as a dictionary.
- ///
- /// Cancellation token
- /// Dictionary containing all configuration key-value pairs
- Task> GetAllValuesAsync(CancellationToken cancellationToken = default);
-
- ///
- /// Forces a refresh of the configuration data from the underlying source.
- ///
- /// Cancellation token
- /// True if refresh was successful, false otherwise
- Task RefreshAsync(CancellationToken cancellationToken = default);
-}
\ No newline at end of file
diff --git a/src/VisionaryCoder.Framework/Configuration/Local/LocalAppConfigurationProviderOptions.cs b/src/VisionaryCoder.Framework/Configuration/Local/LocalAppConfigurationProviderOptions.cs
deleted file mode 100644
index f13f89c..0000000
--- a/src/VisionaryCoder.Framework/Configuration/Local/LocalAppConfigurationProviderOptions.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-namespace VisionaryCoder.Framework.AppConfiguration.Local;
-
-///
-/// Configuration options for Local (file-based) App Configuration provider.
-///
-public sealed class LocalAppConfigurationProviderOptions
-{
- ///
- /// The file path for the configuration file.
- ///
- public string FilePath { get; init; } = "appsettings.json";
-
- ///
- /// Whether to watch the file for changes and automatically reload.
- ///
- public bool ReloadOnChange { get; init; } = true;
-
- ///
- /// Whether the configuration file is optional.
- ///
- public bool Optional { get; init; } = false;
-
- ///
- /// Additional configuration files to include (e.g., environment-specific files).
- ///
- public IEnumerable AdditionalFiles { get; init; } = Array.Empty();
-
- ///
- /// The base path for configuration files.
- ///
- public string? BasePath { get; init; }
-
- ///
- /// The prefix to filter configuration keys (optional).
- ///
- public string? KeyPrefix { get; init; }
-
- ///
- /// Whether to enable caching of configuration values.
- ///
- public bool EnableCaching { get; init; } = true;
-
- ///
- /// The cache expiration time for configuration values.
- ///
- public TimeSpan CacheExpiration { get; init; } = TimeSpan.FromMinutes(5);
-
- ///
- /// Validates the configuration options.
- ///
- internal void Validate()
- {
- if (string.IsNullOrWhiteSpace(FilePath))
- throw new InvalidOperationException("FilePath cannot be null or empty.");
-
- if (CacheExpiration <= TimeSpan.Zero)
- throw new InvalidOperationException("CacheExpiration must be greater than zero.");
-
- // Validate additional files
- foreach (string file in AdditionalFiles)
- {
- if (string.IsNullOrWhiteSpace(file))
- throw new InvalidOperationException("Additional file paths cannot be null or empty.");
- }
- }
-}
\ No newline at end of file
diff --git a/src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageOptions.cs b/src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageOptions.cs
similarity index 95%
rename from src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageOptions.cs
rename to src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageOptions.cs
index b8149ad..604845d 100644
--- a/src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageOptions.cs
+++ b/src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageOptions.cs
@@ -1,4 +1,4 @@
-namespace VisionaryCoder.Framework.Storage.Azure;
+namespace VisionaryCoder.Framework.Data.Azure.Table;
///
/// Configuration options for Azure Table Storage operations.
@@ -66,11 +66,11 @@ public sealed class AzureTableStorageOptions
///
public void Validate()
{
- ArgumentException.ThrowIfNullOrWhiteSpace(TableName, nameof(TableName));
+ ArgumentException.ThrowIfNullOrWhiteSpace(TableName);
if (UseManagedIdentity)
{
- ArgumentException.ThrowIfNullOrWhiteSpace(StorageAccountUri, nameof(StorageAccountUri));
+ ArgumentException.ThrowIfNullOrWhiteSpace(StorageAccountUri);
if (!Uri.TryCreate(StorageAccountUri, UriKind.Absolute, out _))
{
throw new ArgumentException("StorageAccountUri must be a valid absolute URI.", nameof(StorageAccountUri));
@@ -78,7 +78,7 @@ public void Validate()
}
else
{
- ArgumentException.ThrowIfNullOrWhiteSpace(ConnectionString, nameof(ConnectionString));
+ ArgumentException.ThrowIfNullOrWhiteSpace(ConnectionString);
}
if (TimeoutMilliseconds <= 0)
diff --git a/src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageProvider.cs b/src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageProvider.cs
similarity index 93%
rename from src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageProvider.cs
rename to src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageProvider.cs
index a6b1482..cea9bda 100644
--- a/src/VisionaryCoder.Framework/Storage/Azure/Table/AzureTableStorageProvider.cs
+++ b/src/VisionaryCoder.Framework/Data/Azure/Table/AzureTableStorageProvider.cs
@@ -1,13 +1,18 @@
+using Azure;
+using Azure.Data.Tables;
+using Azure.Data.Tables.Models;
+using Azure.Identity;
+using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
-namespace VisionaryCoder.Framework.Storage.Azure;
+namespace VisionaryCoder.Framework.Data.Azure.Table;
///
/// Provides Azure Table Storage-based NoSQL table operations implementation.
/// This service wraps Azure Table Storage operations with logging, error handling, and async support.
/// Supports both connection string and managed identity authentication.
///
-public sealed class AzureTableStorageProvider : ServiceBase
+public sealed class AzureTableStorageProvider : ServiceBase, ITableStorageProvider
{
private readonly AzureTableStorageOptions options;
private readonly TableServiceClient tableServiceClient;
@@ -55,8 +60,8 @@ public bool TableExists()
{
Logger.LogTrace("Table existence check for '{TableName}'", options.TableName);
- NullableResponse response = tableServiceClient.Query(filter: $"TableName eq '{options.TableName}'").FirstOrDefault();
- bool exists = response != null;
+ TableItem? item = tableServiceClient.Query(filter: $"TableName eq '{options.TableName}'").FirstOrDefault();
+ bool exists = item is not null;
Logger.LogTrace("Table existence check for '{TableName}': {Exists}", options.TableName, exists);
return exists;
@@ -163,8 +168,7 @@ public void UpdateEntity(T entity, ETag etag = default, TableUpdateMode mode
///
/// Updates an existing entity in the table asynchronously.
///
- public async Task UpdateEntityAsync(T entity, ETag etag = default, TableUpdateMode mode = TableUpdateMode.Replace,
- CancellationToken cancellationToken = default) where T : class, ITableEntity
+ public async Task UpdateEntityAsync(T entity, ETag etag = default, TableUpdateMode mode = TableUpdateMode.Replace, CancellationToken cancellationToken = default) where T : class, ITableEntity
{
ArgumentNullException.ThrowIfNull(entity);
@@ -208,8 +212,7 @@ public void UpsertEntity(T entity, TableUpdateMode mode = TableUpdateMode.Rep
///
/// Upserts an entity (insert or replace) in the table asynchronously.
///
- public async Task UpsertEntityAsync(T entity, TableUpdateMode mode = TableUpdateMode.Replace,
- CancellationToken cancellationToken = default) where T : class, ITableEntity
+ public async Task UpsertEntityAsync(T entity, TableUpdateMode mode = TableUpdateMode.Replace, CancellationToken cancellationToken = default) where T : class, ITableEntity
{
ArgumentNullException.ThrowIfNull(entity);
@@ -254,8 +257,7 @@ public void DeleteEntity(string partitionKey, string rowKey, ETag etag = default
///
/// Deletes an entity from the table asynchronously.
///
- public async Task DeleteEntityAsync(string partitionKey, string rowKey, ETag etag = default,
- CancellationToken cancellationToken = default)
+ public async Task DeleteEntityAsync(string partitionKey, string rowKey, ETag etag = default, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(partitionKey);
ArgumentException.ThrowIfNullOrWhiteSpace(rowKey);
@@ -278,7 +280,8 @@ public async Task DeleteEntityAsync(string partitionKey, string rowKey, ETag eta
///
/// Gets an entity by partition key and row key.
///
- public T? GetEntity(string partitionKey, string rowKey) where T : class, ITableEntity
+ public T? GetEntity(string partitionKey, string rowKey)
+ where T : class, ITableEntity
{
ArgumentException.ThrowIfNullOrWhiteSpace(partitionKey);
ArgumentException.ThrowIfNullOrWhiteSpace(rowKey);
@@ -339,7 +342,8 @@ public async Task DeleteEntityAsync(string partitionKey, string rowKey, ETag eta
///
/// Queries entities from the table with an optional filter.
///
- public List QueryEntities(string? filter = null, int? maxPerPage = null) where T : class, ITableEntity
+ public List QueryEntities(string? filter = null, int? maxPerPage = null)
+ where T : class, ITableEntity
{
try
{
@@ -348,7 +352,7 @@ public List QueryEntities(string? filter = null, int? maxPerPage = null) w
int pageSize = maxPerPage ?? options.MaxEntitiesPerQuery;
Pageable? query = tableClient.Query(filter, pageSize);
- List results = query.ToList();
+ var results = query.ToList();
Logger.LogTrace("Successfully queried {Count} entities from table '{TableName}'", results.Count, options.TableName);
return results;
}
@@ -362,8 +366,8 @@ public List QueryEntities(string? filter = null, int? maxPerPage = null) w
///
/// Queries entities from the table with an optional filter asynchronously.
///
- public async Task> QueryEntitiesAsync(string? filter = null, int? maxPerPage = null,
- CancellationToken cancellationToken = default) where T : class, ITableEntity
+ public async Task> QueryEntitiesAsync(string? filter = null, int? maxPerPage = null, CancellationToken cancellationToken = default)
+ where T : class, ITableEntity
{
try
{
@@ -391,8 +395,8 @@ public async Task> QueryEntitiesAsync(string? filter = null, int? max
///
/// Enumerates entities from the table with an optional filter asynchronously.
///
- public async IAsyncEnumerable EnumerateEntitiesAsync(string? filter = null, int? maxPerPage = null,
- [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, ITableEntity
+ public async IAsyncEnumerable EnumerateEntitiesAsync(string? filter = null, int? maxPerPage = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ where T : class, ITableEntity
{
Logger.LogDebug("Enumerating entities async from table '{TableName}' with filter '{Filter}'", options.TableName, filter ?? "none");
@@ -414,7 +418,7 @@ public void SubmitBatch(IEnumerable actions)
try
{
- List? actionList = actions.ToList();
+ var actionList = actions.ToList();
Logger.LogDebug("Submitting batch transaction to table '{TableName}' with {Count} actions",
options.TableName, actionList.Count);
@@ -442,7 +446,7 @@ public async Task SubmitBatchAsync(IEnumerable actions,
try
{
- List? actionList = actions.ToList();
+ var actionList = actions.ToList();
Logger.LogDebug("Submitting batch transaction async to table '{TableName}' with {Count} actions",
options.TableName, actionList.Count);
@@ -464,7 +468,8 @@ public async Task SubmitBatchAsync(IEnumerable actions,
///
/// Gets entities by partition key.
///
- public List GetEntitiesByPartitionKey(string partitionKey) where T : class, ITableEntity
+ public List GetEntitiesByPartitionKey(string partitionKey)
+ where T : class, ITableEntity
{
ArgumentException.ThrowIfNullOrWhiteSpace(partitionKey);
@@ -483,4 +488,5 @@ public async Task> GetEntitiesByPartitionKeyAsync(string partitionKey
string filter = $"PartitionKey eq '{partitionKey}'";
return await QueryEntitiesAsync(filter, cancellationToken: cancellationToken);
}
+
}
diff --git a/src/VisionaryCoder.Framework/Data/Azure/Table/ITableStorageProvider.cs b/src/VisionaryCoder.Framework/Data/Azure/Table/ITableStorageProvider.cs
new file mode 100644
index 0000000..f97f177
--- /dev/null
+++ b/src/VisionaryCoder.Framework/Data/Azure/Table/ITableStorageProvider.cs
@@ -0,0 +1,40 @@
+using Azure;
+using Azure.Data.Tables;
+
+namespace VisionaryCoder.Framework.Data.Azure.Table;
+
+///
+/// Defines NoSQL table-oriented storage operations for CRUD, queries and batch operations.
+/// This interface separates table semantics from file/directory storage concerns.
+///
+public interface ITableStorageProvider
+{
+ bool TableExists();
+ Task TableExistsAsync(CancellationToken cancellationToken = default);
+
+ void AddEntity(T entity) where T : class, ITableEntity;
+ Task AddEntityAsync(T entity, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ void UpdateEntity(T entity, ETag etag = default, TableUpdateMode mode = TableUpdateMode.Replace) where T : class, ITableEntity;
+ Task UpdateEntityAsync(T entity, ETag etag = default, TableUpdateMode mode = TableUpdateMode.Replace, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ void UpsertEntity(T entity, TableUpdateMode mode = TableUpdateMode.Replace) where T : class, ITableEntity;
+ Task UpsertEntityAsync(T entity, TableUpdateMode mode = TableUpdateMode.Replace, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ void DeleteEntity(string partitionKey, string rowKey, ETag etag = default);
+ Task DeleteEntityAsync(string partitionKey, string rowKey, ETag etag = default, CancellationToken cancellationToken = default);
+
+ T? GetEntity(string partitionKey, string rowKey) where T : class, ITableEntity;
+ Task GetEntityAsync(string partitionKey, string rowKey, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ List QueryEntities(string? filter = null, int? maxPerPage = null) where T : class, ITableEntity;
+ Task> QueryEntitiesAsync(string? filter = null, int? maxPerPage = null, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ IAsyncEnumerable EnumerateEntitiesAsync(string? filter = null, int? maxPerPage = null, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+
+ void SubmitBatch(IEnumerable actions);
+ Task SubmitBatchAsync(IEnumerable actions, CancellationToken cancellationToken = default);
+
+ List GetEntitiesByPartitionKey(string partitionKey) where T : class, ITableEntity;
+ Task> GetEntitiesByPartitionKeyAsync(string partitionKey, CancellationToken cancellationToken = default) where T : class, ITableEntity;
+}
diff --git a/src/VisionaryCoder.Framework/Extensions/DataConfigurationServiceCollectionExtensions.cs b/src/VisionaryCoder.Framework/Extensions/DataConfigurationExtensions.cs
similarity index 93%
rename from src/VisionaryCoder.Framework/Extensions/DataConfigurationServiceCollectionExtensions.cs
rename to src/VisionaryCoder.Framework/Extensions/DataConfigurationExtensions.cs
index 7258c0e..faa260f 100644
--- a/src/VisionaryCoder.Framework/Extensions/DataConfigurationServiceCollectionExtensions.cs
+++ b/src/VisionaryCoder.Framework/Extensions/DataConfigurationExtensions.cs
@@ -1,10 +1,12 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using VisionaryCoder.Framework.Secrets;
namespace VisionaryCoder.Framework.Extensions;
///
/// Extension methods for configuring database connections and connection strings.
///
-public static class DataConfigurationServiceCollectionExtensions
+public static class DataConfigurationExtensions
{
///
@@ -44,8 +46,8 @@ public static IServiceCollection AddNamedConnectionString(
{
throw new InvalidOperationException($"Connection string '{connectionName}' is not configured.");
}
- services.AddKeyedSingleton(serviceName, connectionStringValue);
- return services;
+ services.AddKeyedSingleton(serviceName, connectionStringValue);
+ return services;
}
/// Adds a connection string from a secret provider to the service collection.
/// The service collection to add the connection string to.
diff --git a/src/VisionaryCoder.Framework/Extensions/DictionaryExtensions.cs b/src/VisionaryCoder.Framework/Extensions/DictionaryExtensions.cs
index 04bf4b5..fdd948d 100644
--- a/src/VisionaryCoder.Framework/Extensions/DictionaryExtensions.cs
+++ b/src/VisionaryCoder.Framework/Extensions/DictionaryExtensions.cs
@@ -4,246 +4,255 @@
using System.Reflection;
namespace VisionaryCoder.Framework.Extensions;
+
public static class DictionaryExtensions
{
- ///
- /// Gets a value from a dictionary or returns a default value if the key doesn't exist.
- ///
+
+ /// The dictionary to search.
/// The type of the keys in the dictionary.
/// The type of the values in the dictionary.
- /// The dictionary to search.
- /// The key to find.
- /// The default value to return if the key is not found.
- /// The value associated with the key or the default value.
- public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key, TValue defaultValue = default!)
+ extension(IDictionary dictionary)
+ {
+ ///
+ /// Gets a value from a dictionary or returns a default value if the key doesn't exist.
+ ///
+ /// The key to find.
+ /// The default value to return if the key is not found.
+ /// The value associated with the key or the default value.
+ public TValue GetValueOrDefault(TKey key, TValue defaultValue = default!)
{
return dictionary.TryGetValue(key, out TValue? value) ? value : defaultValue;
}
+
///
/// Gets a value from a dictionary or computes it if the key doesn't exist.
///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to search.
/// The key to find.
/// A function that computes the value if the key is not found.
/// The value associated with the key or the computed value.
- public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func valueFactory)
+ public TValue GetOrAdd(TKey key, Func valueFactory)
{
- ArgumentNullException.ThrowIfNull(dictionary, nameof(dictionary));
- ArgumentNullException.ThrowIfNull(valueFactory, nameof(valueFactory));
+ ArgumentNullException.ThrowIfNull(dictionary);
+ ArgumentNullException.ThrowIfNull(valueFactory);
if (dictionary.TryGetValue(key, out TValue? value))
{
return value;
}
+
value = valueFactory(key);
dictionary[key] = value;
return value;
}
+
///
/// Adds or updates a value in the dictionary.
///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to modify.
/// The key to add or update.
/// The value to add if the key doesn't exist.
/// A function to generate an updated value based on the key and existing value.
/// The new value in the dictionary.
- public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, TValue addValue, Func updateValueFactory)
+ public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory)
{
- ArgumentNullException.ThrowIfNull(updateValueFactory, nameof(updateValueFactory));
+ ArgumentNullException.ThrowIfNull(updateValueFactory);
if (dictionary.TryGetValue(key, out TValue? existingValue))
{
TValue newValue = updateValueFactory(key, existingValue);
dictionary[key] = newValue;
return newValue;
}
+
dictionary[key] = addValue;
return addValue;
}
+
///
/// Adds or updates a value in the dictionary using a value factory.
///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to modify.
/// The key to add or update.
/// A function to generate a value to add if the key doesn't exist.
/// A function to generate an updated value based on the key and existing value.
/// The new value in the dictionary.
- public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, Func addValueFactory, Func updateValueFactory)
+ public TValue AddOrUpdate(TKey key, Func addValueFactory,
+ Func updateValueFactory)
{
- ArgumentNullException.ThrowIfNull(addValueFactory, nameof(addValueFactory));
+ ArgumentNullException.ThrowIfNull(addValueFactory);
TValue addValue = addValueFactory(key);
return AddOrUpdate(dictionary, key, addValue, updateValueFactory);
}
- /// Converts a dictionary to an immutable dictionary.
- /// The dictionary to convert
- /// An immutable version of the dictionary
- public static IImmutableDictionary ToImmutableDictionary(this IDictionary dictionary) where TKey : notnull
- {
- return dictionary.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value);
- }
- /// Converts a dictionary to a read-only dictionary.
- /// A read-only version of the dictionary
- public static ReadOnlyDictionary ToReadOnlyDictionary(this IDictionary dictionary) where TKey : notnull
+}
+
+
+/// The dictionary to convert
+/// The type of the keys in the dictionaries
+/// The type of the values in the dictionaries
+extension(IDictionary dictionary) where TKey : notnull
{
- return new ReadOnlyDictionary(dictionary);
- }
- /// Merges two dictionaries into a new dictionary.
- /// The type of the keys in the dictionaries
- /// The type of the values in the dictionaries
- /// The first dictionary
- /// The second dictionary
- /// Optional function to resolve conflicts when keys exist in both dictionaries
- /// A new dictionary containing all keys and values from both input dictionaries
- public static Dictionary Merge(this IDictionary first, IDictionary second, Func? conflictResolver = null) where TKey : notnull
+ /// Converts a dictionary to an immutable dictionary.
+ /// An immutable version of the dictionary
+ public IImmutableDictionary ToImmutableDictionary()
+{
+ return dictionary.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value);
+}
+
+/// Converts a dictionary to a read-only dictionary.
+/// A read-only version of the dictionary
+public ReadOnlyDictionary ToReadOnlyDictionary()
+{
+ return new ReadOnlyDictionary(dictionary);
+}
+
+/// Merges two dictionaries into a new dictionary.
+/// The second dictionary
+/// Optional function to resolve conflicts when keys exist in both dictionaries
+/// A new dictionary containing all keys and values from both input dictionaries
+public Dictionary Merge(IDictionary second, Func? conflictResolver = null)
+{
+ ArgumentNullException.ThrowIfNull(dictionary);
+ ArgumentNullException.ThrowIfNull(second);
+ var result = new Dictionary(dictionary);
+ foreach (KeyValuePair kvp in second)
{
- ArgumentNullException.ThrowIfNull(first, nameof(first));
- ArgumentNullException.ThrowIfNull(second, nameof(second));
- var result = new Dictionary(first);
- foreach (KeyValuePair kvp in second)
+ if (result.TryGetValue(kvp.Key, out TValue? existingValue))
{
- if (result.TryGetValue(kvp.Key, out TValue? existingValue))
+ if (conflictResolver != null)
{
- if (conflictResolver != null)
- {
- result[kvp.Key] = conflictResolver(kvp.Key, existingValue, kvp.Value);
- }
- else
- {
- result[kvp.Key] = kvp.Value; // Second dictionary wins by default
- }
+ result[kvp.Key] = conflictResolver(kvp.Key, existingValue, kvp.Value);
}
else
{
- result.Add(kvp.Key, kvp.Value);
+ result[kvp.Key] = kvp.Value; // Second dictionary wins by default
}
}
- return result;
- }
- ///
- /// Applies a transformation function to each value in a dictionary.
- ///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The type of the result values.
- /// The dictionary to transform.
- /// A function to transform each value.
- /// A new dictionary with the same keys but transformed values.
- public static Dictionary TransformValues(this IDictionary dictionary, Func valueSelector) where TKey : notnull
- {
- ArgumentNullException.ThrowIfNull(valueSelector, nameof(valueSelector));
- var result = new Dictionary(dictionary.Count);
- foreach (KeyValuePair kvp in dictionary)
+ else
{
- result.Add(kvp.Key, valueSelector(kvp.Value));
+ result.Add(kvp.Key, kvp.Value);
}
- return result;
}
- /// Filters a dictionary based on a predicate.
- /// The dictionary to filter
- /// A function to test each key-value pair for a condition
- /// A new dictionary containing only the elements that satisfy the condition
- public static Dictionary Where(this IDictionary dictionary, Func predicate) where TKey : notnull
+ return result;
+}
+
+///
+/// Applies a transformation function to each value in a dictionary.
+///
+/// The type of the result values.
+/// A function to transform each value.
+/// A new dictionary with the same keys but transformed values.
+public Dictionary TransformValues(Func valueSelector)
+{
+ ArgumentNullException.ThrowIfNull(valueSelector);
+ var result = new Dictionary(dictionary.Count);
+ foreach (KeyValuePair kvp in dictionary)
{
- ArgumentNullException.ThrowIfNull(predicate, nameof(predicate));
- return dictionary
- .Where(kvp => predicate(kvp.Key, kvp.Value))
- .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ result.Add(kvp.Key, valueSelector(kvp.Value));
}
+ return result;
+}
+
+/// Filters a dictionary based on a predicate.
+/// A function to test each key-value pair for a condition
+/// A new dictionary containing only the elements that satisfy the condition
+public Dictionary Where(Func predicate)
+{
+ ArgumentNullException.ThrowIfNull(predicate);
+ return dictionary
+ .Where(kvp => predicate(kvp.Key, kvp.Value))
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+}
+ }
+
/// Creates a dictionary from an object's properties.
/// The type of the object
/// The object to convert to a dictionary
/// A dictionary with property names as keys and property values as values
public static Dictionary ToDictionary(this T obj) where T : class
+{
+
+ ArgumentNullException.ThrowIfNull(obj);
+ var dictionary = new Dictionary();
+ PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ foreach (PropertyInfo property in properties)
{
- ArgumentNullException.ThrowIfNull(obj, nameof(obj));
- var dictionary = new Dictionary();
- PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
- foreach (PropertyInfo property in properties)
- {
- dictionary[property.Name] = property.GetValue(obj);
- }
- return dictionary;
+ dictionary[property.Name] = property.GetValue(obj);
}
-
- /// Checks if a dictionary is null or empty.
- /// The dictionary to check
- /// True if the dictionary is null or empty; otherwise, false
- public static bool IsNullOrEmpty(this IDictionary? dictionary)
+ return dictionary;
+}
+
+/// Checks if a dictionary is null or empty.
+/// The dictionary to check
+/// True if the dictionary is null or empty; otherwise, false
+public static bool IsNullOrEmpty(this IDictionary? dictionary)
+{
+ return dictionary == null || dictionary.Count == 0;
+}
+
+/// The dictionary to modify.
+/// The type of the keys in the dictionary.
+/// The type of the values in the dictionary.
+extension(IDictionary < TKey, TValue > dictionary)
{
- return dictionary == null || dictionary.Count == 0;
- }
- ///
- /// Removes multiple keys from a dictionary.
- ///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to modify.
- /// The keys to remove.
- /// The number of elements removed.
- public static int RemoveRange(this IDictionary dictionary, IEnumerable keys)
+ ///
+ /// Removes multiple keys from a dictionary.
+ ///
+ /// The keys to remove.
+ /// The number of elements removed.
+ public int RemoveRange(IEnumerable keys)
+{
+ ArgumentNullException.ThrowIfNull(keys);
+ int count = 0;
+ foreach (TKey key in keys)
{
- ArgumentNullException.ThrowIfNull(keys, nameof(keys));
- int count = 0;
- foreach (TKey key in keys)
+ if (dictionary.Remove(key))
{
- if (dictionary.Remove(key))
- {
- count++;
- }
+ count++;
}
- return count;
}
- ///
- /// Tries to remove a key from the dictionary and returns its value.
- ///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to modify.
- /// The key to remove.
- /// The value associated with the key if found, default otherwise.
- /// True if the key was found and removed; otherwise, false.
- public static bool TryRemove(this IDictionary dictionary, TKey key, [MaybeNullWhen(false)] out TValue value)
+ return count;
+}
+
+///
+/// Tries to remove a key from the dictionary and returns its value.
+///
+/// The key to remove.
+/// The value associated with the key if found, default otherwise.
+/// True if the key was found and removed; otherwise, false.
+public bool TryRemove(TKey key, [MaybeNullWhen(false)] out TValue value)
+{
+ if (dictionary.TryGetValue(key, out value))
{
- if (dictionary.TryGetValue(key, out value))
- {
- return dictionary.Remove(key);
- }
- value = default!;
- return false;
+ return dictionary.Remove(key);
}
- ///
- /// Tries to update a value for an existing key.
- ///
- /// The type of the keys in the dictionary.
- /// The type of the values in the dictionary.
- /// The dictionary to modify.
- /// The key to update.
- /// The new value to set.
- /// True if the key was found and updated; otherwise, false.
- public static bool TryUpdate(this IDictionary dictionary, TKey key, TValue newValue)
+ value = default!;
+ return false;
+}
+
+///
+/// Tries to update a value for an existing key.
+///
+/// The key to update.
+/// The new value to set.
+/// True if the key was found and updated; otherwise, false.
+public bool TryUpdate(TKey key, TValue newValue)
+{
+ if (!dictionary.ContainsKey(key))
{
- if (!dictionary.ContainsKey(key))
- {
- return false;
- }
- dictionary[key] = newValue;
- return true;
+ return false;
}
- /// Performs an action on each element in the dictionary.
- /// The dictionary to process
- /// The action to perform on each element
- public static void ForEach(this IDictionary dictionary, Action action)
+ dictionary[key] = newValue;
+ return true;
+}
+
+/// Performs an action on each element in the dictionary.
+/// The action to perform on each element
+public void ForEach(Action action)
+{
+ ArgumentNullException.ThrowIfNull(action);
+ foreach (KeyValuePair kvp in dictionary)
{
- ArgumentNullException.ThrowIfNull(action, nameof(action));
- foreach (KeyValuePair kvp in dictionary)
- {
- action(kvp.Key, kvp.Value);
- }
+ action(kvp.Key, kvp.Value);
}
+}
+ }
+
/// Inverts a dictionary, using values as keys and keys as values.
/// The type of the keys in the original dictionary
/// The type of the values in the original dictionary
@@ -253,52 +262,52 @@ public static void ForEach(this IDictionary dictiona
public static Dictionary Invert(this IDictionary dictionary)
where TKey : notnull
where TValue : notnull
+{
+ var result = new Dictionary(dictionary.Count);
+ foreach (KeyValuePair kvp in dictionary)
{
- var result = new Dictionary(dictionary.Count);
- foreach (KeyValuePair kvp in dictionary)
+ if (result.ContainsKey(kvp.Value))
{
- if (result.ContainsKey(kvp.Value))
- {
- throw new ArgumentException("Dictionary cannot be inverted because it contains duplicate values");
- }
- result.Add(kvp.Value, kvp.Key);
+ throw new ArgumentException("Dictionary cannot be inverted because it contains duplicate values");
}
- return result;
+ result.Add(kvp.Value, kvp.Key);
}
- ///
- /// Increments a numeric value in a dictionary.
- ///
- /// The type of the keys in the dictionary.
- /// The dictionary to modify.
- /// The key to increment.
- /// The increment value (default 1).
- /// The new value after incrementing.
- public static int IncrementValue(this IDictionary dictionary, TKey key, int increment = 1)
+ return result;
+}
+///
+/// Increments a numeric value in a dictionary.
+///
+/// The type of the keys in the dictionary.
+/// The dictionary to modify.
+/// The key to increment.
+/// The increment value (default 1).
+/// The new value after incrementing.
+public static int IncrementValue(this IDictionary dictionary, TKey key, int increment = 1)
+{
+ if (dictionary.TryGetValue(key, out int currentValue))
{
- if (dictionary.TryGetValue(key, out int currentValue))
- {
- int newValue = currentValue + increment;
- dictionary[key] = newValue;
- return newValue;
- }
- dictionary[key] = increment;
- return increment;
+ int newValue = currentValue + increment;
+ dictionary[key] = newValue;
+ return newValue;
}
- ///
- /// Adds an item to a list value in a dictionary, creating the list if necessary.
- ///
- /// The type of the keys in the dictionary.
- /// The type of items in the list.
- /// The dictionary to modify.
- /// The key whose list to add to.
- /// The item to add to the list.
- public static void AddToList(this IDictionary> dictionary, TKey key, TListItem item)
+ dictionary[key] = increment;
+ return increment;
+}
+///
+/// Adds an item to a list value in a dictionary, creating the list if necessary.
+///
+/// The type of the keys in the dictionary.
+/// The type of items in the list.
+/// The dictionary to modify.
+/// The key whose list to add to.
+/// The item to add to the list.
+public static void AddToList(this IDictionary> dictionary, TKey key, TListItem item)
+{
+ if (!dictionary.TryGetValue(key, out List? list))
{
- if (!dictionary.TryGetValue(key, out List? list))
- {
- list = new List();
- dictionary[key] = list;
- }
- list.Add(item);
+ list = [];
+ dictionary[key] = list;
}
+ list.Add(item);
+}
}
diff --git a/src/VisionaryCoder.Framework/Extensions/EnumerableExtensions.cs b/src/VisionaryCoder.Framework/Extensions/EnumerableExtensions.cs
index 4f0de7e..777b2e9 100644
--- a/src/VisionaryCoder.Framework/Extensions/EnumerableExtensions.cs
+++ b/src/VisionaryCoder.Framework/Extensions/EnumerableExtensions.cs
@@ -21,7 +21,7 @@ public static bool ContainsDuplicates(this IEnumerable? collection, IEqual
{
return false;
}
- HashSet set = comparer == null ? new HashSet() : new HashSet(comparer);
+ HashSet set = comparer == null ? [] : new HashSet(comparer);
return instance.Any(item => !set.Add(item));
}
/// Determines whether the sequence is null or empty.
diff --git a/src/VisionaryCoder.Framework/Extensions/DivideByZeroExtensions.cs b/src/VisionaryCoder.Framework/Extensions/MathExtensions.cs
similarity index 98%
rename from src/VisionaryCoder.Framework/Extensions/DivideByZeroExtensions.cs
rename to src/VisionaryCoder.Framework/Extensions/MathExtensions.cs
index a182099..396b25b 100644
--- a/src/VisionaryCoder.Framework/Extensions/DivideByZeroExtensions.cs
+++ b/src/VisionaryCoder.Framework/Extensions/MathExtensions.cs
@@ -4,7 +4,7 @@ namespace VisionaryCoder.Framework.Extensions;
///
/// Provides extension methods for divide-by-zero validation and safe division operations.
///
-public static class DivideByZeroExtensions
+public static class MathExtensions
{
///
/// Throws a if the specified value equals zero.
diff --git a/src/VisionaryCoder.Framework/Extensions/ServiceCollectionExtensions.cs b/src/VisionaryCoder.Framework/Extensions/ServiceCollectionExtensions.cs
deleted file mode 100644
index 43f51e1..0000000
--- a/src/VisionaryCoder.Framework/Extensions/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using VisionaryCoder.Framework.Providers;
-
-namespace VisionaryCoder.Framework.Extensions;
-///
-/// Extension methods for configuring the VisionaryCoder Framework services.
-///
-public static class ServiceCollectionExtensions
-{
- ///
- /// Adds the VisionaryCoder Framework services to the service collection.
- ///
- /// The service collection to configure.
- /// The service collection for method chaining.
- public static IServiceCollection AddVisionaryCoderFramework(this IServiceCollection services)
- {
- return services.AddVisionaryCoderFramework(_ => { });
- }
-
- ///
- /// Adds the VisionaryCoder Framework services to the service collection with configuration.
- ///
- /// The service collection to configure.
- /// Action to configure framework options.
- /// The service collection for method chaining.
- public static IServiceCollection AddVisionaryCoderFramework(
- this IServiceCollection services,
- Action configureOptions)
- {
- ArgumentNullException.ThrowIfNull(services);
- ArgumentNullException.ThrowIfNull(configureOptions);
- // Configure framework options
- services.Configure(configureOptions);
- // Register core framework services
- services.AddSingleton();
- services.AddScoped();
- // Framework services are now registered
- return services;
- }
-
- ///
- /// Adds framework correlation ID generation services.
- ///
- /// The service collection to configure.
- /// The service collection for method chaining.
- public static IServiceCollection AddFrameworkCorrelation(this IServiceCollection services)
- {
- services.AddScoped();
- return services;
- }
-}
diff --git a/src/VisionaryCoder.Framework/Extensions/TypeExtension.cs b/src/VisionaryCoder.Framework/Extensions/TypeExtension.cs
index 0874dd5..db51b54 100644
--- a/src/VisionaryCoder.Framework/Extensions/TypeExtension.cs
+++ b/src/VisionaryCoder.Framework/Extensions/TypeExtension.cs
@@ -14,583 +14,584 @@ public static class TypeExtension
/// The type of the value.
/// The value to convert.
/// The boolean value, or false if conversion fails.
- public static bool AsBoolean(this T value)
- {
- if (value == null)
- {
- return false;
- }
- return (value) switch
- {
- bool boolValue => boolValue,
- string stringValue => bool.TryParse(stringValue, out bool result) && result,
- int intValue => intValue != 0,
- long longValue => longValue != 0,
- double doubleValue => Math.Abs(doubleValue) > double.Epsilon,
- decimal decimalValue => decimalValue != 0,
- _ => false
- };
- }
+ public static bool AsBoolean(this T value)
+ {
+ if (value == null)
+ {
+ return false;
+ }
+ return (value) switch
+ {
+ bool boolValue => boolValue,
+ string stringValue => bool.TryParse(stringValue, out bool result) && result,
+ int intValue => intValue != 0,
+ long longValue => longValue != 0,
+ double doubleValue => Math.Abs(doubleValue) > double.Epsilon,
+ decimal decimalValue => decimalValue != 0,
+ _ => false
+ };
+ }
/// Converts the value to an integer.
- /// The type of the value.
- /// The value to convert.
- /// The default value to return if conversion fails.
- /// The integer value, or the default value if conversion fails.
- public static int AsInteger(this T value, int defaultValue = 0)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- int intValue => intValue,
- bool boolValue => boolValue ? 1 : 0,
- string stringValue => int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int result) ? result : defaultValue,
- double doubleValue => (int)doubleValue,
- decimal decimalValue => (int)decimalValue,
- long longValue => longValue > int.MaxValue || longValue < int.MinValue ? defaultValue : (int)longValue,
- float floatValue => (int)floatValue,
- byte byteValue => byteValue,
- short shortValue => shortValue,
- uint uintValue => uintValue > int.MaxValue ? defaultValue : (int)uintValue,
- _ => defaultValue
- };
- }
+ /// The type of the value.
+ /// The value to convert.
+ /// The default value to return if conversion fails.
+ /// The integer value, or the default value if conversion fails.
+ public static int AsInteger(this T value, int defaultValue = 0)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ int intValue => intValue,
+ bool boolValue => boolValue ? 1 : 0,
+ string stringValue => int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int result) ? result : defaultValue,
+ double doubleValue => (int)doubleValue,
+ decimal decimalValue => (int)decimalValue,
+ long longValue => longValue > int.MaxValue || longValue < int.MinValue ? defaultValue : (int)longValue,
+ float floatValue => (int)floatValue,
+ byte byteValue => byteValue,
+ short shortValue => shortValue,
+ uint uintValue => uintValue > int.MaxValue ? defaultValue : (int)uintValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a long.
/// The long value, or the default value if conversion fails.
- public static long AsLong(this T value, long defaultValue = 0)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- long longValue => longValue,
- int intValue => intValue,
- string stringValue => long.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out long result) ? result : defaultValue,
- double doubleValue => (long)doubleValue,
- decimal decimalValue => (long)decimalValue,
- float floatValue => (long)floatValue,
- uint uintValue => uintValue,
- ulong ulongValue => ulongValue > long.MaxValue ? defaultValue : (long)ulongValue,
- _ => defaultValue
- };
- }
+ public static long AsLong(this T value, long defaultValue = 0)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ long longValue => longValue,
+ int intValue => intValue,
+ string stringValue => long.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out long result) ? result : defaultValue,
+ double doubleValue => (long)doubleValue,
+ decimal decimalValue => (long)decimalValue,
+ float floatValue => (long)floatValue,
+ uint uintValue => uintValue,
+ ulong ulongValue => ulongValue > long.MaxValue ? defaultValue : (long)ulongValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a double.
/// The double value, or the default value if conversion fails.
- public static double AsDouble(this T value, double defaultValue = 0.0)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- double doubleValue => doubleValue,
- int intValue => intValue,
- long longValue => longValue,
- bool boolValue => boolValue ? 1.0 : 0.0,
- string stringValue => double.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double result) ? result : defaultValue,
- decimal decimalValue => (double)decimalValue,
- float floatValue => floatValue,
- ulong ulongValue => ulongValue,
- _ => defaultValue
- };
- }
+ public static double AsDouble(this T value, double defaultValue = 0.0)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ double doubleValue => doubleValue,
+ int intValue => intValue,
+ long longValue => longValue,
+ bool boolValue => boolValue ? 1.0 : 0.0,
+ string stringValue => double.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double result) ? result : defaultValue,
+ decimal decimalValue => (double)decimalValue,
+ float floatValue => floatValue,
+ ulong ulongValue => ulongValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a decimal.
/// The decimal value, or the default value if conversion fails.
- public static decimal AsDecimal(this T value, decimal defaultValue = 0m)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- decimal decimalValue => decimalValue,
- bool boolValue => boolValue ? 1m : 0m,
- string stringValue => decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result) ? result : defaultValue,
- double doubleValue => (decimal)doubleValue,
- float floatValue => (decimal)floatValue,
- _ => defaultValue
- };
- }
+ public static decimal AsDecimal(this T value, decimal defaultValue = 0m)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ decimal decimalValue => decimalValue,
+ bool boolValue => boolValue ? 1m : 0m,
+ string stringValue => decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result) ? result : defaultValue,
+ double doubleValue => (decimal)doubleValue,
+ float floatValue => (decimal)floatValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a float.
/// The float value, or the default value if conversion fails.
- public static float AsFloat(this T value, float defaultValue = 0.0f)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- bool boolValue => boolValue ? 1.0f : 0.0f,
- string stringValue => float.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defaultValue,
- double doubleValue => (float)doubleValue,
- decimal decimalValue => (float)decimalValue,
- _ => defaultValue
- };
- }
+ public static float AsFloat(this T value, float defaultValue = 0.0f)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ bool boolValue => boolValue ? 1.0f : 0.0f,
+ string stringValue => float.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defaultValue,
+ double doubleValue => (float)doubleValue,
+ decimal decimalValue => (float)decimalValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a string.
/// The string value, or the default value if conversion fails.
- public static string AsString(this T value, string defaultValue = "")
- {
- return value?.ToString() ?? defaultValue;
- }
+ public static string AsString(this T value, string defaultValue = "")
+ {
+ return value?.ToString() ?? defaultValue;
+ }
/// Converts the value to a DateTime.
/// The DateTime value, or the default value if conversion fails.
- public static DateTime AsDateTime(this T value, DateTime defaultValue = default)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- DateTime dateTimeValue => dateTimeValue,
- string stringValue => DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result) ? result : defaultValue,
- long longValue => DateTimeOffset.FromUnixTimeMilliseconds(longValue).DateTime,
- int intValue => DateTimeOffset.FromUnixTimeSeconds(intValue).DateTime,
- _ => defaultValue
- };
- }
+ public static DateTime AsDateTime(this T value, DateTime defaultValue = default)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ DateTime dateTimeValue => dateTimeValue,
+ string stringValue => DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result) ? result : defaultValue,
+ long longValue => DateTimeOffset.FromUnixTimeMilliseconds(longValue).DateTime,
+ int intValue => DateTimeOffset.FromUnixTimeSeconds(intValue).DateTime,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a DateTimeOffset.
/// The DateTimeOffset value, or the default value if conversion fails.
- public static DateTimeOffset AsDateTimeOffset(this T value, DateTimeOffset defaultValue = default)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- DateTimeOffset dateTimeOffsetValue => dateTimeOffsetValue,
- DateTime dateTimeValue => new DateTimeOffset(dateTimeValue),
- string stringValue => DateTimeOffset.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset result) ? result : defaultValue,
- long longValue => DateTimeOffset.FromUnixTimeMilliseconds(longValue),
- int intValue => DateTimeOffset.FromUnixTimeSeconds(intValue),
- _ => defaultValue
- };
- }
+ public static DateTimeOffset AsDateTimeOffset(this T value, DateTimeOffset defaultValue = default)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ DateTimeOffset dateTimeOffsetValue => dateTimeOffsetValue,
+ DateTime dateTimeValue => new DateTimeOffset(dateTimeValue),
+ string stringValue => DateTimeOffset.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset result) ? result : defaultValue,
+ long longValue => DateTimeOffset.FromUnixTimeMilliseconds(longValue),
+ int intValue => DateTimeOffset.FromUnixTimeSeconds(intValue),
+ _ => defaultValue
+ };
+ }
/// Converts the value to a Guid.
/// The Guid value, or the default value if conversion fails.
- public static Guid AsGuid(this T value, Guid defaultValue = default)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- Guid guidValue => guidValue,
- string stringValue => Guid.TryParse(stringValue, out Guid result) ? result : defaultValue,
- byte[] byteArray => byteArray.Length == 16 ? new Guid(byteArray) : defaultValue,
- _ => defaultValue
- };
- }
+ public static Guid AsGuid(this T value, Guid defaultValue = default)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ Guid guidValue => guidValue,
+ string stringValue => Guid.TryParse(stringValue, out Guid result) ? result : defaultValue,
+ byte[] byteArray => byteArray.Length == 16 ? new Guid(byteArray) : defaultValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a byte.
/// The byte value, or the default value if conversion fails.
- public static byte AsByte(this T value, byte defaultValue = 0)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- int intValue => intValue >= byte.MinValue && intValue <= byte.MaxValue ? (byte)intValue : defaultValue,
- bool boolValue => boolValue ? (byte)1 : (byte)0,
- string stringValue => byte.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out byte result) ? result : defaultValue,
- double doubleValue => doubleValue >= byte.MinValue && doubleValue <= byte.MaxValue ? (byte)doubleValue : defaultValue,
- decimal decimalValue => decimalValue >= byte.MinValue && decimalValue <= byte.MaxValue ? (byte)decimalValue : defaultValue,
- _ => defaultValue
- };
- }
+ public static byte AsByte(this T value, byte defaultValue = 0)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ int intValue => intValue >= byte.MinValue && intValue <= byte.MaxValue ? (byte)intValue : defaultValue,
+ bool boolValue => boolValue ? (byte)1 : (byte)0,
+ string stringValue => byte.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out byte result) ? result : defaultValue,
+ double doubleValue => doubleValue >= byte.MinValue && doubleValue <= byte.MaxValue ? (byte)doubleValue : defaultValue,
+ decimal decimalValue => decimalValue >= byte.MinValue && decimalValue <= byte.MaxValue ? (byte)decimalValue : defaultValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a short.
/// The short value, or the default value if conversion fails.
- public static short AsShort(this T value, short defaultValue = 0)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- int intValue => intValue >= short.MinValue && intValue <= short.MaxValue ? (short)intValue : defaultValue,
- bool boolValue => boolValue ? (short)1 : (short)0,
- string stringValue => short.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out short result) ? result : defaultValue,
- double doubleValue => doubleValue >= short.MinValue && doubleValue <= short.MaxValue ? (short)doubleValue : defaultValue,
- decimal decimalValue => decimalValue >= short.MinValue && decimalValue <= short.MaxValue ? (short)decimalValue : defaultValue,
- _ => defaultValue
- };
- }
+ public static short AsShort(this T value, short defaultValue = 0)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ int intValue => intValue >= short.MinValue && intValue <= short.MaxValue ? (short)intValue : defaultValue,
+ bool boolValue => boolValue ? (short)1 : (short)0,
+ string stringValue => short.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out short result) ? result : defaultValue,
+ double doubleValue => doubleValue >= short.MinValue && doubleValue <= short.MaxValue ? (short)doubleValue : defaultValue,
+ decimal decimalValue => decimalValue >= short.MinValue && decimalValue <= short.MaxValue ? (short)decimalValue : defaultValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a char.
/// The char value, or the default value if conversion fails.
- public static char AsChar(this T value, char defaultValue = default)
- {
- if (value == null)
- return defaultValue;
- return value switch
- {
- char charValue => charValue,
- string stringValue => stringValue.Length > 0 ? stringValue[0] : defaultValue,
- int intValue => intValue >= char.MinValue && intValue <= char.MaxValue ? (char)intValue : defaultValue,
- byte byteValue => (char)byteValue,
- _ => defaultValue
- };
- }
+ public static char AsChar(this T value, char defaultValue = default)
+ {
+ if (value == null)
+ return defaultValue;
+ return value switch
+ {
+ char charValue => charValue,
+ string stringValue => stringValue.Length > 0 ? stringValue[0] : defaultValue,
+ int intValue => intValue >= char.MinValue && intValue <= char.MaxValue ? (char)intValue : defaultValue,
+ byte byteValue => (char)byteValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a byte array.
/// The byte array, or null if conversion fails.
- public static byte[]? AsByteArray(this T value)
- {
- if (value == null)
- return null;
- return value switch
- {
- byte[] byteArrayValue => byteArrayValue,
- string stringValue => Encoding.UTF8.GetBytes(stringValue),
- Guid guidValue => guidValue.ToByteArray(),
- _ => null
- };
- }
+ public static byte[]? AsByteArray(this T value)
+ {
+ if (value == null)
+ return null;
+ return value switch
+ {
+ byte[] byteArrayValue => byteArrayValue,
+ string stringValue => Encoding.UTF8.GetBytes(stringValue),
+ Guid guidValue => guidValue.ToByteArray(),
+ _ => null
+ };
+ }
/// Converts the value to an enum of type TEnum.
- /// The type of the value.
- /// The enum type to convert to.
- /// The value to convert.
- /// The default value to return if conversion fails.
- /// The enum value, or the default value if conversion fails.
- public static TEnum AsEnum(this T value, TEnum defaultValue = default) where TEnum : struct, Enum
- {
- if (value == null)
- return defaultValue;
- return (value) switch
- {
- TEnum enumValue => enumValue,
- string stringValue => Enum.TryParse(stringValue, true, out TEnum result) ? result : defaultValue,
- int intValue => Enum.IsDefined(typeof(TEnum), intValue) ? (TEnum)Enum.ToObject(typeof(TEnum), intValue) : defaultValue,
- byte byteValue => Enum.IsDefined(typeof(TEnum), byteValue) ? (TEnum)Enum.ToObject(typeof(TEnum), byteValue) : defaultValue,
- short shortValue => Enum.IsDefined(typeof(TEnum), shortValue) ? (TEnum)Enum.ToObject(typeof(TEnum), shortValue) : defaultValue,
- _ => defaultValue
- };
- }
+ /// The type of the value.
+ /// The enum type to convert to.
+ /// The value to convert.
+ /// The default value to return if conversion fails.
+ /// The enum value, or the default value if conversion fails.
+ public static TEnum AsEnum(this T value, TEnum defaultValue = default) where TEnum : struct, Enum
+ {
+ if (value == null)
+ return defaultValue;
+ return (value) switch
+ {
+ TEnum enumValue => enumValue,
+ string stringValue => Enum.TryParse(stringValue, true, out TEnum result) ? result : defaultValue,
+ int intValue => Enum.IsDefined(typeof(TEnum), intValue) ? (TEnum)Enum.ToObject(typeof(TEnum), intValue) : defaultValue,
+ byte byteValue => Enum.IsDefined(typeof(TEnum), byteValue) ? (TEnum)Enum.ToObject(typeof(TEnum), byteValue) : defaultValue,
+ short shortValue => Enum.IsDefined(typeof(TEnum), shortValue) ? (TEnum)Enum.ToObject(typeof(TEnum), shortValue) : defaultValue,
+ _ => defaultValue
+ };
+ }
/// Converts the value to a TimeSpan.
/// The TimeSpan value, or the default value if conversion fails.
- public static TimeSpan AsTimeSpan(this T value, TimeSpan defaultValue = default)
- {
- if (value == null)
- return defaultValue;
- return (value) switch
- {
- TimeSpan timeSpanValue => timeSpanValue,
- string stringValue => TimeSpan.TryParse(stringValue, CultureInfo.InvariantCulture, out TimeSpan result) ? result : defaultValue,
- long longValue => TimeSpan.FromTicks(longValue),
- int intValue => TimeSpan.FromMilliseconds(intValue),
- double doubleValue => TimeSpan.FromMilliseconds(doubleValue),
- _ => defaultValue
- };
- }
+ public static TimeSpan AsTimeSpan(this T value, TimeSpan defaultValue = default)
+ {
+ if (value == null)
+ return defaultValue;
+ return (value) switch
+ {
+ TimeSpan timeSpanValue => timeSpanValue,
+ string stringValue => TimeSpan.TryParse(stringValue, CultureInfo.InvariantCulture, out TimeSpan result) ? result : defaultValue,
+ long longValue => TimeSpan.FromTicks(longValue),
+ int intValue => TimeSpan.FromMilliseconds(intValue),
+ double doubleValue => TimeSpan.FromMilliseconds(doubleValue),
+ _ => defaultValue
+ };
+ }
/// Converts the value to an array of T where T is the type of the array elements.
- ///