2

I'm building a ASP.NET Core Web API and the application consists of 3 main modules.

  1. Data module: Contains the entities and DbContext
  2. Web API: Contains the controllers
  3. Service module: Contains all the data processing logic between the data module and the web API.

I'm not using repository pattern to access data from the service module, and I was thinking about fully exposing my DbContext to the service module to query it directly from the service module. Later, I created an interface and defined some methods to expose only the DbSet<T> or IQueryable<T> to its caller,

public interface IShareableContext
{
    //option 1
    DbSet<TEntity> ExposeDbSet<TEntity>() where TEntity : class;
    //option 2
    IQueryable<TEntity> ExposeIQueryable<TEntity>() where TEntity : class;
    Task SaveChangesAsync();
}

internal class DefaultSharedContext : IShareableContext
{
    private readonly ApplicationDbContext _ctx;

    public DefaultSharedContext(ApplicationDbContext ctx)
    {
        _ctx = ctx;
    }

    public DbSet<TEntity> ExposeDbSet<TEntity>() where TEntity : class
    {
        return _ctx.Set<TEntity>();
    }

    public IQueryable<TEntity> ExposeIQueryable<TEntity>() where TEntity : class
    {
        return _ctx.Set<TEntity>();
    }

    public async Task SaveChangesAsync()
    {
        await _ctx.SaveChangesAsync();
    }
}

And this interface and implementation will be registered on the dependency injection container to be used by the service module. But I'm still wondering whether should I choose option 1 method or option 2 method in my IShareableContext interface. Especially about returning a concrete class vs interface. If I'm returning IQueryable<T> I will have to define and implement some additional methods for behaviors such as add, delete and modify since IQueryable<T> does not define them. But I can't figure out whether staying away from returning DbSet<T> is worth for having to implement a few more behaviors.

9
  • Have you considered creating another interface that extends IQueryable that defines the extra methods that you need?
    – JimmyJames
    Nov 27, 2023 at 19:28
  • @JimmyJames then I will have to reimplement all the methods in the IQueryable. Nov 27, 2023 at 20:15
  • 1
    Do you actually need the full flexibility of the DbSet, or IQueriable, or do you have a relatively small, finite set of use cases (e.g., something that you could literally list as bullet points that you could then turn into method names)? If the latter, then there's your data access interface. Nov 27, 2023 at 20:20
  • 1
    "I prefer returning an interface type" - that was not my question. My question was what kinds of capabilities the clients of that interface need - this is what should inform the design of your interface (what level of abstraction, what kinds of methods, how many methods). Concrete class vs interface type is not the thing to focus on, but rather what should be in that interface. Nov 28, 2023 at 9:42
  • 2
    "Clients need ... all capabilities of IQuaryable<T>" - that may actually be true, but it also might be an overkill that at the moment seems appealing for its short-term convenience - what I'm getting at is that you yourself should question & reexamine that assumption before you proceed (the answer you accepted is saying the same thing, BTW; it's a good answer, re-read it carefully a couple of times). E.g. IQuaryable<T> might be more low level than desireable. If after examining those assumptions IQuaryable<T> still seems like the right choice, then sure - go for it. Nov 28, 2023 at 11:04

3 Answers 3

8

You should return exactly what using clients need.

If you believe in the solid principles the two worth considering here are Interface Segregation (depend only on what you need) and Dependency Inversion (depend on abstractions, not on concretions).

Design your methods so that code that uses them can follow these principles.

Why? Because when you follow the ISP you make your needs clear. That keeps your options open about what can fulfill them. Because when you follow DIP changes are less likely to impact you since you depend on stable abstractions not volatile concretions. Which lets you change the concretions with little pain.

I will have to reimplement all the methods in the IQueryable.

If you really need all those methods you can compose yourself of, and delegate to, something that already implements them. It’s a bit of keyboard typing but very flexible.

If you don't really need all those methods you can still compose and delegate to something that already implements them. Prune what you expose back to what's really needed. Done this way you can add more as needed until you stabilize your interface, and later refactor to squeeze out implementations that are not needed.

1
  • 1
    I like your suggestion since retuning an interface seems an acceptable tradeoff for writing few more lines in its implementation class and this works well with DIP. Thank you! Nov 28, 2023 at 9:04
1

As a general pattern, I suggest returning the concrete thing (in your case DbSet<TEntity>) and accepting in method parameters the most generic thing you can work with (in your case, since clients need to add, delete, etc. maybe IDbSet<TEntity>).

Also,

If I'm returning IQueryable I will have to define and implement some additional methods for behaviors such as add, delete and modify since IQueryable does not define them.

don't do that. At that point, you're back at implementing the repository pattern.

1
  • 1
    EF Core does not have IDbSet<T>. Appears that it is only an EF thing. Nov 28, 2023 at 8:53
1

If you have to implement methods of IQueryable then your object is not an IQueryable and you shouldn’t return that interface.

Unless you think that adding these methods adds value and you would ever use them for real and they have real implementations doing real useful work.

Not the answer you're looking for? Browse other questions tagged or ask your own question.