Skip to main content

Entity Framework Core

The Spur.EntityFrameworkCore package wraps common EF Core operations so they return Result<T> instead of throwing exceptions.

Installation

dotnet add package Spur.EntityFrameworkCore

Query extensions

All query extensions are on IQueryable<T> and work with any EF Core DbSet.

FirstOrResultAsync

Returns the first matching record, or Error.NotFound if none:

using Spur.EntityFrameworkCore;

var result = await _db.Users
.Where(u => u.IsActive)
.FirstOrResultAsync();

With a predicate:

var result = await _db.Users
.FirstOrResultAsync(u => u.Email == email);

With a custom error:

var result = await _db.Users
.FirstOrResultAsync(
u => u.Email == email,
notFoundError: Error.NotFound($"No user with email {email}", "USER_NOT_FOUND"));

SingleOrResultAsync

Returns exactly one matching record. Fails if zero or more than one:

var result = await _db.Users
.SingleOrResultAsync(u => u.Id == userId);

ExistsOrResultAsync

Checks if any record matches. Returns Result<Unit>:

var result = await _db.Users
.ExistsOrResultAsync(u => u.Email == email);

ToResultListAsync

Loads all matching records into an IReadOnlyList<T>:

var result = await _db.Products
.Where(p => p.CategoryId == categoryId)
.ToResultListAsync();

ToResultListOrFailAsync

Like ToResultListAsync, but fails if the list is empty:

var result = await _db.Orders
.Where(o => o.UserId == userId)
.ToResultListOrFailAsync(
notFoundError: Error.NotFound("No orders found", "NO_ORDERS"));

SaveChangesResultAsync

Wraps SaveChangesAsync and catches database exceptions:

var result = await _db.SaveChangesResultAsync();

Exception mapping:

ExceptionError
DbUpdateConcurrencyExceptionError.Conflict
DbUpdateException (unique violation)Error.Conflict
DbUpdateException (FK violation)Error.Validation
Other DbUpdateExceptionError.Unexpected

In a pipeline

public async Task<Result<UserDto>> GetUserDto(int userId)
{
return await _db.Users
.FirstOrResultAsync(u => u.Id == userId)
.Validate(u => u.IsActive,
Error.Validation("User is inactive", "USER_INACTIVE"))
.Map(u => new UserDto(u.Id, u.Name, u.Email));
}

See also