-
Notifications
You must be signed in to change notification settings - Fork 0
EF Core Integration
The Masterly.NonEmptyList.EntityFrameworkCore package provides Entity Framework Core integration for NonEmptyList<T> and ImmutableNonEmptyList<T>.
dotnet add package Masterly.NonEmptyList.EntityFrameworkCore
Or via Package Manager Console:
Install-Package Masterly.NonEmptyList.EntityFrameworkCore
Store NonEmptyList<T> properties as JSON strings in the database.
using Masterly.NonEmptyList.EntityFrameworkCore;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public NonEmptyList<string> Tags { get; set; } = new("default");
}
public class AppDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Tags)
.HasNonEmptyListConversion();
}
}var options = new JsonSerializerOptions { WriteIndented = true };
modelBuilder.Entity<Product>()
.Property(p => p.Tags)
.HasNonEmptyListConversion(options);public class Document
{
public int Id { get; set; }
public ImmutableNonEmptyList<string> Authors { get; set; }
}
modelBuilder.Entity<Document>()
.Property(d => d.Authors)
.HasImmutableNonEmptyListConversion();Automatically configure all NonEmptyList<T> properties:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Auto-configure all NonEmptyList and ImmutableNonEmptyList properties
modelBuilder.ApplyNonEmptyListConventions();
}Configure one-to-many relationships with NonEmptyList<T> as the navigation property.
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; }
public NonEmptyList<OrderItem> Items { get; set; } = null!;
}
public class OrderItem
{
public int Id { get; set; }
public string ProductName { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
}modelBuilder.Entity<Order>()
.HasNonEmptyManyWithOne(
o => o.Items,
i => i.Order,
i => i.OrderId);modelBuilder.Entity<Order>()
.HasNonEmptyManyWithDeleteBehavior(
o => o.Items,
i => i.Order,
DeleteBehavior.Cascade);modelBuilder.Entity<Order>()
.HasRequiredNonEmptyMany(
o => o.Items,
i => i.Order);Configure many-to-many relationships with NonEmptyList<T>.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public NonEmptyList<Course> Courses { get; set; } = null!;
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public NonEmptyList<Student> Students { get; set; } = null!;
}modelBuilder.Entity<Student>()
.HasNonEmptyManyToMany(
s => s.Courses,
c => c.Students);public class AuthorBook
{
public int AuthorId { get; set; }
public int BookId { get; set; }
public DateTime AssignedDate { get; set; }
}
modelBuilder.Entity<Author>()
.HasMany(a => a.Books)
.WithMany(b => b.Authors)
.UsingEntity<AuthorBook>(
l => l.HasOne<Book>().WithMany().HasForeignKey(ab => ab.BookId),
r => r.HasOne<Author>().WithMany().HasForeignKey(ab => ab.AuthorId));Include() does not work with NonEmptyList<T> navigation properties. EF Core cannot instantiate NonEmptyList<T> during materialization because it requires at least one element.
Use projections instead:
// DON'T DO THIS - will throw
var student = context.Students.Include(s => s.Courses).First();
// DO THIS - use projection
var result = context.Students
.Where(s => s.Id == studentId)
.Select(s => new
{
s.Name,
CourseCount = s.Courses.Count(),
CourseTitles = s.Courses.Select(c => c.Title).ToList()
})
.FirstOrDefault();Validate NonEmptyList<T> properties on save:
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString)
.UseNonEmptyListValidation();
});This throws InvalidOperationException if any NonEmptyList<T> property is null or empty when saving.
Convert query results to NonEmptyList<T>:
// ToNonEmptyList - throws if empty
NonEmptyList<Product> products = context.Products
.Where(p => p.IsActive)
.ToNonEmptyList();
// ToNonEmptyListOrNull - returns null if empty
NonEmptyList<Product>? products = context.Products
.Where(p => p.Category == "Electronics")
.ToNonEmptyListOrNull();
// Async versions
NonEmptyList<Product> products = await context.Products
.Where(p => p.Price > 100)
.ToNonEmptyListAsync();public class AppDbContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(connectionString)
.UseNonEmptyListValidation();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// JSON conversion for Tags
modelBuilder.Entity<Product>()
.Property(p => p.Tags)
.HasNonEmptyListConversion();
// One-to-many relationship
modelBuilder.Entity<Order>()
.HasNonEmptyManyWithOne(
o => o.Items,
i => i.Order,
i => i.OrderId);
}
}| Feature | Supported |
|---|---|
| JSON Value Conversion | ✅ Yes |
| ImmutableNonEmptyList | ✅ Yes |
| One-to-Many Relationships | ✅ Yes |
| Many-to-Many Relationships | ✅ Yes (with limitations) |
| Include() on NonEmptyList | ❌ No (use projections) |
| Query Extensions | ✅ Yes |
| Validation Interceptor | ✅ Yes |
| Change Tracking | ✅ Yes |
| Auto-Configuration | ✅ Yes |
- Learn about JSON Serialization for basic JSON support
- See Thread Safety for concurrency considerations