Skip to content

Repositories for SQL databases (EF Core)

anton-martyniuk edited this page Feb 13, 2023 · 5 revisions

Modern generic repositories for SQL databases are built on top of the following ORM frameworks: EF Core and Dapper. To use EF Core repository install the Modern.Repositories.EFCore.DependencyInjection Nuget package and register it within Modern builder in DI:

builder.Services
    .AddModern()
    .AddRepositoriesEfCore(options =>
    {
        options.AddRepository<FlyingDbContext, AirplaneDbo, long>(useDbFactory: false);
    });

Specify the type of EF Core DbContext, Dbo entity model and primary key. useDbFactory parameter indicates whether repository with DbContextFactory should be used. The default value is false.

ℹ️ Use this parameter if you plan to inherit from this generic repository and extend or change its functionality.
When using DbContextFactory every repository creates and closes a database connection in each method.
When NOT using DbContextFactory repository shares the same database connection during its lifetime.

⚠️ It is not recommended to use useDbFactory = false when repository is registered as SingleInstance, otherwise a single database connection will persist during the whole application lifetime


Modern generic repositories are flexible, easily changeable and extendable. New methods can be added to repository as well as each method can be overriden.

The following interfaces can be inherited from:

IModernCrudRepository<TEntity, TId>
IModernQueryRepository<TEntity, TId>

Or inherit from a combined interface:

IModernRepository<TEntity, TId> : IModernCrudRepository<TEntity, TId>, IModernQueryRepository<TEntity, TId>

Lets have a look at example of inheritance from ModernEfCoreRepository:

public interface IAirplaneRepository : IModernRepository<AirplaneDbo, long>
{
}

public class AirplaneRepository : ModernEfCoreRepositoryWithFactory<FlyingDbContext, AirplaneDbo, long>, IAirplaneRepository
{
    public AirplaneRepository(IDbContextFactory<FlyingDbContext> dbContextFactory, IOptions<EfCoreRepositoryConfiguration?> configuration)
        : base(dbContextFactory, configuration)
    {
    }

    // Override GetEntityId
    protected override long GetEntityId(AirplaneDbo entity) => entity.Id;

    // Override GetEntityIncludeQuery to include Airplane Model
    protected override EntityIncludeQuery<AirplaneDbo> GetEntityIncludeQuery()
        => new(query => query.Include(t => t.Model));
    
    // Override CreateAsync to select existing Airplane Model
    public override async Task<AirplaneDbo> CreateAsync(AirplaneDbo entity, CancellationToken cancellationToken = default)
    {
        await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);
        var model = await dbContext.AirplaneModels.FirstOrDefaultAsync(x => x.Name == entity.Model.Name, cancellationToken);
        if (model is not null)
        {
            entity.Model = model;
        }

        await dbContext.Airplanes.AddAsync(entity, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);

        return entity;
    }

    // Override CreateAsync(list) to select existing Airplane Model
    public override async Task<List<AirplaneDbo>> CreateAsync(List<AirplaneDbo> entities, CancellationToken cancellationToken = default)
    {
        await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);

        foreach (var entity in entities)
        {
            var model = await dbContext.AirplaneModels.FirstOrDefaultAsync(x => x.Name == entity.Model.Name, cancellationToken);
            if (model is not null)
            {
                entity.Model = model;
            }

            await dbContext.Airplanes.AddAsync(entity, cancellationToken);
        }
        
        await dbContext.SaveChangesAsync(cancellationToken);

        return entities;
    }
}
Clone this wiki locally