Introduction

Abp.EntityFrameworkCore nuget package is used to integrate to Entity Framework (EF) Core ORM framework. After installing this package, we should also add a DependsOn attribute for AbpEntityFrameworkCoreModule.

DbContext

EF Core requires to define a class derived from DbContext. In ABP, we should derive from AbpDbContext as shown below:

public class MyDbContext : AbpDbContext
{
    public DbSet<Product> Products { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options)
        : base(options)
    {
    }
}

Constructor should get a DbContextOptions<T> as shown above.

Configuration

There are a few ways of adding your DbContext to the application.

In Startup Class

For an ASP.NET Core project, you will have a Startup file and you can use AddDbContext extension method of EF Core as you always do:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    ...
    
    services.AddDbContext<MyDbContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("Default"));
    });

    ...
}

This approach is standard, simple and enough for ABP. In this approach, connection string is static (because this code runs once in the application). If you have single database for a dbcontext, that's fine. But if you want to dynamically determine connection string, see the next section.

In Module PreInitialize

You can do it in PreInitialize of your module as shown below:

public class MyEfCoreAppModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.DefaultNameOrConnectionString = GetConnectionString("Default");

        Configuration.Modules.AbpEfCore().AddDbContext<MyDbContext>(options =>
        {
            options.DbContextOptions.UseSqlServer(options.ConnectionString);
        });

        ...
    }
}

First, we are setting default connection string (we can get it from a configuration file for example). Then we are using AddDbContext method of ABP EF Core module. In this method, we can setup DbContext options.

options.ConnectionString is the default connection string normally. But since ABP uses IConnectionStringResolver to determine it, this behaviour can be changed and connection string can be determined dynamically. Also, in AddDbContext method, you don't have to use options.ConnectionString if you want.

The action passed to AddDbContext is called whenever a DbContext instance will be created.

Note that; there is also IServiceCollection.AddAbpDbContext extension method that can be used in Startup class. But even in that case, you should define DefaultNameOrConnectionString in your module class.

Repositories

Repositories are used to abstract data access from higher layers. See repository documentation for more. 

Default Repositories

Abp.EntityFrameworkCore implements default repositories for all entities defined in your DbContext. You don't have to create repository classes to use predefined repository methods. Example:

public class PersonAppService : IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

        _personRepository.Insert(person);
    }
}

PersonAppService contructor-injects IRepository<Person> and uses the Insert method. In this way, you can easily inject IRepository<TEntity> (or IRepository<TEntity, TPrimaryKey>) and use predefined methods. See repository documentation for all predefined methods.

Custom Repositories

If standard repository methods are not sufficient, you can create custom repository classes for your entities.

Application Specific Base Repository Class

ASP.NET Boilerplate provides a base class EfCoreRepositoryBase to implement repositories easily. To implement IRepository interface, you can just derive your repository from this class. But it's better to create your own base class that extens EfRepositoryBase. Thus, you can add shared/common methods to your repositories easily. An example base class all for repositories of a SimpleTaskSystem application:

//Base class for all repositories in my application
public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>
    where TEntity : class, IEntity<TPrimaryKey>
{
    public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    //add common methods for all repositories
}

//A shortcut for entities those have integer Id
public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int>
    where TEntity : class, IEntity<int>
{
    public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    //do not add any method here, add to the class above (because this class inherits it)
}

Notice that we're inheriting from EfCoreRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>. This declares to ASP.NET Boilerplate to use SimpleTaskSystemDbContext in our repositories.

Custom Repository Example

To implement a custom repository, just derive from your application specific base repository class we created above.

Assume that we have a Task entity that can be assigned to a Person (entity) and a Task has a State (new, assigned, completed... and so on). We may need to write a custom method to get list of Tasks with some conditions and with AssisgnedPerson property pre-fetched (included) in a single database query. See the example code:

public interface ITaskRepository : IRepository<Task, long>
{
    List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
    public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
    {
        var query = GetAll();

        if (assignedPersonId.HasValue)
        {
            query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
        }

        if (state.HasValue)
        {
            query = query.Where(task => task.State == state);
        }

        return query
            .OrderByDescending(task => task.CreationTime)
            .Include(task => task.AssignedPerson)
            .ToList();
    }
}

We first defined ITaskRepository and then implemented it. GetAll() returns IQueryable<Task>, then we can add some Where filters using given parameters. Finally we can call ToList() to get list of Tasks.

You can also use Context object in repository methods to reach to your DbContext and directly use Entity Framework APIs. 

Repository Best Practices

Missing Points

Abp.EntityFrameworkCore module currently does not support following ABP features: