views:

98

answers:

3

I have a repository like so:

public interface IRepository
{
    void Save<T>(T entity);
    void Create<T>(T entity);
    void Update<T>(T entity);
    void Delete<T>(T entity);
    IQueryable<T> GetAll<T>();
}

My question is, where should my transaction boundaries be? Should I open a new transaction on every method and commit it before returning? Or should it be around the entire repository so that the transaction is only committed when the repository is disposed/garbage collected?

+1  A: 

Transactions should not be applied to repositories, because if more than one needs to participate in a transaction there's no way to say it.

Have a separate service tier that uses repositories and model objects to satisfy use cases. The service methods know where the transaction boundaries need to be. That's where they should be applied.

duffymo
A service tier would be nice, but overkill for the app I'm designing. I just want the repository to act like an in-memory collection, so that's why I exposed IQueryable.
Daniel T.
A: 

One of the options is the unit of work pattern. This is already explained in several previous questions.

Paco
+1  A: 

Unit of Work is definitely the way to go. If you're using SQL Server with a local database, TransactionScope will do most of the heavy lifting for you; as long as you share sessions between repositories (which you're doing through constructor injection, right...?), then you can nest these to your heart's content. By default, it enlists in the "ambient" transaction if there is one, and starts a new one if there isn't, which is exactly the behaviour you want for a unit of work.

So your repositories might look like this:

public class UserRepository : IUserRepository
{
    public UserRepository(ISession session)
    {
        this.Session = session;
    }

    public void Save(User user)
    {
        using (TransactionScope tsc = new TransactionScope())
        {
            Session.Save(user);
            tsc.Complete();
        }
    }

    protected ISession Session { get; set; }
}

public class OrderRepository : IOrderRepository
{
    public OrderRepository(ISession session)
    {
        this.Session = session;
    }

    public void Save(Order order)
    {
        using (TransactionScope tsc = new TransactionScope())
        {
            Session.Save(order);
            tsc.Complete();
        }
    }

    protected ISession Session { get; set; }
}

Then you can perform the complete unit of work like so:

User currentUser = GetCurrentUser();
using (TransactionScope tsc = new TransactionScope())
{
    ISession session = SessionFactory.OpenSession();

    Order order = new Order(...);
    order.User = currentUser;
    IOrderRepository orderRepository = GetOrderRepository(session);
    orderRepository.Save(order);

    currentUser.LastOrderDate = DateTime.Now;
    IUserRepository userRepository = GetUserRepository(session);
    userRepository.Save(currentUser);

    tsc.Complete();
}

If you don't like TransactionScope or your environment prevents you from using it effectively then you can always implement your own UOW or use an existing implementation. Or if you're just an architectural neat-freak then you can do both - use a generic unit-of-work interface with one of the main DI libraries, and implement your concrete UOW using the TransactionScope.

Aaronaught
Would this be an example of the unit of work pattern?
Daniel T.
`TransactionScope` is, fundamentally, a UOW implementation, so yes; if you want a "true" UOW then you should use an interface instead, along with a factory or IoC container, and wrap the `TransactionScope` in one implementation. But if your target will always be SQL Server then a `TransactionScope` by itself is usually good enough.
Aaronaught
Unit of work looks like it's a synonym for what I'm calling service, so I don't see why it's overkill and UOW is not. Can someone explain how it's fundamentally different? Don't confuse "service" with "web service" or WSDL.
duffymo
@duffymo: Was this directed at me? I think maybe it was intended as a response to the comment on your answer?
Aaronaught