Using DbContext and DbSet instead of implementing

2019-03-22 06:36发布

问题:

I have seen plenty of articles about implementing repositories and a unit of work. I have also seen articles about how doing this is just adding extra complexity, because the DbContext is already using the repository and unit of work pattern.

I will be refactoring an application that pretty much has a repository for each entity, and would like to remove as much complexity as possible.

Can anyone explain/provide links to articles/blogs/etc that explain how to use the DbContext instead of my own repositories?

回答1:

Rob Conery's a smart guy, but I have to disagree with him on this one. Command/Query method he suggests just removes the query logic from the action (which is something, but not much). There's still no true abstraction. And, the base controller method is not great either. While the method of data access (here, an ORM) is abstracted to just one place in your code, making for somewhat easier changes in the future, it does nothing to abstract the API for working with that data layer, so it almost becomes pointless. The only thing it really saves you from is having to put private readonly AppContext context = new AppContext(); at the top of every controller. You could perhaps combine the two, but then you're still looking at having to modify every one of those query classes if your data layer changes.

I think the chief problem here is that everyone is trying to achieve something different. Rob's suggested approaches are geared towards staying DRY. Personally, my goal in abstracting the data layer is for the easy ability to switch out data access methods at a later point. Perhaps that's because I've been burned in the past by choose some method of getting at data that ended up not working out ideally in the long run. We can at least both agree, though, the traditional way of implementing repositories is a bad idea.

In truth, this is a problem with no true answer. To a certain extent you have to just do what works best for you and your application. The method I've settled on is somewhat akin to the repository pattern, I use generic methods instead of a generic class. Something like the following:

public class Repository : IRepository
{
    protected readonly DbContext context;

    public Repository(DbContext context)
    {
        this.context = context;
    }

    public IEnumerable<TEntity> GetAll<TEntity>()
    {
        var dbSet = context.Set<TEntity>;
        return dbSet.ToList();
    }

    ...
}

My actual class is much more complex than that, but that's enough to illustrate the main points. First, the context is injected. This is one area where I strongly disagree with Rob. Perhaps if you're playing fast and loose with your context you may not know "where it came from", but I use a dependency injection container which creates one instance per request of my context. In other words, I know exactly where it came from.

Second, because this is a standard old class with generic methods, I don't need to new up a bunch of them in my controller actions. I also don't have to define a separate repository class for each entity. I can simply inject this one dependency into my controller and roll:

public class FooController : Controller
{
    private readonly IRepository repo;

    public FooController(IRepository repo)
    {
        this.repo = repo;
    }

    ...
}

Then, if I want to fetch some Foos, I just do:

repo.GetAll<Foo>();

Or if I want some Bars: repo.GetAll<Bar>().

Then, you can start to do really interesting things via generic constraints. Let's say I'd like to be able to pull out only items that are "published". All I need is an interface like:

public interface IPublishable
{
    PublishStatus Status { get; }
    DateTime? PublishDate { get; }
    DateTime? ExpireDate { get; }
}

Then, I simply make whatever entities I want to be "publishable" implement this interface or inherit from an abstract class that implements it. Once that's all set up, I can now do something like the following in my repository:

public IEnumerable<TEntity> GetAllPublished<TEntity>()
    where TEntity : IPublishable
{
    var dbSet = context.Set<TEntity>();
    return dbSet.Where(m =>
        m.Status == PublishStatus.Published &&
        m.PublishDate.HasValue && m.PublishDate.Value <= DateTime.Now &&
        (!m.ExpireDate.HasValue || m.ExpireDate.Value > DateTime.Now)
    ).ToList();
}

Now, I have one method in one repository that can pull out just the published items for any entity that implements IPublishable. Code duplication is at a bare minimum, and more importantly, if I need to switch out the data access layer with something else like a differ ORM or even a Web API, I just have to change this one repository class. All the rest of my code happily chugs along as if nothing happened.