0

I read that Domain project shouldn't specify any ORM. So if I have to create interface for DbContext implemented in Infrastructture project, how can I do it? How can I specify all required DbSet? Interface in Domain project:

public interface IConfigurationDbContext
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

Implementation in Infrastructure project:

public class ConfigurationDbContext : DbContext, IConfigurationDbContext
{
    public ConfigurationDbContext([NotNull] DbContextOptions<ConfigurationDbContext> options) : base(options)
    {
    }

    public DbSet<Client> Clients { get; set; }
    public DbSet<ApiResource> ApiResources { get; set; }
    public DbSet<ApiScope> ApiScopes { get; set; }
    public DbSet<IdentityResource> IdentityResources { get; set; }
}

As u see, IConfigurationDbContext interface doesn't contain any DbSet, because this way require to specify used ORM in Domain project. So how should I create this interface?

Szyszka947
  • 473
  • 2
  • 5
  • 21

1 Answers1

1

Use IQueryable to create a full-featured abstraction over your DbContext. This preserves the core query-building capabilities of the DbContext, and is easilly substituable by another type using Queryable.AsQueryable.

public interface IConfigurationRepository
{
    public IQueryable<Client> Clients { get; }
    public IQueryable<ApiResource> ApiResources { get; }
    public IQueryable<ApiScope> ApiScopes { get; }
    public IQueryable<IdentityResource> IdentityResources { get; }

    public void Add<TEntity>(TEntity e);
    public void Remove<TEntity>(TEntity e);
    public Task<int> SaveChangesAsync();
}

And you can implement this interface instead of having DbSet properties on your DbContext or you can use explicit interface implementation, eg:

    public IQueryable<Client> Clients => this.Set<Client>();

    public IQueryable<ApiResource> ApiResources => this.Set<ApiResource>();

    public IQueryable<ApiScope> ApiScopes => this.Set<ApiScope>();

    public IQueryable<IdentityResource> IdentityResources => this.Set<IdentityResource>();

If you remove the DbSet properties, you need to declare your entity types in OnModelCreating like this:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<IdentityResource>();
    builder.Entity<ApiResource>();
    builder.Entity<ApiScope>();
    builder.Entity<Client>();
    . . .

 }

And when you need the DbSet<TEntity> get it with

dbContext.Set<TEntity>()
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • It looks pretty good. But what if I want to implement e.g. `AddAsync` or `Entry`? In Domain project I don't have access to EntityEntry class. – Szyszka947 Aug 15 '22 at 20:53
  • 1
    Add/Remove wouldn't leak; so you can add those. For EntityEntries and other EF-specific functionality, you can always downcast to DbContext . If you write an interface that can only be implemented by a DbContext, then you might as well reference your DbContext directly. – David Browne - Microsoft Aug 15 '22 at 21:26
  • Ok, I implemented AddAsync, but I get an error when calling: `InvalidOperationException: The entity type 'IdentityResource' was not found. Ensure that the entity type has been added to the model.` Here's how I implemented it: https://pastebin.com/GLJzv16X. Seems that IQueryable can't replace DbSet? – Szyszka947 Aug 16 '22 at 13:43
  • This feels like half-baked attempt at a compromise. Repository having IQueryable and SaveChanges leaks underlying storage. I don't see much difference than just using DbContext directly. I would not recommend adding a paper-thin abstraction over ORM to satisfy some kind of architectural purism. – Euphoric Apr 11 '23 at 13:23
  • It only decouples the app from EF-specific types. So you can use this pattern for EF-based and non-EF-based repositories, without loosing the core functionality of EF. – David Browne - Microsoft Apr 11 '23 at 15:12