4

I'm working on a quite large application. The domain has about 20-30 types, implemented as ORM classes (for example EF Code First or XPO, doesn't matter for the question). I've read several articles and suggestions about a generic implementation of the repository pattern and combining it with the unit of work pattern, resulting a code something like this:

public interface IRepository<T> {
  IQueryable<T> AsQueryable();
  IEnumerable<T> GetAll(Expression<Func<T, bool>> filter);
  T GetByID(int id);

  T Create();
  void Save(T);
  void Delete(T);
}

public interface IMyUnitOfWork : IDisposable {
  void CommitChanges();
  void DropChanges();

  IRepository<Product> Products { get; }
  IRepository<Customer> Customers { get; }
}

Is this pattern suitable for really large applications? Every example has about 2, maximum 3 repositories in the unit of work. As far as I understood the pattern, at the end of the day the number of repository references (lazy initialized in the implementation) equal (or nearly equal) to the number of domain entity classes, so that one can use the unit of work for complex business logic implementation. So for example let's extend the above code like this:

public interface IMyUnitOfWork : IDisposable {
  ...

  IRepository<Customer> Customers { get; }
  IRepository<Product> Products { get; }
  IRepository<Orders> Orders { get; }

  IRepository<ProductCategory> ProductCategories { get; }
  IRepository<Tag> Tags { get; }

  IRepository<CustomerStatistics> CustomerStatistics  { get; }

  IRepository<User> Users { get; }
  IRepository<UserGroup> UserGroups { get; }
  IRepository<Event> Events { get; }

  ...   
}

How many repositories cab be referenced until one thinks about code smell? Or is it totally normal for this pattern? I could probably separate this interface into 2 or 3 different interfaces all implementing IUnitOfWork, but then the usage would be less comfortable.

UPDATE

I've checked a basically nice solution here recommended by @qujck. My problem with the dynamic repository registration and "dictionary based" approach is that I would like to enjoy the direct references to my repositories, because some of the repositories will have special behaviour. So when I write my business code I would like to be able to use it like this for example:

using (var uow = new MyUnitOfWork()) {
  var allowedUsers = uow.Users.GetUsersInRolw("myRole");
  // ... or
  var clothes = uow.Products.GetInCategories("scarf", "hat", "trousers");
}

So here I'm benefiting that I have a strongly typed IRepository and IRepository reference, hence I can use the special methods (implemented as extension methods or by inheriting from the base interface). If I use a dynamic repository registration and retrieval method, I think I'm gonna loose this, or at least have to do some ugly castings all the time.

For the matter of DI, I would try to inject a repository factory to my real unit of work, so it can lazily instantiate the repositories.

Community
  • 1
  • 1
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93

2 Answers2

3

Building on my comments above and on top of the answer here.

With a slightly modified unit of work abstraction

public interface IMyUnitOfWork
{
    void CommitChanges();
    void DropChanges();

    IRepository<T> Repository<T>();
}

You can expose named repositories and specific repository methods with extension methods

public static class MyRepositories
{
    public static IRepository<User> Users(this IMyUnitOfWork uow)
    {
        return uow.Repository<User>();
    }

    public static IRepository<Product> Products(this IMyUnitOfWork uow)
    {
        return uow.Repository<Product>();
    }

    public static IEnumerable<User> GetUsersInRole(
        this IRepository<User> users, string role)
    {
        return users.AsQueryable().Where(x => true).ToList();
    }

    public static IEnumerable<Product> GetInCategories(
        this IRepository<Product> products, params string[] categories)
    {
        return products.AsQueryable().Where(x => true).ToList();
    }
}

That provide access the data as required

using(var uow = new MyUnitOfWork())
{
    var allowedUsers = uow.Users().GetUsersInRole("myRole");

    var result = uow.Products().GetInCategories("scarf", "hat", "trousers");
}
Community
  • 1
  • 1
qujck
  • 14,388
  • 4
  • 45
  • 74
2

The way I tend to approach this is to move the type constraint from the repository class to the methods inside it. That means that instead of this:

public interface IMyUnitOfWork : IDisposable
{
    IRepository<Customer> Customers { get; }
    IRepository<Product> Products { get; }
    IRepository<Orders> Orders { get; }
    ...
}

I have something like this:

public interface IMyUnitOfWork : IDisposable
{
    Get<T>(/* some kind of filter expression in T */);
    Add<T>(T);
    Update<T>(T);
    Delete<T>(/* some kind of filter expression in T */);
    ...
}

The main benefit of this is that you only need one data access object on your unit of work. The downside is that you don't have type-specific methods like Products.GetInCategories() any more. This can be problematic, so my solution to this is usually one of two things.

Separation of concerns

First, you can rethink where the separation between "data access" and "business logic" lies, so that you have a logic-layer class ProductService that has a method GetInCategory() that can do this:

using (var uow = new MyUnitOfWork())
{
    var productsInCategory = GetAll<Product>(p => ["scarf", "hat", "trousers"].Contains(u.Category));
}

Your data access and business logic code is still separate.

Encapsulation of queries

Alternatively, you can implement a specification pattern, so you can have a namespace MyProject.Specifications in which there is a base class Specification<T> that has a filter expression somewhere internally, so that you can pass it to the unit of work object and that UoW can use the filter expression. This lets you have derived specifications, which you can pass around, and now you can write this:

using (var uow = new MyUnitOfWork())
{
    var searchCategories = new Specifications.Products.GetInCategories("scarf", "hat", "trousers");
    var productsInCategories = GetAll<Product>(searchCategories);
}

If you want a central place to keep commonly-used logic like "get user by role" or "get products in category", then instead of keeping it in your repository (which should be pure data access, strictly speaking) then you could have those extension methods on the objects themselves instead. For example, Product could have a method or an extension method InCategory(string) that returns a Specification<Product> or even just a filter such as Expression<Func<Product, bool>>, allowing you to write the query like this:

using (var uow = new MyUnitOfWork())
{
    var productsInCategory = GetAll(Product.InCategories("scarf", "hat", "trousers");
}

(Note that this is still a generic method, but type inference will take care of it for you.)

This keeps all the query logic on the object being queried (or on an extensions class for that object), which still keeps your data and logic code nicely separated by class and by file, whilst allowing you to share it as you have been sharing your IRepository<T> extensions previously.

Example

To give a more specific example, I'm using this pattern with EF. I didn't bother with specifications; I just have service classes in the logic layer that use a single unit of work for each logical operation ("add a new user", "get a category of products", "save changes to a product" etc). The core of it looks like this (implementations omitted for brevity and because they're pretty trivial):

public class EFUnitOfWork: IUnitOfWork
{
    private DbContext _db;

    public EntityFrameworkSourceAdapter(DbContext context) {...}

    public void Add<T>(T item) where T : class, new() {...}
    public void AddAll<T>(IEnumerable<T> items) where T : class, new() {...}

    public T Get<T>(Expression<Func<T, bool>> filter) where T : class, new() {...}
    public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> filter = null) where T : class, new() {...}

    public void Update<T>(T item) where T : class, new() {...}

    public void Remove<T>(Expression<Func<T, bool>> filter) where T : class, new() {...}

    public void Commit() {...}

    public void Dispose() {...}
}

Most of those methods use _db.Set<T>() to get the relevant DbSet, and then just query it with LINQ using the provided Expression<Func<T, bool>>.

Community
  • 1
  • 1
anaximander
  • 7,083
  • 3
  • 44
  • 62
  • Thanks for the answer. I was thinking about this approach (generic methods) but at the end I didn't like it because I wanted to separate the data access logic for each kind of entity. In my opinion my repositories can have some lower level business logic, like get all users in a role, or get all regions for a user where he has access rights, etc. I came to this conclusion when I realized that these methods are used widely in the upper level business layer. – Zoltán Tamási Jul 23 '14 at 13:04
  • Or better to say I would put those lower-level logics not INSIDE the repository class, but prefferably in extension methods of for example IRepository. However, I could add all of these logics as extensions to a non-generic IRepository as well (with generic methods). I'm still thinking about it. – Zoltán Tamási Jul 23 '14 at 13:14
  • In that case, you could have those extension methods on `User` instead of on `IRepository`. That would actually blend nicely with the specification pattern; I'll edit this in to my answer. – anaximander Jul 23 '14 at 13:30
  • That would be a good solution if these logics were as simple as forming some kind of criteria expression, but in my real app it's usually not the case. I still would prefer to put them as extensions to repositories, I don't think that would violate the data access responsibility of them as the methods don't belong to them phisically. I could write static helper methods for these as well, putting them as extensions would be just a syntactic sugar. I'm thinking a bit more and will give some feedback when I finally can decide the way to go. – Zoltán Tamási Jul 23 '14 at 13:47
  • Thanks, I like your solution more than others. If we state that the only task of repositories is to handle persistency and probably transactions (unit of work), then I think there's no reason not to make it non-generic as an interface and make its methods generic. I'll try to work on this path. My new problem is where to put lower-level, but business-related logics which are not fitting to repositories, still used commonly. Here is my new question :) [link](http://stackoverflow.com/questions/24913705/where-should-i-put-commonly-used-data-access-code-with-logic-not-fitting-to-repo) – Zoltán Tamási Jul 24 '14 at 18:17
  • Just as a feedback after 2.5 years, in my newer projects I use your first approach: having an `IRepository` with generic methods, and putting the entity-specific data access methods to each service class. If the domain is nicely spread out to service classes (seriously taking SRP into account), then it will work nicely and I didn't experience any serious drawbacks so far. – Zoltán Tamási Dec 17 '16 at 15:42