efcore-patterns

Entity Framework Core patterns for ABP Framework code-first development with PostgreSQL.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "efcore-patterns" with this command: npx skills add thapaliyabikendra/ai-artifacts/thapaliyabikendra-ai-artifacts-efcore-patterns

EF Core Patterns

Entity Framework Core patterns for ABP Framework code-first development with PostgreSQL.

Entity Base Classes

Base Class Fields Included

Entity<TKey>

Id

AuditedEntity<TKey>

  • CreationTime, CreatorId, LastModificationTime, LastModifierId

FullAuditedEntity<TKey>

  • IsDeleted, DeleterId, DeletionTime

AggregateRoot<TKey>

Entity + Domain Events + Concurrency Token

FullAuditedAggregateRoot<TKey>

Most common - full features

Entity Configuration

public class Patient : FullAuditedAggregateRoot<Guid> { public string FirstName { get; private set; } public string LastName { get; private set; } public string Email { get; private set; }

private Patient() { } // For EF Core

public Patient(Guid id, string firstName, string lastName, string email) : base(id)
{
    FirstName = Check.NotNullOrWhiteSpace(firstName, nameof(firstName), maxLength: 100);
    LastName = Check.NotNullOrWhiteSpace(lastName, nameof(lastName), maxLength: 100);
    Email = Check.NotNullOrWhiteSpace(email, nameof(email), maxLength: 255);
}

}

Fluent API Configuration

public class PatientConfiguration : IEntityTypeConfiguration<Patient> { public void Configure(EntityTypeBuilder<Patient> builder) { builder.ToTable("Patients"); builder.HasKey(x => x.Id);

    builder.Property(x => x.FirstName).IsRequired().HasMaxLength(100);
    builder.Property(x => x.LastName).IsRequired().HasMaxLength(100);
    builder.Property(x => x.Email).IsRequired().HasMaxLength(255);

    builder.HasIndex(x => x.Email).IsUnique();
    builder.HasQueryFilter(x => !x.IsDeleted); // ABP soft delete
}

}

Relationships

One-to-Many (1:N)

builder.Entity<Appointment>(b => { b.HasOne(x => x.Doctor) .WithMany(x => x.Appointments) .HasForeignKey(x => x.DoctorId) .OnDelete(DeleteBehavior.Restrict); });

Many-to-Many (N:N)

// Explicit join entity (recommended for ABP) public class DoctorSpecialization : Entity { public Guid DoctorId { get; set; } public Guid SpecializationId { get; set; } public override object[] GetKeys() => new object[] { DoctorId, SpecializationId }; }

builder.Entity<DoctorSpecialization>(b => { b.HasKey(x => new { x.DoctorId, x.SpecializationId }); b.HasOne(x => x.Doctor).WithMany(x => x.Specializations).HasForeignKey(x => x.DoctorId); b.HasOne(x => x.Specialization).WithMany(x => x.Doctors).HasForeignKey(x => x.SpecializationId); });

One-to-One (1:1)

builder.Entity<PatientProfile>(b => { b.HasOne(x => x.Patient) .WithOne(x => x.Profile) .HasForeignKey<PatientProfile>(x => x.PatientId); });

Value Objects (Owned Types)

builder.Entity<Patient>(b => { b.OwnsOne(x => x.Address, address => { address.Property(a => a.Street).HasMaxLength(200); address.Property(a => a.City).HasMaxLength(100); }); });

Migrations

Add migration

cd api/src/ClinicManagementSystem.EntityFrameworkCore dotnet ef migrations add AddPatientEntity --startup-project ../ClinicManagementSystem.DbMigrator

Apply migration

dotnet run --project ../ClinicManagementSystem.DbMigrator

PostgreSQL-Specific Patterns

Data Types

builder.Entity<AuditRecord>(b => { b.Property(x => x.Tags).HasColumnType("text[]"); // Array b.Property(x => x.Metadata).HasColumnType("jsonb"); // JSON b.Property(x => x.Id).HasDefaultValueSql("gen_random_uuid()"); // UUID });

Index Types

builder.Entity<Patient>(b => { b.HasIndex(x => x.Email).IsUnique(); // B-tree (default) b.HasIndex(x => x.Tags).HasMethod("GIN"); // GIN for arrays/jsonb b.HasIndex(x => x.SearchVector).HasMethod("GIN"); // Full-text search b.HasIndex(x => x.CreationTime).HasMethod("BRIN"); // Large tables b.HasIndex(x => x.Email).HasFilter(""IsDeleted" = false"); // Partial });

Full-Text Search

builder.Entity<Patient>(b => { b.Property(x => x.SearchVector) .HasColumnType("tsvector") .HasComputedColumnSql( "to_tsvector('english', coalesce("FirstName", '') || ' ' || coalesce("LastName", ''))", stored: true);

b.HasIndex(x => x.SearchVector).HasMethod("GIN");

});

// Query var patients = await dbSet .Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery("english", searchTerm))) .ToListAsync();

Performance Patterns

Batch Operations (EF Core 7+)

// Batch update await _context.Patients .Where(p => p.Status == PatientStatus.Inactive) .ExecuteUpdateAsync(s => s.SetProperty(p => p.IsArchived, true));

// Batch delete await _context.AuditLogs .Where(l => l.CreationTime < DateTime.UtcNow.AddMonths(-6)) .ExecuteDeleteAsync();

Split Queries

var doctors = await _context.Doctors .Include(d => d.Appointments) .Include(d => d.Specializations) .AsSplitQuery() // Avoid Cartesian explosion .ToListAsync();

Compiled Queries

private static readonly Func<ClinicDbContext, Guid, Task<Patient?>> GetPatientById = EF.CompileAsyncQuery((ClinicDbContext context, Guid id) => context.Patients.FirstOrDefault(p => p.Id == id));

Global Query Filters

// ABP automatically applies: // - ISoftDelete: WHERE IsDeleted = false // - IMultiTenant: WHERE TenantId = @currentTenantId

// Disable temporarily using (_dataFilter.Disable<ISoftDelete>()) { var allPatients = await _patientRepository.GetListAsync(); }

Concurrency Handling

// ABP provides automatic concurrency via AggregateRoot try { await _patientRepository.UpdateAsync(patient); } catch (AbpDbConcurrencyException) { throw new UserFriendlyException("Record modified by another user. Please refresh."); }

Quality Checklist

  • Entities inherit appropriate ABP base class

  • Private setters with public domain methods

  • Private parameterless constructor for EF Core

  • Fluent API configuration in separate class

  • Indexes defined for query patterns

  • Relationships have explicit delete behavior

  • PostgreSQL-specific types where appropriate (jsonb, arrays)

  • GIN indexes for jsonb and full-text columns

Detailed References

For comprehensive patterns, see:

  • references/postgresql-advanced.md

  • references/migration-strategies.md

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

clean-code-dotnet

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-excellence

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-advanced-types

No summary provided by upstream source.

Repository SourceNeeds Review