Шаг 13: DbContext и конфигурации EF Core
DbContext -- главный класс Entity Framework Core, представляющий сессию с базой данных. Через него выполняется чтение, запись и транзакции.
OrderDbContext.cs
В папке Persistence проекта Infrastructure:
using Microsoft.EntityFrameworkCore;
using OrderManagement.Domain.Entities;
using OrderManagement.Domain.Interfaces;
namespace OrderManagement.Infrastructure.Persistence;
public class OrderDbContext : DbContext, IUnitOfWork
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
public OrderDbContext(
DbContextOptions<OrderDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(OrderDbContext).Assembly);
}
}
DbSet<Order> -- коллекция, привязанная к таблице. ApplyConfigurationsFromAssembly автоматически находит все IEntityTypeConfiguration<T>.
OrderConfiguration.cs
В папке Persistence/Configurations:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OrderManagement.Domain.Entities;
namespace OrderManagement.Infrastructure.Persistence.Configurations;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.HasKey(o => o.Id);
builder.Property(o => o.CustomerId)
.IsRequired().HasMaxLength(100);
builder.Property(o => o.Status)
.IsRequired().HasConversion<string>().HasMaxLength(20);
builder.Property(o => o.CreatedAt).IsRequired();
builder.OwnsOne(o => o.ShippingAddress, sa =>
{
sa.Property(a => a.Street)
.HasColumnName("ShippingStreet")
.HasMaxLength(200).IsRequired();
sa.Property(a => a.City)
.HasColumnName("ShippingCity")
.HasMaxLength(100).IsRequired();
sa.Property(a => a.PostalCode)
.HasColumnName("ShippingPostalCode")
.HasMaxLength(20).IsRequired();
sa.Property(a => a.Country)
.HasColumnName("ShippingCountry")
.HasMaxLength(100).IsRequired();
});
builder.OwnsOne(o => o.TotalAmount, m =>
{
m.Property(x => x.Amount)
.HasColumnName("TotalAmount")
.HasColumnType("decimal(18,2)").IsRequired();
m.Property(x => x.Currency)
.HasColumnName("TotalCurrency")
.HasMaxLength(3).IsRequired();
});
builder.HasMany(o => o.Items).WithOne()
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade);
builder.Ignore(o => o.DomainEvents);
builder.HasIndex(o => o.CustomerId);
builder.HasIndex(o => o.Status);
}
}
OwnsOne маппит Value Object как Owned Type -- столбцы хранятся в таблице Orders. HasConversion<string>() сохраняет enum как текст. Ignore исключает DomainEvents из схемы БД.
OrderItemConfiguration.cs
public class OrderItemConfiguration
: IEntityTypeConfiguration<OrderItem>
{
public void Configure(EntityTypeBuilder<OrderItem> builder)
{
builder.ToTable("OrderItems");
builder.HasKey(i => i.Id);
builder.Property(i => i.ProductId)
.IsRequired().HasMaxLength(100);
builder.Property(i => i.ProductName)
.IsRequired().HasMaxLength(200);
builder.Property(i => i.Quantity).IsRequired();
builder.OwnsOne(i => i.UnitPrice, m =>
{
m.Property(x => x.Amount)
.HasColumnName("UnitPrice")
.HasColumnType("decimal(18,2)").IsRequired();
m.Property(x => x.Currency)
.HasColumnName("UnitCurrency")
.HasMaxLength(3).IsRequired();
});
builder.Ignore(i => i.DomainEvents);
builder.Ignore(i => i.TotalPrice);
}
}