YC.Monad.EntityFrameworkCore 1.0.0

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

YC.Monad.EntityFrameworkCore

License: MIT NuGet

Description

YC.Monad.EntityFrameworkCore is an extension library for YC.Monad that provides Entity Framework Core integration. It offers async extension methods that seamlessly convert EF Core query results into Option<T> types, making it easier to work with potentially absent data in a type-safe, functional way.

Features

  • Async Option Extensions: Convert EF Core query results to Option<T> types
  • FirstOrNoneAsync: Safe alternative to FirstOrDefaultAsync
  • SingleOrNoneAsync: Safe alternative to SingleOrDefaultAsync
  • LastOrNoneAsync: Safe alternative to LastOrDefaultAsync
  • FindOrNoneAsync: Safe alternative to FindAsync for DbSet operations
  • Type-Safe: No more null reference exceptions
  • Cancellation Token Support: Full support for async cancellation

Getting Started

Dependencies

  • .NET 6 or later
  • YC.Monad 1.1.0 or later
  • Microsoft.EntityFrameworkCore 6.0 or later

Installation

You can install the YC.Monad.EntityFrameworkCore package via NuGet:

dotnet add package YC.Monad.EntityFrameworkCore

Or via Package Manager Console:

Install-Package YC.Monad.EntityFrameworkCore

Usage

FirstOrNoneAsync

Retrieves the first element of a query or returns None if no element is found:

using YC.Monad;
using YC.Monad.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

// Without predicate
var user = await dbContext.Users
    .FirstOrNoneAsync();

user.Match(
    some: u => Console.WriteLine($"Found: {u.Name}"),
    none: () => Console.WriteLine("No user found")
);

// With predicate
var activeUser = await dbContext.Users
    .FirstOrNoneAsync(u => u.IsActive);

// With cancellation token
var user = await dbContext.Users
    .FirstOrNoneAsync(cancellationToken);

// With predicate and cancellation token
var user = await dbContext.Users
    .FirstOrNoneAsync(u => u.Age > 18, cancellationToken);

SingleOrNoneAsync

Retrieves the single element that matches the query or returns None if no element is found. Throws if more than one element exists:

// Find a unique user by email
var user = await dbContext.Users
    .SingleOrNoneAsync(u => u.Email == "user@example.com");

user.Match(
    some: u => Console.WriteLine($"User ID: {u.Id}"),
    none: () => Console.WriteLine("User not found")
);

// Without predicate
var singleUser = await dbContext.Users
    .Where(u => u.Id == userId)
    .SingleOrNoneAsync();

// With cancellation token
var user = await dbContext.Users
    .SingleOrNoneAsync(u => u.Username == "admin", cancellationToken);

LastOrNoneAsync

Retrieves the last element of a query or returns None if no element is found:

// Get the most recent order
var lastOrder = await dbContext.Orders
    .OrderBy(o => o.CreatedAt)
    .LastOrNoneAsync();

lastOrder.Match(
    some: o => Console.WriteLine($"Last order: {o.Id}"),
    none: () => Console.WriteLine("No orders found")
);

// With predicate
var lastActiveOrder = await dbContext.Orders
    .LastOrNoneAsync(o => o.Status == OrderStatus.Active);

// With cancellation token
var lastOrder = await dbContext.Orders
    .LastOrNoneAsync(o => o.CustomerId == customerId, cancellationToken);

FindOrNoneAsync

Finds an entity by its primary key or returns None if not found:

// Find by single key
var user = await dbContext.Users
    .FindOrNoneAsync(userId);

// Find by composite key
var orderItem = await dbContext.OrderItems
    .FindOrNoneAsync(orderId, productId);

// With cancellation token
var user = await dbContext.Users
    .FindOrNoneAsync(new object[] { userId }, cancellationToken);

// Usage with pattern matching
await dbContext.Products
    .FindOrNoneAsync(productId)
    .Match(
        some: async product => 
        {
            product.Stock -= quantity;
            await dbContext.SaveChangesAsync();
            Console.WriteLine("Stock updated");
        },
        none: () => Console.WriteLine("Product not found")
    );

Complete Examples

Repository Pattern with Option

public class UserRepository
{
    private readonly AppDbContext _context;

    public UserRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<Option<User>> GetByIdAsync(int id, CancellationToken ct = default)
    {
        return await _context.Users.FindOrNoneAsync(id, ct);
    }

    public async Task<Option<User>> GetByEmailAsync(string email, CancellationToken ct = default)
    {
        return await _context.Users
            .FirstOrNoneAsync(u => u.Email == email, ct);
    }

    public async Task<Option<User>> GetActiveUserByUsernameAsync(string username, CancellationToken ct = default)
    {
        return await _context.Users
            .Where(u => u.IsActive)
            .SingleOrNoneAsync(u => u.Username == username, ct);
    }
}

Service Layer with Railway-Oriented Programming

public class OrderService
{
    private readonly AppDbContext _context;

    public OrderService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<Result<Order>> CreateOrderAsync(int userId, List<int> productIds)
    {
        var user = await _context.Users.FindOrNoneAsync(userId);

        return await user
            .ToResult(Error.Create("USER_NOT_FOUND", "User not found"))
            .BindAsync(async u => await ValidateUserAsync(u))
            .BindAsync(async u => await CreateOrderForUserAsync(u, productIds));
    }

    private async Task<Result<User>> ValidateUserAsync(User user)
    {
        if (!user.IsActive)
            return Result<User>.Failure(Error.Create("USER_INACTIVE", "User is not active"));

        return Result<User>.Success(user);
    }

    private async Task<Result<Order>> CreateOrderForUserAsync(User user, List<int> productIds)
    {
        var order = new Order { UserId = user.Id, CreatedAt = DateTime.UtcNow };
        
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();

        return Result<Order>.Success(order);
    }
}

Query with Optional Filtering

public async Task<List<Product>> GetProductsAsync(
    Option<string> category,
    Option<decimal> minPrice,
    CancellationToken ct = default)
{
    var query = _context.Products.AsQueryable();

    // Apply optional filters
    query = category.Match(
        some: cat => query.Where(p => p.Category == cat),
        none: () => query
    );

    query = minPrice.Match(
        some: price => query.Where(p => p.Price >= price),
        none: () => query
    );

    return await query.ToListAsync(ct);
}

Benefits

Type Safety

Instead of dealing with null values:

// Traditional approach - null checks required
var user = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user != null)
{
    // Use user
}
else
{
    // Handle null case
}

Use Option types for explicit handling:

// With Option - explicit and type-safe
var userOption = await dbContext.Users.FirstOrNoneAsync(u => u.Id == userId);

userOption.Match(
    some: user => ProcessUser(user),
    none: () => HandleNotFound()
);

Railway-Oriented Programming

Chain operations safely without null checking:

var result = await dbContext.Users
    .FirstOrNoneAsync(u => u.Email == email)
    .MapAsync(async user => await UpdateUserAsync(user))
    .BindAsync(async user => await SendEmailAsync(user))
    .ToResult(Error.Create("USER_NOT_FOUND", "User not found"));
  • YC.Monad - Core functional programming types (Result, Option, Error)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

Author

Yusuf Çırak

Support

If you encounter any issues or have questions, please open an issue on GitHub.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 274 12/15/2025