EF6 Code First with generic repository and Dependency Injection and SoC

前端 未结 1 1145
眼角桃花
眼角桃花 2021-01-30 04:17

After a lots of reading and trying things out with Entity Framework latest stable version (6.1.1).

I\'m reading lots of contradictions about whether or not

1条回答
  •  生来不讨喜
    2021-01-30 05:18

    A little explanation will hopefully clear up your confusion. The repository pattern exists to abstract away database connection and querying logic. ORMs (object-relational mappers, like EF) have been around in one form or another so long that many people have forgotten or never had the immense joy and pleasure of dealing with spaghetti code littered with SQL queries and statements. Time was that if you wanted to query a database, you were actually responsible for crazy things like initiating a connection and actually constructing SQL statements from ether. The point of the repository pattern was to give you a single place to put all this nastiness, away from your beautiful pristine application code.

    Fast forward to 2014, Entity Framework and other ORMs are your repository. All the SQL logic is packed neatly away from your prying eyes, and instead you have a nice programmatic API to use in your code. In one respect, that's enough abstraction. The only thing it doesn't cover is the dependency on the ORM itself. If you later decide you want to switch out Entity Framework for something like NHibernate or even a Web API, you've got to do surgery on your application to do so. As a result, adding another layer of abstraction is still a good idea, but just not a repository, or at least let's say a typical repository.

    The repository you have is a typical repository. It merely creates proxies for the Entity Framework API methods. You call repo.Add and the repository calles context.Add. It's, frankly, ridiculous, and that's why many, including myself, say don't use repositories with Entity Framework.

    So what should you do? Create services, or perhaps it's best said as "service-like classes". When services start being discussed in relation to .NET, all of sudden you're talking about all kinds of things that are completely irrelevant to what we're discussing here. A service-like class is like a service in that it has endpoints that return a particular set of data or perform a very specific function on some set of data. For example, whereas with a typical repository you would find your self doing things like:

    articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
    

    Your service class would work like:

    service.GetPublishedArticles();
    

    See, all the logic for what qualifies as a "published article" is neatly contain in the endpoint method. Also, with a repository, you're still exposing the underlying API. It's easier to switch out with something else because the base datastore is abstracted, but if the API for querying into that datastore changes you're still up a creek.

    UPDATE

    Set up would be very similar; the difference is mostly in how you use a service versus a repository. Namely, I wouldn't even make it entity dependent. In other words, you'd essentially have a service per context, not per entity.

    As always, start with an interface:

    public interface IService
    {
        IEnumerable
    GetPublishedArticles(); ... }

    Then, your implementation:

    public class EntityFrameworkService : IService
        where TContext : DbContext
    {
        protected readonly TContext context;
    
        public EntityFrameworkService(TContext context)
        {
            this.context = context;
        }
    
        public IEnumerable
    GetPublishedArticles() { ... } }

    Then, things start to get a little hairy. In the example method, you could simply reference the DbSet directly, i.e. context.Articles, but that implies knowledge about the DbSet names in the context. It's better to use context.Set(), for more flexibility. Before I jump trains too much though, I want to point out why I named this EntityFrameworkService. In your code, you would only ever reference your IService interface. Then, via your dependency injection container, you can substitute EntityFrameworkService for that. This opens up the ability to create other service providers like maybe WebApiService, etc.

    Now, I like to use a single protected method that returns a queryable that all my service methods can utilize. This gets rid of a lot of the cruft like having to initialize a DbSet instance each time via var dbSet = context.Set();. That would look a little like:

    protected virtual IQueryable GetQueryable(
        Expression> filter = null,
        Func, IOrderedQueryable> orderBy = null,
        string includeProperties = null,
        int? skip = null,
        int? take = null)
        where TEntity : class
    {
        includeProperties = includeProperties ?? string.Empty;
        IQueryable query = context.Set();
    
        if (filter != null)
        {
            query = query.Where(filter);
        }
    
        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }
    
        if (orderBy != null)
        {
            query = orderBy(query);
        }
    
        if (skip.HasValue)
        {
            query = query.Skip(skip.Value);
        }
    
        if (take.HasValue)
        {
            query = query.Take(take.Value);
        }
    
        return query;
    }
    

    Notice that this method is, first, protected. Subclasses can utilize it, but this should definitely not be part of the public API. The whole point of this exercise is to not expose queryables. Second, it's generic. In otherwords, it can handle any type you throw at it as long as there's something in the context for it.

    Then, in our little example method, you'd end up doing something like:

    public IEnumerable
    GetPublishedArticles() { return GetQueryable
    ( m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now, m => m.OrderByDescending(o => o.PublishDate) ).ToList(); }

    Another neat trick to this approach is the ability to have generic service methods utilizing interfaces. Let's say I wanted to be able to have one method to get a published anything. I could have an interface like:

    public interface IPublishable
    {
        PublishStatus Status { get; set; }
        DateTime PublishDate { get; set; }
    }
    

    Then, any entities that are publishable would just implement this interface. With that in place, you can now do:

    public IEnumerable GetPublished()
        where TEntity : IPublishable
    {
        return GetQueryable(
            m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
            m => m.OrderByDescending(o => o.PublishDate)
        ).ToList();
    }
    

    And then in your application code:

    service.GetPublished
    ();

    0 讨论(0)
提交回复
热议问题