using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class PatientConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("patients"); builder.HasKey(p => p.Id); builder.Property(p => p.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()"); builder.Property(p => p.Mrn).HasColumnName("mrn").HasMaxLength(20).IsRequired(); builder.Property(p => p.FirstName).HasColumnName("first_name").HasMaxLength(100).IsRequired(); builder.Property(p => p.LastName).HasColumnName("last_name").HasMaxLength(100).IsRequired(); builder.Property(p => p.DateOfBirth).HasColumnName("date_of_birth"); builder.Property(p => p.Gender).HasColumnName("gender").HasMaxLength(10).IsRequired(); builder.Property(p => p.Status).HasColumnName("status").HasMaxLength(20).HasDefaultValue("active"); builder.Property(p => p.BloodType).HasColumnName("blood_type").HasMaxLength(5) .HasConversion( v => v!.Value.ToDbString(), v => BloodTypeExtensions.FromDbString(v)); builder.Property(p => p.Allergies).HasColumnName("allergies"); builder.Property(p => p.EmergencyContactName).HasColumnName("emergency_contact_name").HasMaxLength(200); builder.Property(p => p.EmergencyContactPhone).HasColumnName("emergency_contact_phone").HasMaxLength(20); builder.Property(p => p.NameSearchToken) .HasColumnName("name_search_token") .HasMaxLength(64); builder.Property(p => p.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("NOW()"); // MRN uses exact-match unique index — MRN lookups are always equality checks, // never LIKE/ILIKE. A B-tree unique index satisfies O(log n) point lookup. // Name search uses ILIKE on first_name/last_name — a prefix scan, not an // equality match — so no index here; full ILIKE is intentionally unindexed // at this scale (pg_trgm GIN would be warranted at >500k patients). builder.HasIndex(p => p.Mrn).IsUnique(); builder.HasIndex(p => p.NameSearchToken); } }