简体   繁体   中英

Simple Injector: injecting navigation properties into repositories

Let's assume we have an entity with multiple many to many navigation fields of the same type:

class Post : IdProvider<TPrimaryKey>
    where TKey : struct
{
    ...
    public virtual ICollection<User> LikedBy { get; set; }
    public virtual ICollection<User> SharedBy { get; set; }
}

Bear in mind it's purely a made up example and it can be anything with two properties of the same type.

I'm currently running base generic repositories classes for a single entities registered in this manner . The definition looks like this:

EntityRepository<TKey, T> : EntityRepositoryBase, IEntityRepository<TKey, T>
    where T : class, IIdProvider<TKey> 
    where TKey : struct 

DI picking up the right typeparam and injects it into my repos, works like a charm. I'm very new to DI and read this article but still struggle to see why is it so bad if it's making my life easier and hides EF specific methods out of controller's sight.

And then I have N-to-many base repository class that handles relations between two entities. Maybe it's an oversimplification but I'd like to think each many-to-many relationships has principal and dependent so I will be adding liked/shared Users to the Post and never vise versa. In my current implementation base repo class has an abstract property

public abstract Expression<Func<TPrincipal, ICollection<TDependent>>> GetDependentCollectionExpression { get; }

so I can't make base repo class non-abstract and currently have to create multiple implementation of this class for each of my relationships explicitly specifying dependent property.

class PostToLikedUsersRepository : PrincipalToManyDependentRepository<Post, User>
{
    ...
    public override Expression<Func<Post, ICollection<User>>> GetDependentCollectionExpression
    {
        get { return item => item.LikedBy; }
    }
}

One way to get rid of it is to search for a right TPrincipal's property with a TDependent type via reflection, but there going to be many of these in current example. I never thought of how I can even handle multiple repos implementation for the same Principal to Dependent type as I'm not sure how can I make DI inject the right implementation of each repo to each controller: one responsible for likes and one responsible for shares.

So my intuition and intent is to parametrize the property selecion somehow. Seems like I need to inject another parameter to my repo that gets the right property automagically, but I have no idea how I can achieve it. Should I inject the property here? But how and where exactly should I pick the right value for it in a general manner? Should it be another constructor parameter?

I don't agree with the article's interpretation of "mediation" between the data layer and the application logic. Mediation is an act of facilitating negotiation between the layers not concealing/stone-walling them. A repository should serve to simplify the interaction between application logic and data. If you have chosen an ORM such as Entity Framework to serve as your data access layer, then my recommendation would be to embrace everything that it offers to make your code:

  • Simpler to write.
  • Simpler to understand.
  • Simpler to maintain.

Generic repositories IMO are an anti-pattern in the sense that they encourage developers to think of entities individually rather than collectively as a domain structure. If I have Orders, customers, and order lines, do I need repositories for each? If I create an order I also need to create order lines, and associate the order to a customer. With generic repositories I might have an OrderRepository : Repository<Order> with a Get<T> base method, but then I need to also get a Customer, does that mean a reference to a CustomerRepository too? What about creating OrderLines? They have dependencies on products and other elements. If the repositories are to serve to isolate my application logic from the entities then would that mean using an OrderLineRepository, ProductRepository etc. All just to create an order. To isolate application code from entities and the like, repositories should not return entities. If they do you are still dependent on EF behind the scenes. Lazy Loading will fail if you're outside of a DbContext's scope. Returning DTOs was one common work-around for this, but this leads to huge inefficiencies and inflexibility when serving up data while trying to hide Entity Framework. It also leads to brittle code or a lot of duplication as single-purpose repositories are shared between different areas of the application where each area constantly struggles with the need to get a bit more data than previous consumers, or get that data in a slightly different way. You start to see a number of similar methods in the repository, or extra arguments in methods, or very ugly patterns like attempts to send in expression trees or "include" string lists into methods to attempt to abstract what EF can do for you.

Overall while these dependencies might look simple individually, the code needed to coordinate and test application logic becomes very bulky and complex.

For me, repositories serve three key purposes:

  • To make application logic easier to test. (Abstract the DB, not EF)
  • To serve as a factory to ensure that entities are initialized correctly.
  • To centralize low-level filters and functions. (IsActive, authorization, multi-tenant, soft-delete, etc.)

You could argue that the "factory" responsibility should fall on a separate class which has dependencies on a repository or repositories. From my perspective the repository has access to the information it needs from the DbContext so it keeps it simpler. That said, my repositories are not generic classes, but instead manage a vertical similar to how I structure controllers. A repository serves the needs of a controller, being the needs of a key area of the application. I may have additional repositories to serve other centralized concerns that are shared. (Lookups, authentication, etc.) This helps ensure that repositories make my code easy to test, and easy to modify as the implementation of the repository serves one purpose. Changes to other areas of the application that may be interested in Orders don't impact my OrderManagementRepository which serves an OrderManagementController for example.

The repository facilitates, or mediates interations with EF, not isolates.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM