Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implement a transaction for my repositories with Entity Framework?

I am trying to utilize the repository design pattern in my application for 2 reasons

  1. I like to de-couple my application from Entity in case I decide to not use Entity Framework at some point

  2. I want to be able reuse the logic that interacts with the model

I successfully setup and used the repository pattern. However, I have one complexity to deal with which is a transaction.

I want to be able to use transaction so that I can make multiple calls to the repository and then commit or rollback.

Here is my repository interface

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Support.Repositories.Contracts
{
    public interface IRepository<TModel> where TModel : class
    {
        // Get records by it's primary key
        TModel Get(int id);

        // Get all records
        IEnumerable<TModel> GetAll();

        // Get all records matching a lambda expression
        IEnumerable<TModel> Find(Expression<Func<TModel, bool>> predicate);

        // Get the a single matching record or null
        TModel SingleOrDefault(Expression<Func<TModel, bool>> predicate);

        // Add single record
        void Add(TModel entity);

        // Add multiple records
        void AddRange(IEnumerable<TModel> entities);

        // Remove records
        void Remove(TModel entity);

        // remove multiple records
        void RemoveRange(IEnumerable<TModel> entities);
    }
}

Then I create an implementation for Entity Framework like so

using Support.Repositories.Contracts;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Support.Repositories
{
    public class EntityRepository<TEntity> : IRepository<TEntity>
        where TEntity : class
    {
        protected readonly DbContext Context;
        protected readonly DbSet<TEntity> DbSet;

        public EntityRepository(DbContext context)
        {
            Context = context;
            DbSet = context.Set<TEntity>();
        }

        public TEntity Get(int id)
        {
            return DbSet.Find(id);
        }

        public IEnumerable<TEntity> GetAll()
        {
            return DbSet.ToList();
        }

        public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
            return DbSet.Where(predicate);
        }

        public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return DbSet.SingleOrDefault(predicate);
        }

        public void Add(TEntity entity)
        {
            DbSet.Add(entity);
        }

        public void AddRange(IEnumerable<TEntity> entities)
        {
            DbSet.AddRange(entities);
        }

        public void Remove(TEntity entity)
        {
            DbSet.Remove(entity);
        }

        public void RemoveRange(IEnumerable<TEntity> entities)
        {
            DbSet.RemoveRange(entities);
        }

    }
}

Now, I create a IUnitOfWork to interact with repository like so

using System;

namespace App.Repositories.Contracts
{
    public interface IUnitOfWork : IDisposable
    {
        IUserRepository Users { get; }
        IAddressRepository Addresses { get;  }
    }
}

Then I implemented this interface for Entity Framework like this:

using App.Contexts;
using App.Repositories.Contracts;
using App.Repositories.Entity;

namespace App.Repositories
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly AppContext _context;
        public IUserRepository  Users { get; private set; }
        public IAddressRepository Addresses { get; private set; }

        public UnitOfWork(AppContext context)
        {
            _context = context;

            Users = new UserRepository(_context);
            Addresses = new AddressRepository(_context);
        }

        public UnitOfWork() : this(new AppContext())
        {
        }

        public int Save()
        {
            return _context.SaveChanges();
        }

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

I am able to use the repository like this

using(var repository = new UnitOfWork())
{
     repository.Users.Add(new User(... User One ...))
     repository.Save();

     repository.Addresses(new Address(... Address For User One ...))
     repository.Save();

     repository.Users.Add(new User(... User Two...))
     repository.Save();

     repository.Addresses(new Address(... Address For User Two...))
     repository.Save();
}

Now, I want to be able to use database transaction so only when everything is good then commit otherwise rollback.

My first take is to add a new method called BeginTransaction() to my UnitOfWork class. But will couple my code to Entity Framework only.

Now, I am thinking to create a new interface that provides BeginTransaction(), Commit() and Rollback() method which will allow me to write an implementation for any ORM.

i.e.

namespace Support.Contracts
{
    public IRepositoryDatabase
    {
        SomethingToReturn BeginTransaction();

        void Commit();
        void Rollback();
    }
}

The question is how would I tie IRepositoryDatabase back to my UnitOfWork so I can implement correctly? And what would BeginTransaction() needs to return?

like image 208
Junior Avatar asked Oct 06 '16 22:10

Junior


People also ask

How are transactions implemented in Entity Framework?

In Entity Framework, the SaveChanges() method internally creates a transaction and wraps all INSERT, UPDATE and DELETE operations under it. Multiple SaveChanges() calls, create separate transactions, perform CRUD operations and then commit each transaction.

Does Entity Framework support transactions?

In all versions of Entity Framework, whenever you execute SaveChanges() to insert, update or delete on the database the framework will wrap that operation in a transaction. This transaction lasts only long enough to execute the operation and then completes.

Does Entity Framework implement repository pattern?

The Entity Framework DbContext class is based on the Unit of Work and Repository patterns and can be used directly from your code, such as from an ASP.NET Core MVC controller.

What is the right way to include a repository into a controller?

By taking advantage of dependency injection (DI), repositories can be injected into a controller's constructor. the following diagram shows the relationship between the repository and Entity Framework data context, in which MVC controllers interact with the repository rather than directly with Entity Framework.


3 Answers

I think I figured out the way to do it. (I hope I did it the right way)

Here is what I have done, I hope this helps someone looking to do the same thing.

I created a new Interface like so

using System;

    namespace Support.Repositories.Contracts
    {
        public interface IDatabaseTransaction : IDisposable
        {
            void Commit();
    
            void Rollback();
        }
    }

Then I implemented IDatabaseTransaction for Entity framework like so

using Support.Repositories.Contracts;
using System.Data.Entity;

namespace Support.Entity.Repositories
{
    public class EntityDatabaseTransaction : IDatabaseTransaction
    {
        private DbContextTransaction _transaction;

        public EntityDatabaseTransaction(DbContext context)
        {
            _transaction = context.Database.BeginTransaction();
        }

        public void Commit()
        {
            _transaction.Commit();
        }

        public void Rollback()
        {
            _transaction.Rollback();
        }

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

Then, I added a new method called BeginTransaction() to my IUnitOfWork contract like so

using System;

namespace App.Repositories.Contracts
{
    public interface IUnitOfWork : IDisposable
    {
        IDatabaseTransaction BeginTransaction();
        IUserRepository Users { get; }
        IAddressRepository Addresses { get;  }
    }
}

Finally, following is my UnitOfwork implementation for Entity

using App.Contexts;
using App.Repositories.Contracts;
using App.Repositories.Entity;
using Support.Repositories;


namespace App.Repositories
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly AppContext _context;
        public IUserRepository  Users { get; private set; }
        public IAddressRepository Addresses { get; private set; }

        public UnitOfWork(AppContext context)
        {
            _context = context;

            Users = new UserRepository(_context);
            Addresses = new AddressRepository(_context);
        }

        public UnitOfWork() : this(new AppContext())
        {
        }

        public int Save()
        {
            return _context.SaveChanges();
        }

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

        public IDatabaseTransaction BeginTransaction()
        {
            return new EntityDatabaseTransaction(_context);
        }
    }
}

And here is how I consume the UnitOfWork implementation from my controller

using(var unitOfWork = new UnitOfWork())
using(var transaction = new unitOfWork.BeginTransaction())
{
     try
     {
         unitOfWork.Users.Add(new User(... User One ...))
         unitOfWork.Save();

         unitOfWork.Addresses(new Address(... Address For User One ...))
         unitOfWork.Save();

         unitOfWork.Users.Add(new User(... User Two...))
         unitOfWork.Save();

         unitOfWork.Addresses(new Address(... Address For User Two...))
         unitOfWork.Save();
         transaction.Commit();
     } 
     catch(Exception)
     {
          transaction.Rollback();
     }

}
like image 142
Junior Avatar answered Oct 06 '22 22:10

Junior


In EF Core, Although UnitOfWork pattern is implemented internally, you can simply use IDbContextTransaction interface as follow (supposing that you use Dependency Injection):

public interface IUnitOfWork 
{
    int SaveChanges();
    Task<int> SaveChangesAsync();
    IDbContextTransaction BeginTransaction();
    Task<IDbContextTransaction> BeginTransactionAsync();
    
    IUserRepository Users { get; }
    IAddressRepository Addresses { get;  }
}

and the implementation:

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private bool _disposed;
    private readonly AppDbContext _context;

    public UnitOfWork(AppDbContext context, 
                 IUserRepository userRepositpry, IAddressRepository addressRepository)
    {
        _context = context;
        Users = userRepositpry;
        Addresses = addressRepository;
    }

    public IUserRepository Users { get; }
    public IAddressRepository Addresses { get; }

    public int SaveChanges()
    {
        return _context.SaveChanges();
    }

    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public IDbContextTransaction BeginTransaction()
    {
        return _context.Database.BeginTransaction();
    }

    public async Task<IDbContextTransaction> BeginTransactionAsync()
    {
        return await _context.Database.BeginTransactionAsync();
    }

    protected void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        this._disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Usage:

 public class FooService
{
    private readonly IUnitOfWork _unitOfWork;
    public FooService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void Bar()
    {
        using (var transaction = _unitOfWork.BeginTransaction())
        {
            try
            {
                _unitOfWork.Users.Add(new UserModel("dummy username"));
                _unitOfWork.SaveChanges();
                _unitOfWork.Addresses.Add(new AddressModel("dummy address"));
                _unitOfWork.SaveChanges();

                transaction.Commit();
            }
            catch (Exception)
            {
                transaction.Rollback();
            }
        }
    }
}
like image 39
Efe Avatar answered Oct 06 '22 22:10

Efe


While comments by Sir Rufo are correct, you did said that wanted an EF independent solution and although usually abstracting away from the ORM is an overkill, if you are still set on handling the transaction yourself you can use TransactionScope (which was apparently the way to achieve control over the transaction before having BeginTransaction in the context.Database).

Please see the following article for details: https://msdn.microsoft.com/en-us/data/dn456843.aspx

Relevant bits are that you can enclose all the calls in a TransactionScope (this will actually work out of the box in other ORMs as well):

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 

namespace TransactionsExamples 
{ 
    class TransactionsExample 
    { 
        static void UsingTransactionScope() 
        { 
            using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
            { 
                using (var conn = new SqlConnection("...")) 
                { 
                    conn.Open(); 

                    var sqlCommand = new SqlCommand(); 
                    sqlCommand.Connection = conn; 
                    sqlCommand.CommandText = 
                        @"UPDATE Blogs SET Rating = 5" + 
                            " WHERE Name LIKE '%Entity Framework%'"; 
                    sqlCommand.ExecuteNonQuery(); 

                    using (var context = 
                        new BloggingContext(conn, contextOwnsConnection: false)) 
                    { 
                        var query = context.Posts.Where(p => p.Blog.Rating > 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
                        context.SaveChanges(); 
                    } 
                } 

                scope.Complete(); 
            } 
        } 
    } 
}

But do need to mind the following caveats:

There are still some limitations to the TransactionScope approach:

  • Requires .NET 4.5.1 or greater to work with asynchronous methods
  • It cannot be used in cloud scenarios unless you are sure you have one and only one connection (cloud scenarios do not support distributed transactions)
  • It cannot be combined with the Database.UseTransaction() approach of the previous sections
  • It will throw exceptions if you issue any DDL (e.g. because of a Database Initializer) and have not enabled distributed transactions through the MSDTC Service
like image 34
Fredy Treboux Avatar answered Oct 06 '22 20:10

Fredy Treboux