Search code examples
c#interfaceentity-framework-coredbset

How to abstract a EntityFramework Core DBSet<T> within an interface?


I am using EF Core 6, and my DbContext looks like this :

public class SpaceBattlesDatabase: DbContext, ISpaceBattlesDataBase
{
     public DbSet<Building> Buildings => Set<Building>();
     // + other irrelevant code
}

I would like to abstract it with an interface, to use in the central layer in a clean architecture..

public interface ISpaceBattlesDataBase
{
    public IQueryable<Building> Buildings { get; }
}

But the compilator warns me :

Error CS0738 'SpaceBattlesDatabase' does not implement interface member 'ISpaceBattlesDataBase.Buildings'. 'SpaceBattlesDatabase.Buildings' cannot implement 'ISpaceBattlesDataBase.Buildings' because it does not have the matching return type of 'IQueryable'.

But according to the documentation and the metadata, DbSet<TEntity> should implement IQueryable<TEntity>..

IEnumerable<T> doesn't work either.. Am I missing something here ?

How can I abstract the DbSet to use in the interface ?

Thank you


Solution

  • Am I missing something here?

    C# does not support return type covariance for interface implementations (I would say that explanation given by Eric Lippert here can shed some light on why). In this particular case you can work around with explicit interface implementation:

    public class SpaceBattlesDatabase: DbContext, ISpaceBattlesDataBase
    {
        public DbSet<Building> Buildings => Set<Building>();
        IQueryable<Building> ISpaceBattlesDataBase.Buildings => Buildings;
    }
    
    public interface ISpaceBattlesDataBase
    {
        IQueryable<Building> Buildings { get; }
    }
    

    Another approach with abstract base class inheriting from interface due to C# 9 feature covariant returns types:

    public class SpaceBattlesDatabase: SpaceBattlesDataBase
    {
        public override DbSet<Building> Buildings => Set<Building>();
    }
    
    public abstract class SpaceBattlesDataBase : DbContext, ISpaceBattlesDataBase
    {
        public abstract IQueryable<Building> Buildings { get; }
    }
    
    public interface ISpaceBattlesDataBase
    {
        IQueryable<Building> Buildings { get; }
    }