# Abp Ef Core

> ABP Entity Framework Core - DbContext, entity configuration, EfCoreRepository implementation, migrations (dotnet ef migrations add), data seeding. Use when working in EntityFrameworkCore projects, adding migrations, or implementing EF Core repositories.

- Skill: `majiayu000/abp-ef-core` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/abp-ef-core`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/abp-ef-core/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/abp-ef-core

---


# ABP Entity Framework Core

> **Docs**: https://abp.io/docs/latest/framework/data/entity-framework-core

## Never Do

| Don't | Do Instead |
|-------|-----------|
| Skip `b.ConfigureByConvention()` | Always call it first in entity config |
| `AddDefaultRepositories(includeAllEntities: true)` | Use `AddDefaultRepositories()` only for aggregate roots |
| Inject `DbContext` in application/domain services | Use `IRepository<T>` or custom repository interface |
| Use `DbContext` directly outside the EF Core project | Access via `GetDbContextAsync()` inside repository only |

## DbContext Configuration

```csharp
[ConnectionStringName("Default")]
public class MyProjectDbContext : AbpDbContext<MyProjectDbContext>
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }

    public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Configure all entities
        builder.ConfigureMyProject();
    }
}
```

## Entity Configuration

```csharp
public static class MyProjectDbContextModelCreatingExtensions
{
    public static void ConfigureMyProject(this ModelBuilder builder)
    {
        Check.NotNull(builder, nameof(builder));

        builder.Entity<Book>(b =>
        {
            b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema);
            b.ConfigureByConvention(); // ABP conventions (audit, soft-delete, etc.)

            // Property configurations
            b.Property(x => x.Name)
                .IsRequired()
                .HasMaxLength(BookConsts.MaxNameLength);

            b.Property(x => x.Price)
                .HasColumnType("decimal(18,2)");

            // Indexes
            b.HasIndex(x => x.Name);

            // Relationships
            b.HasOne<Author>()
                .WithMany()
                .HasForeignKey(x => x.AuthorId)
                .OnDelete(DeleteBehavior.Restrict);
        });
    }
}
```

## Repository Implementation

```csharp
public class BookRepository : EfCoreRepository<MyProjectDbContext, Book, Guid>, IBookRepository
{
    public BookRepository(IDbContextProvider<MyProjectDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    public async Task<Book> FindByNameAsync(
        string name,
        bool includeDetails = true,
        CancellationToken cancellationToken = default)
    {
        var dbSet = await GetDbSetAsync();

        return await dbSet
            .IncludeDetails(includeDetails)
            .FirstOrDefaultAsync(
                b => b.Name == name,
                GetCancellationToken(cancellationToken));
    }

    public async Task<List<Book>> GetListByAuthorAsync(
        Guid authorId,
        bool includeDetails = false,
        CancellationToken cancellationToken = default)
    {
        var dbSet = await GetDbSetAsync();

        return await dbSet
            .IncludeDetails(includeDetails)
            .Where(b => b.AuthorId == authorId)
            .ToListAsync(GetCancellationToken(cancellationToken));
    }

    public override async Task<IQueryable<Book>> WithDetailsAsync()
    {
        return (await GetQueryableAsync())
            .Include(b => b.Reviews);
    }
}
```

## Extension Method for Include
```csharp
public static class BookEfCoreQueryableExtensions
{
    public static IQueryable<Book> IncludeDetails(
        this IQueryable<Book> queryable,
        bool include = true)
    {
        if (!include)
        {
            return queryable;
        }

        return queryable
            .Include(b => b.Reviews);
    }
}
```

## Migration Commands

```bash
# Navigate to EF Core project
cd src/MyProject.EntityFrameworkCore

# Add migration
dotnet ef migrations add MigrationName

# Apply migration (choose one):
dotnet run --project ../MyProject.DbMigrator   # Recommended - also seeds data
dotnet ef database update  # EF Core command only

# Remove last migration (if not applied)
dotnet ef migrations remove

# Generate SQL script
dotnet ef migrations script
```

> **Note**: ABP templates include `IDesignTimeDbContextFactory` in the EF Core project, so `-s` (startup project) parameter is not needed.

## Module Configuration

```csharp
[DependsOn(typeof(AbpEntityFrameworkCoreModule))]
public class MyProjectEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<MyProjectDbContext>(options =>
        {
            // Add default repositories for aggregate roots only (DDD best practice)
            options.AddDefaultRepositories();
            // ⚠️ Avoid includeAllEntities: true - it creates repositories for child entities,
            // allowing them to be modified without going through the aggregate root,
            // which breaks data consistency
        });

        Configure<AbpDbContextOptions>(options =>
        {
            options.UseSqlServer(); // or UseNpgsql(), UseMySql(), etc.
        });
    }
}
```

## Best Practices

### Repositories for Aggregate Roots Only
Don't use `includeAllEntities: true` in `AddDefaultRepositories()`. This creates repositories for child entities, allowing direct modification without going through the aggregate root - breaking DDD data consistency rules.

```csharp
// ✅ Correct - Only aggregate roots get repositories
options.AddDefaultRepositories();

// ❌ Avoid - Creates repositories for ALL entities including child entities
options.AddDefaultRepositories(includeAllEntities: true);
```

### Always Call ConfigureByConvention
```csharp
builder.Entity<MyEntity>(b =>
{
    b.ConfigureByConvention(); // Don't forget this!
    // Other configurations...
});
```

### Use Table Prefix
```csharp
public static class MyProjectConsts
{
    public const string DbTablePrefix = "App";
    public const string DbSchema = null; // Or "myschema"
}
```

### Performance Tips
- Add explicit indexes for frequently queried fields
- Use `AsNoTracking()` for read-only queries
- Avoid N+1 queries with `.Include()` or specifications
- ABP handles cancellation automatically; use `GetCancellationToken(cancellationToken)` only in custom repository methods
- Consider query splitting for complex queries with multiple collections

### Accessing Raw DbContext
```csharp
public async Task CustomOperationAsync()
{
    var dbContext = await GetDbContextAsync();

    // Raw SQL
    await dbContext.Database.ExecuteSqlRawAsync(
        "UPDATE Books SET IsPublished = 1 WHERE AuthorId = {0}",
        authorId
    );
}
```

## Data Seeding

```csharp
public class MyProjectDataSeedContributor : IDataSeedContributor, ITransientDependency
{
    private readonly IRepository<Book, Guid> _bookRepository;
    private readonly IGuidGenerator _guidGenerator;

    public async Task SeedAsync(DataSeedContext context)
    {
        if (await _bookRepository.GetCountAsync() > 0)
        {
            return;
        }

        await _bookRepository.InsertAsync(
            new Book(_guidGenerator.Create(), "Sample Book", 19.99m, Guid.Empty),
            autoSave: true
        );
    }
}
```

