Monad.NET.EntityFrameworkCore
2.0.0-beta.2
dotnet add package Monad.NET.EntityFrameworkCore --version 2.0.0-beta.2
NuGet\Install-Package Monad.NET.EntityFrameworkCore -Version 2.0.0-beta.2
<PackageReference Include="Monad.NET.EntityFrameworkCore" Version="2.0.0-beta.2" />
<PackageVersion Include="Monad.NET.EntityFrameworkCore" Version="2.0.0-beta.2" />
<PackageReference Include="Monad.NET.EntityFrameworkCore" />
paket add Monad.NET.EntityFrameworkCore --version 2.0.0-beta.2
#r "nuget: Monad.NET.EntityFrameworkCore, 2.0.0-beta.2"
#:package Monad.NET.EntityFrameworkCore@2.0.0-beta.2
#addin nuget:?package=Monad.NET.EntityFrameworkCore&version=2.0.0-beta.2&prerelease
#tool nuget:?package=Monad.NET.EntityFrameworkCore&version=2.0.0-beta.2&prerelease
Monad.NET
Monad.NET is a functional programming library for .NET. Option, Result, Validation, Try, and more — with zero dependencies on .NET 6+.
// Transform nullable chaos into composable clarity
var result = user.ToOption()
.Filter(u => u.IsActive)
.Map(u => u.Email)
.Bind(email => SendWelcome(email))
.Match(
some: _ => "Email sent",
none: () => "User not found or inactive"
);
Author: Behrang Mohseni
License: MIT — Free for commercial and personal use
Upgrading to v2.0
Version 2.0 is a major release focused on C#-idiomatic naming and API simplification:
- ~285 methods removed for better discoverability
- Rust-style → C#-style naming:
Unwrap()→GetValue(),FlatMap→Bind,MapErr→MapError default(Result<T,E>)now protected: ThrowsInvalidOperationExceptionto prevent invalid states- 2,042 tests ensure correctness across all changes
All core functionality remains — removed methods have straightforward replacements using Match(), GetValueOr(), and LINQ.
See the full migration guide →
Table of Contents
- Why Monad.NET?
- Which Monad Should I Use?
- Installation
- Quick Start
- Documentation
- Examples
- Performance
- Resources
- FAQ
- Contributing
Why Monad.NET?
Modern C# has excellent features—nullable reference types, pattern matching, records. So why use Monad.NET?
The short answer: Composability. While C# handles individual cases well, chaining operations that might fail, be absent, or need validation quickly becomes verbose. Monad.NET provides a unified API for composing these operations elegantly.
Honest Comparisons with Modern C#
Optional Values: Option<T> vs Nullable Reference Types
Modern C# (NRT enabled):
User? user = FindUser(id);
if (user is not null)
{
Profile? profile = user.GetProfile();
if (profile is not null)
{
return profile.Email; // Still might be null!
}
}
return "default@example.com";
With Monad.NET:
return FindUser(id)
.Bind(user => user.GetProfile())
.Map(profile => profile.Email)
.GetValueOr("default@example.com");
Verdict: NRTs catch null issues at compile time—use them! But Option<T> shines when you need to chain operations or transform optional values. If you're writing nested null checks, Option is cleaner.
Error Handling: Result<T, E> vs Exceptions
Modern C# with exceptions:
public Order ProcessOrder(OrderRequest request)
{
try
{
var validated = ValidateOrder(request); // throws ValidationException
var inventory = ReserveInventory(validated); // throws InventoryException
var payment = ChargePayment(inventory); // throws PaymentException
return CreateOrder(payment);
}
catch (ValidationException ex) { /* handle */ }
catch (InventoryException ex) { /* handle */ }
catch (PaymentException ex) { /* handle */ }
}
With Monad.NET:
public Result<Order, OrderError> ProcessOrder(OrderRequest request)
{
return ValidateOrder(request)
.Bind(ReserveInventory)
.Bind(ChargePayment)
.Bind(CreateOrder);
}
Verdict: Exceptions are fine for exceptional situations (network failures, disk errors). Use Result<T, E> when failure is expected (validation errors, business rule violations). The signature Result<Order, OrderError> tells callers exactly what can go wrong—no surprises.
Validation: Validation<T, E> vs FluentValidation
With FluentValidation (industry standard):
public class UserValidator : AbstractValidator<UserRequest>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty().MinimumLength(2);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 120);
}
}
// Usage
var result = await validator.ValidateAsync(request);
if (!result.IsOk)
return BadRequest(result.Errors);
With Monad.NET:
var user = ValidateName(request.Name)
.Apply(ValidateEmail(request.Email), (name, email) => (name, email))
.Apply(ValidateAge(request.Age), (partial, age) => new User(partial.name, partial.email, age));
Verdict: FluentValidation is battle-tested and has more features (async rules, dependency injection, localization). Use it for complex scenarios. Validation<T, E> is lighter, has no dependencies, and works well with other Monad.NET types. Choose based on your needs.
Discriminated Unions: The Missing Feature in C#
The Problem: C# still lacks native discriminated unions (sum types) as of C# 14. Despite adding extension members, null-conditional assignment, field-backed properties, and other features—discriminated unions didn't make the cut. This remains one of the most requested language features, with the proposal actively discussed by the C# Language Design Team. F#, Rust, Swift, Kotlin, and TypeScript all have this feature. C# developers have been waiting for years.
With Monad.NET Source Generators:
[Union]
public abstract partial record GetUserResult
{
public partial record Success(User User) : GetUserResult;
public partial record NotFound : GetUserResult;
public partial record ValidationError(string Message) : GetUserResult;
}
// Exhaustive matching - compiler ensures all cases handled
result.Match(
success: s => Ok(s.User),
notFound: _ => NotFound(),
validationError: e => BadRequest(e.Message)
);
Design Principles
- Explicit over implicit — No hidden nulls, no surprise exceptions
- Composition over inheritance — Small, focused types that combine well
- Immutability by default — All types are immutable and thread-safe
- Minimal dependencies — Zero on .NET 6+; only Microsoft polyfills on netstandard2.x
Which Monad Should I Use?
| Scenario | Use This |
|---|---|
| A value might be missing | Option<T> |
| An operation can fail with a typed error | Result<T, E> |
| Need to show ALL validation errors at once | Validation<T, E> |
| Wrapping code that throws exceptions | Try<T> |
| A list must have at least one item | NonEmptyList<T> |
| UI state for async data loading (Blazor) | RemoteData<T, E> |
| Compose async operations with shared dependencies | ReaderAsync<R, A> |
| Dependency injection without DI container | Reader<R, A> |
| Need to accumulate logs/traces alongside results | Writer<W, T> |
| Thread state through pure computations | State<S, A> |
| Defer and compose side effects | IO<T> |
Language Inspirations
These types come from functional programming languages. Here's the lineage:
| Monad.NET | F# | Rust | Haskell |
|---|---|---|---|
Option<T> |
Option<'T> |
Option<T> |
Maybe a |
Result<T,E> |
Result<'T,'E> |
Result<T,E> |
Either a b |
Validation<T,E> |
— | — | Validation e a |
Try<T> |
— | — | — (Scala) |
RemoteData<T,E> |
— | — | — (Elm) |
NonEmptyList<T> |
— | — | NonEmpty a |
Writer<W,T> |
— | — | Writer w a |
Reader<R,A> |
— | — | Reader r a |
ReaderAsync<R,A> |
— | — | ReaderT IO r a |
State<S,A> |
— | — | State s a |
IO<T> |
— | — | IO a |
Installation
# Core library
dotnet add package Monad.NET
# Optional: Discriminated unions via source generators
dotnet add package Monad.NET.SourceGenerators
# Optional: ASP.NET Core integration
dotnet add package Monad.NET.AspNetCore
# Optional: Entity Framework Core integration
dotnet add package Monad.NET.EntityFrameworkCore
Dependencies
| Target Framework | Dependencies |
|---|---|
| .NET 6.0+ | None (zero dependencies) |
| .NET Standard 2.1 | Microsoft.Bcl.AsyncInterfaces, System.Collections.Immutable, System.Text.Json |
| .NET Standard 2.0 | Above + System.Memory |
Note: The netstandard2.x dependencies are Microsoft polyfill packages that provide modern .NET APIs to older frameworks. They are automatically included and have no transitive third-party dependencies.
Quick Start
Option — Handle missing values
// Method syntax (recommended)
var email = FindUser(id)
.Select(user => user.Email)
.Where(email => email.Contains("@"))
.SelectMany(email => ValidateEmail(email));
// Or use Map/Filter/AndThen
var email = FindUser(id)
.Map(user => user.Email)
.Filter(email => email.Contains("@"))
.Bind(email => ValidateEmail(email));
Result — Handle expected failures
public Result<Order, OrderError> ProcessOrder(OrderRequest request)
{
return ValidateOrder(request)
.Bind(order => CheckInventory(order))
.Bind(order => ChargePayment(order))
.Tap(order => _logger.LogInfo($"Order {order.Id} created"))
.TapErr(err => _logger.LogError($"Order failed: {err}"));
}
Validation — Collect all errors
var user = ValidateName(form.Name)
.Apply(ValidateEmail(form.Email), (name, email) => (name, email))
.Apply(ValidateAge(form.Age), (partial, age) => new User(partial.name, partial.email, age));
// Shows ALL validation errors at once
user.Match(
valid: u => CreateUser(u),
invalid: errors => ShowErrors(errors)
);
Important: LINQ query syntax (
from...select) on Validation short-circuits on the first error. UseApply()orZip()to accumulate all errors.
Discriminated Unions — Type-safe alternatives
[Union]
public abstract partial record Shape
{
public partial record Circle(double Radius) : Shape;
public partial record Rectangle(double Width, double Height) : Shape;
}
// Exhaustive matching
var area = shape.Match(
circle: c => Math.PI * c.Radius * c.Radius,
rectangle: r => r.Width * r.Height
);
Documentation
| Document | Description |
|---|---|
| NuGet Packages | All packages with version badges and installation instructions |
| Quick Start Guide | Get up and running in 5 minutes |
| Core Types | Detailed docs for Option, Result, Validation, Try, and more |
| Advanced Usage | LINQ, async, collection operations, parallel processing |
| Examples | Real-world code samples |
| Integrations | Source Generators, ASP.NET Core, Entity Framework Core |
| API Reference | Complete API documentation |
| Compatibility | Supported .NET versions |
| Performance Benchmarks | Detailed performance comparisons and analysis |
| Versioning Policy | API versioning and deprecation policy |
| Pitfalls & Gotchas | Common mistakes to avoid |
| Logging Guidance | Best practices for logging |
| Type Selection Guide | Decision flowchart for choosing the right type |
| Migration Guide | Migrate from language-ext, OneOf, FluentResults |
| Architectural Decisions | Design decisions, rationale, and trade-offs |
Examples
The examples/ folder contains a comprehensive example application:
examples/Monad.NET.Examples— Interactive console app demonstrating all monad types with real-world patterns.
Performance
Monad.NET is designed for correctness and safety first, but performance is still a priority:
| Aspect | Details |
|---|---|
| Struct-based | Option<T>, Result<T,E>, Try<T>, etc. are readonly struct — no heap allocations |
| No boxing | Generic implementations avoid boxing value types |
| Lazy evaluation | UnwrapOrElse, OrElse use Func<> for deferred computation |
| Zero allocations | Most operations on value types are allocation-free |
| Aggressive inlining | Hot paths use [MethodImpl(AggressiveInlining)] |
| ConfigureAwait(false) | All async methods use ConfigureAwait(false) |
For typical use cases, the overhead is negligible (nanoseconds). The safety guarantees and code clarity typically outweigh any micro-optimization concerns.
Resources
Want to dive deeper into functional programming and these patterns?
Books
| Book | Author | Why Read It |
|---|---|---|
| Functional Programming in C# | Enrico Buonanno | The definitive guide to FP in C#. Covers Option, Result, Validation, and more. |
| Domain Modeling Made Functional | Scott Wlaschin | Uses F# but concepts translate directly. Excellent on making illegal states unrepresentable. |
| Programming Rust | Blandy, Orendorff, Tindall | Rust's Option and Result are nearly identical to Monad.NET's versions. |
Online Resources
| Resource | Description |
|---|---|
| F# for Fun and Profit | Scott Wlaschin's legendary site. Start with Railway Oriented Programming. |
| Rust Error Handling | Official Rust book chapter on Option and Result. |
| Haskell Error Handling | Haskell wiki on error handling patterns. |
| Parse, Don't Validate | Alexis King's influential post on type-driven design. |
Videos & Talks
| Talk | Speaker | Topics |
|---|---|---|
| Functional Design Patterns | Scott Wlaschin | Monads, Railway Oriented Programming, composition |
| Domain Modeling Made Functional | Scott Wlaschin | Making illegal states unrepresentable |
| The Power of Composition | Scott Wlaschin | Why small, composable functions matter |
Related C# Libraries
| Library | Description |
|---|---|
| language-ext | Extensive FP library for C#. More features than Monad.NET but steeper learning curve. |
| OneOf | Focused on discriminated unions. Lighter weight. |
| FluentResults | Result pattern with fluent API. Good for simple use cases. |
| ErrorOr | Discriminated union for errors. Popular in Clean Architecture circles. |
Key Concepts
- Railway Oriented Programming — Treat errors as alternate tracks, not exceptions
- Making Illegal States Unrepresentable — Use types to prevent bugs at compile time
- Parse, Don't Validate — Push validation to the boundaries, work with valid types internally
- Composition over Inheritance — Small, focused types that combine well
When NOT to Use Monad.NET
Monad.NET is not always the right choice. Here's when to stick with native C#:
| Scenario | Recommendation |
|---|---|
| Simple null checks | Use ??, ?., and nullable reference types |
| Exceptional failures (IO, network) | Use exceptions — they're designed for this |
| Performance-critical hot loops | Avoid lambda allocations; use traditional control flow |
| Team unfamiliar with FP concepts | Consider the learning curve before adoption |
| Simple CRUD operations | Often overkill; use when composition benefits outweigh complexity |
Good rule of thumb: If you're writing if (x != null) { ... } once, use nullable. If you're chaining multiple such checks, use Option.
FAQ
Can I use Monad.NET with Entity Framework?
Yes! Use Option<T> for optional relationships and Result<T, E> for operations that might fail. See EF Core Integration.
Can I use Monad.NET with ASP.NET Core?
Absolutely. See ASP.NET Core Integration.
What's the difference between Result and Validation?
Result— Short-circuits on first error (like&&)Validation— Accumulates ALL errors (for showing multiple validation messages)
Is Monad.NET thread-safe?
Yes. All types are immutable readonly struct with no shared mutable state.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md for guidelines.
Development requirements:
- .NET 8.0 SDK or later (for building all targets)
- Your preferred IDE (Visual Studio, Rider, VS Code)
git clone https://github.com/behrangmohseni/Monad.NET.git
cd Monad.NET
dotnet build
dotnet test
License
This project is licensed under the MIT License.
You are free to use, modify, and distribute this library in both commercial and open-source projects. See LICENSE for details.
Monad.NET — Functional programming for the pragmatic .NET developer.
Documentation · NuGet · Issues
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Monad.NET (>= 2.0.0-beta.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0-beta.2 | 44 | 2/2/2026 |
| 2.0.0-beta.1 | 37 | 2/1/2026 |
| 1.1.2 | 83 | 1/25/2026 |
| 1.1.1 | 90 | 1/7/2026 |
| 1.1.0 | 94 | 12/30/2025 |
| 1.0.0 | 93 | 12/28/2025 |
| 1.0.0-beta.2 | 134 | 12/24/2025 |
| 1.0.0-beta.1 | 128 | 12/23/2025 |
| 1.0.0-alpha.13 | 128 | 12/22/2025 |
| 1.0.0-alpha.11 | 74 | 12/21/2025 |
| 1.0.0-alpha.10 | 225 | 12/16/2025 |
| 1.0.0-alpha.9 | 220 | 12/16/2025 |
| 1.0.0-alpha.8 | 194 | 12/15/2025 |