3

Lets say I have 2 tables. ProductCategory and Product. I have 1 generic repository that can handle both tables:

public class GenericRepository<T> : IRepository<T>

But when using unit of work pattern, am I forced to create a repository for ALL tables in my database?

public interface IUnitOfWork : IDisposable
{
    int SaveChanges();

    IRepository<ProductCategory> ProductCategoryRepository { get; }
    IRepository<Product> ProductRepository { get; }
}

Is there not a way I can add the generic repository to the unit of work class?

Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406
  • So you want to use the unit of work pattern, but don't want to add the repositories to the unit of work? Hmmm... don't think so. But if the problem is the amount of changes you need to make for every new entity to the system, take a look at [this article](http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=84). – Steven Mar 21 '12 at 16:27
  • @Steven so you're saying it's ok to create a repository for every new entity? – Shawn Mclean Mar 21 '12 at 16:31
  • 1
    This is why I'm against generic repositories, they serve the database instead of the application. If you need to save a product, you have product repository with a method 'Save(Product p)' and let the repo handle it from there. ProductCategories, UoW that the repo's business. The app only knows about the repo abstraction, not EF, nhibernate, tables and other persistence details. – MikeSW Mar 21 '12 at 18:29

3 Answers3

5

You can add a generic method to the IUnitOfWork interface:

public interface IUnitOfWork : IDisposable
{
    int SaveChanges();

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

But i don't recommend it. It's smells like Service Locator anti-pattern and SRP violation. Better way is to remove all repositories from the IUnitOfWork interface, because providing access to repository is not UnitOfWork's responsibility. I recommend to separate repository from UnitOfWork and inject their into the consumer by itself.

public class Consumer
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IRepository<Product> _products;

    public Consumer(IUnitOfWork unitOfWork, IRepository<Product> products)
    {
        _unitOfWork = unitOfWork;
        _products = products;
    }

    public void Action()
    {
        var product = _products.GetOne();

        product.Name = "new name";
        _products.Update(product);

        _unitOfWork.SaveChanges();
    }
}

UDATE:

UnitOfWork and Repository can share context instance. Here the sample of code:

public class EfUnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;

    public EfUnitOfWork(DbContext context)
    {
        _context = context;
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }
}

public class EfRepository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;

    public EfRepository(DbContext context)
    {
        _context = context;
    }

    //... repository methods...
}

public class Program
{
    public static void Main()
    {
        //poor man's dependency injection
        var connectionString = "northwind";

        var context = new DbContext(connectionString);
        var unitOfWork = new EfUnitOfWork(context);
        var repository = new EfRepository<Product>(context);
        var consumer = new Consumer(unitOfWork, repository);
        consumer.Action();
    }
}
Dmitriy Startsev
  • 1,442
  • 10
  • 19
1

Demonstrating a solution with only one class would be

public class Session : ISession
{
    private readonly DbContext _dbContext;
    public Session(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> expression) where TEntity : class
    {
        return _dbContext.Set<TEntity>().SingleOrDefault(expression);
    }

    public IQueryable<TEntity> Query<TEntity>() where TEntity : class
    {
        return _dbContext.Set<TEntity>().AsQueryable();
    }

    public void Commit()
    {
        try { _dbContext.SaveChanges(); }
        catch (DbEntityValidationException ex)
        {
            var m = ex.ToFriendlyMessage();
            throw new DbEntityValidationException(m);
        }
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }

    public void Add<TEntity>(IEnumerable<TEntity> items) where TEntity : class
    {
        items.ToList().ForEach(Add);
    }

    public void Add<TEntity>(TEntity item) where TEntity : class
    {
        _dbContext.Set<TEntity>().Add(item);
    }

    public void Remove<TEntity>(TEntity item) where TEntity : class
    {
        _dbContext.Set<TEntity>().Remove(item);
    }

    public void Remove<TEntity>(Expression<Func<TEntity, bool>> expression) where TEntity : class
    {
        var items = Query<TEntity>().Where(expression);
        Remove<TEntity>(items);
    }

    public void Remove<TEntity>(IEnumerable<TEntity> items) where TEntity : class
    {
        items.ToList().ForEach(Remove);
    }
}

and then your usage can be

public class User
{
    public int? Id { get; set; }
    public string Name { get; set; }
    public DateTime Dob { get; set; }
}
public class Usage
{
    private readonly ISession _session;
    public Usage(ISession session) { _session = session; }

    public void Create(User user)
    {
        _session.Add(user);
        _session.Commit();
    }
    public void Update(User user)
    {
        var existing = _session.Single<User>(x => x.Id == user.Id);

        // this gets cumbursome for an entity with many properties. 
        // I would use some thing like valueinjecter (nuget package)
        // to inject the existing customer values into the one retreived from the Db.
        existing.Name = user.Name;
        existing.Dob = user.Dob;

        _session.Commit();
    }
}

I have deliberately not included a Repository class. To have a class encapsulate both queries and commands for every entity is an over kill and a needless abstraction. Its almost a design flaw at a fundamental level. Queries and commands are fundamentally different concerns. Queries in the most simplest manner can be created as extensions methods on the ISession interface. Commands can be done using a few classes like such..

public interface ICommand<in TSource>
{
    void ApplyTo(TSource source);
}
public interface ICommandHandler<out TSource>
{
    void Handle(ICommand<TSource> command);
}
public class LinqCommandHandler : ICommandHandler<IStore>
{
    private readonly ISession _session;

    public LinqCommandHandler(ISession session)
    {
        _session = session;
    }
    public void Handle(ICommand<IStore> command)
    {
        command.ApplyTo(_session);
        _session.Commit();
    }
}
public class UpdateDobForUserName : ICommand<IStore>
{
    public string UserName { get; set; }
    public DateTime Dob { get; set; }
    public void OnSend(IStore store)
    {
        var existing = store.Query<User>().SingleOrDefault(x => x.Name == UserName);
        existing.Dob = Dob;
    }
}

public class Usage
{
    private readonly ICommandHandler<IStore> _commandHandler;

    public Usage(ICommandHandler<IStore> commandHandler)
    {
        _commandHandler = commandHandler;
    }

    public void Update()
    {
        var command = new UpdateDobForUserName {UserName = "mary", Dob = new DateTime(1960, 10, 2)};
        _commandHandler.Handle(command);
    }
}

The IStore above is the same as the Session class, except that it doesn't implement the IDisposable interface and doesn't have a Commit() method. The ISession then obviously inherits from an IStore and also implements IDisposable and has one method Commit(). This ensures an ICommand<IStore> can never open or dispose connections and cannot commit. Its responsibility is to define a command and define how its applied. Who applies it and what happens and what not on command application is a different responsibility which is with the ICommandHandler<IStore>.

afif
  • 460
  • 3
  • 14
  • Oren Eini has spoken at length about the evils of the Repository pattern. Check out http://ayende.com/blog/153701/ask-ayende-life-without-repositories-are-they-worth-living for starters – afif Mar 27 '12 at 04:22
0

There are many ways to implement Unit of work. I prefer having the repositories take a Unit of Work in its constructor (which is passed via Dependency Injection), then you only create repositories for your needs.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • @Mystere Man I would also like a code sample. I have been looking at UnitOfWork and I have been hating because I think it turns your UnitOfWork into a glorified service locator. Uow.UserRepo, Uow.EmployeeRepo, Uow.ManagerRepo, Uow.DepartmentRepo just seems about the stupidest thing I could do. I would love to see what you had in mind. – uriDium Sep 26 '13 at 09:41
  • @uriDium - here's an example, maybe not the best, but the first I found.. http://elegantcode.com/2009/12/15/entity-framework-ef4-generic-repository-and-unit-of-work-prototype/ – Erik Funkenbusch Sep 26 '13 at 15:22