Book Image

Entity Framework Core Cookbook - Second Edition

By : Ricardo Peres
Book Image

Entity Framework Core Cookbook - Second Edition

By: Ricardo Peres

Overview of this book

Entity Framework is a highly recommended Object Relation Mapping tool used to build complex systems. In order to survive in this growing market, the knowledge of a framework that helps provide easy access to databases, that is, Entity Framework has become a necessity. This book will provide .NET developers with this knowledge and guide them through working efficiently with data using Entity Framework Core. You will start off by learning how to efficiently use Entity Framework in practical situations. You will gain a deep understanding of mapping properties and find out how to handle validation in Entity Framework. The book will then explain how to work with transactions and stored procedures along with improving Entity Framework using query libraries. Moving on, you will learn to improve complex query scenarios and implement transaction and concurrency control. You will then be taught to improve and develop Entity Framework in complex business scenarios. With the concluding chapter on performance and scalability, this book will get you ready to use Entity Framework proficiently.
Table of Contents (15 chapters)
Entity Framework Core Cookbook - Second Edition
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Implementing the unit of work pattern


In the next example, we present an implementation of the Unit of Work pattern. This pattern was introduced by Martin Fowler, and you can read about it at http://martinfowler.com/eaaCatalog/unitOfWork.html. Basically, this pattern states that we keep track of all entities that are affected by a business transaction and send them all at once to the database, sorting out the ordering of the changes to apply—inserts before updates, and so on.

Getting ready

We will be using the NuGet Package Manager to install the Entity Framework Core 1 package, Microsoft.EntityFrameworkCore. We will also be using a SQL Server database for storing the data, so we will also need Microsoft.EntityFrameworkCore.SqlServer.

To mock interfaces and base classes, we will use Moq.

Finally, xunit is the package we will be using for the unit tests and dotnet-text-xunit adds tooling support for Visual Studio. Note that the UnitTests project is a .NET Core App 1.0 (netcoreapp1.0), that Microsoft.EntityFrameworkCore.Design is configured as a build dependency, and Microsoft.EntityFrameworkCore.Tools is set as a tool.

Open Using EF Core Solution from the included source code examples.

Execute the database setup script from the code samples included for this recipe. This can be found in the DataAccess project within the Database folder.

How to do it…

  1. First, we start by adding a new unit test in the UnitTests project to define the tests for using a unit of work pattern with the following code:

    using System;
    using System.Linq;
    using BusinessLogic;
    using DataAccess;
    using Moq;
    using Xunit;
    namespace UnitTests
    {
        public class UnitOfWorkTest : BaseTest
        {
            [Fact]
            public void ShouldReadToDatabaseOnRead()
            {
                //Arrange
                var findCalled = false;
                var mock = new Mock<IDbContext>();
                mock.Setup(x => x.Set<Blog>()).Callback(() => findCalled = true);
                var context = mock.Object;
                var unitOfWork = new UnitOfWork(context);
                var repository = new BlogRepository(context);
                //Act
                var blogs = repository.Set();
                //Assert
                Assert.True(findCalled);
            }
            [Fact]
            public void ShouldNotCommitToDatabaseOnDataChange()
            {
                //Arrange
                var saveChangesCalled = false;
                var data = new[] { new Blog() { Id = 1, Title = "Test" }              }.AsQueryable();
                var mock = new Mock<IDbContext>();
                mock.Setup(x => x.Set<Blog>()).Returns(data);
                mock.Setup(x => x.SaveChanges()).Callback(() => saveChangesCalled = true);
                var context = mock.Object;
                var unitOfWork = new UnitOfWork(context);
                var repository = new BlogRepository(context);
                //Act
                var blogs = repository.Set();
                blogs.First().Title = "Not Going to be Written";
                //Assert
                Assert.False(saveChangesCalled);
            }
            [Fact]
            public void ShouldPullDatabaseValuesOnARollBack()
            {
                //Arrange
                var saveChangesCalled = false;
                var rollbackCalled = false;
                var data = new[] { new Blog() { Id = 1, Title = "Test" } }.AsQueryable();
                var mock = new Mock<IDbContext>();
                mock.Setup(x => x.Set<Blog>()).Returns(data);
                mock.Setup(x => x.SaveChanges()).Callback(() => saveChangesCalled = true);
                mock.Setup(x => x.Rollback()).Callback(() => rollbackCalled = true);
                var context = mock.Object;
                var unitOfWork = new UnitOfWork(context);
                var repository = new BlogRepository(context);
                //Act
                var blogs = repository.Set();
                blogs.First().Title = "Not Going to be Written";
                repository.RollbackChanges();
                //Assert
                Assert.False(saveChangesCalled);
                Assert.True(rollbackCalled);
            }
            [Fact]
            public void ShouldCommitToDatabaseOnSaveCall()
            {
                //Arrange
                var saveChangesCalled = false;
                var data = new[] { new Blog() { Id = 1, Title = "Test" } }.AsQueryable();
                var mock = new Mock<IDbContext>();
                mock.Setup(x => x.Set<Blog>()).Returns(data);
                 mock.Setup(x => x.SaveChanges()).Callback(() => saveChangesCalled = true);
                var context = mock.Object;
                var unitOfWork = new UnitOfWork(context);
                var repository = new BlogRepository(context);
                //Act
                var blogs = repository.Set();
                blogs.First().Title = "Going to be Written";
                repository.SaveChanges();
                //Assert
                Assert.True(saveChangesCalled);
            }
            [Fact]
            public void ShouldNotCommitOnError()
            {
                //Arrange
                var rollbackCalled = false;
                var data = new[] { new Blog() { Id = 1, Title = "Test" } }.AsQueryable();
                var mock = new Mock<IDbContext>();
                mock.Setup(x => x.Set<Blog>()).Returns(data);
                mock.Setup(x => x.SaveChanges()).Throws(new Exception());
                mock.Setup(x => x.Rollback()).Callback(() => rollbackCalled = true);
                var context = mock.Object;
                var unitOfWork = new UnitOfWork(context);
                var repository = new BlogRepository(context);
                //Act
                var blogs = repository.Set();
                blogs.First().Title = "Not Going to be Written";
                try
                {
                    repository.SaveChanges();
                }
                catch
                {
                }
                //Assert
                Assert.True(rollbackCalled);
            }
        }
    }
  2. In the DataAccess project, create a new C# class named BlogContext with the following code:

    using BusinessLogic;
    using System.Linq;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.EntityFrameworkCore.Metadata.Internal;
    namespace DataAccess
    {
        public class BlogContext : DbContext, IDbContext
        {
            private readonly string _connectionString;
          
            public BlogContext(string connectionString)
            {
                _connectionString = connectionString;
            }
            public DbSet<Blog> Blogs { get; set; }
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(_connectionString);
                base.OnConfiguring(optionsBuilder);
            }
            public void Rollback()
            {
                ChangeTracker.Entries().ToList().ForEach(x =>
                {
                    x.State = EntityState.Detached;
                    var keys = GetEntityKey(x.Entity);
                    Set(x.Entity.GetType(), keys);
                });
            }
            
            public DbSet<T> Set<T>() where T : class
            {
                return Set<T>();
            }
            
            public object[] GetEntityKey<T>(T entity) where T : class
            {
                var state = Entry(entity);
                var metadata = state.Metadata;
                var key = metadata.FindPrimaryKey();
                var props = key.Properties.ToArray();
                return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();
            }
        }
    }
  3. In the DataAccess project, create a new C# interface called IDbContext with the following code:

    using System.Linq;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.ChangeTracking;
    namespace DataAccess
    {
        public interface IDbContext
        {
            ChangeTracker ChangeTracker { get; }
            DbSet<T> Set<T>() where T : class;
            IQueryable<T> Set<T>() where T : class;
            EntityEntry<T> Entry<T>(T entity) where T : class;
            int SaveChanges();
            void Rollback();
        }
    }
  4. In the DataAccess project, create a new C# interface called IUnitOfWork with the following code:

    namespace DataAccess
    {
      public interface IUnitOfWork
      {
        void RegisterNew<T>(T entity) where T : class;
        void RegisterUnchanged<T>(T entity) where T : class;
        void RegisterChanged<T>(T entity) where T : class;
        void RegisterDeleted<T>(T entity) where T : class;
        void Refresh();
        void Commit();
        IDbContext Context { get; }
      }
    }
  5. In the DataAccess project, add a new C# class named UnitOfWork with the following code:

    using Microsoft.EntityFrameworkCore;
    namespace DataAccess
    {
      public class UnitOfWork : IUnitOfWork
      {
        public IDbContext Context { get; private set; }
        public UnitOfWork(IDbContext context)
        {
          Context = context;
        }
        public void RegisterNew<T>(T entity) where T : class
        {
          Context.Set<T>().Add(entity);
        }
        public void RegisterUnchanged<T>(T entity) where T : class
        {
          Context.Entry(entity).State = EntityState.Unchanged;
        }
        public void RegisterChanged<T>(T entity) where T : class
        {
          Context.Entry(entity).State = EntityState.Modified;
        }
        public void RegisterDeleted<T>(T entity) where T : class
        {
          Context.Set<T>().Remove(entity);
        }
        public void Refresh()
        {
          Context.Rollback();
        }
        public void Commit()
        {
          Context.SaveChanges();
        }
      }
    }
  6. Create a new C# file in the DataAccess project with this content:

    using System.Linq;
    namespace DataAccess
    {
        public interface IRepository<out T> where T : class
        {
            IQueryable<T> Set();
            void RollbackChanges();
            void SaveChanges();
        }
    }
  7. Also in the DataAccess project, add a new C# interface named IBlogRepository with the following code:

    using System.Linq;
    namespace DataAccess
    {
      public interface IBlogRepository : IRepository<Blog>
      {
      }
    }
  8. In the DataAccess project, create a new C# class named BlogRepository containing the following code:

    using System.Linq;
    using BusinessLogic;
    namespace DataAccess
    {
      public class BlogRepository : IBlogRepository
      {
        private readonly IUnitOfWork _unitOfWork;
        public BlogRepository(IUnitOfWork unitOfWork)
        {
          _unitOfWork = unitOfWork;
        }
        public IQueryable<Blog> Set
        {
          return _unitOfWork.Context.Set<Blog>();
        }
        public void RollbackChanges()
        {
          _unitOfWork.Refresh();
        }
        public void SaveChanges()
        {
          try
          {
            _unitOfWork.Commit();
          }
          catch (Exception)
          {
            _unitOfWork.Refresh();
            throw;
          }
        }
      }
    }
  9. In BlogController, update BlogContext to use IBlogRepository with the following code:

    using BusinessLogic;
    using System.Linq;
    using DataAccess;
    using Microsoft.AspNet.Mvc;
    using Microsoft.Extensions.Configuration;
    namespace UI.Controllers
    {
      public class BlogController : Controller
      {
        private IBlogRepository _repository;
        public BlogController(IBlogRepository repository)
        {
          _repository = repository;
        }
        //
        // GET: /Blog/
        public IActionResult Index()
        {
          var blog = _repository.Set().First();
          return View(blog);
        }
      }
    }
  10. Finally, register the IUnitOfWork interface in the Startup.cs file, in the ConfigureServices method:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
           services.AddSingleton<IConfiguration>(_ => Configuration);
           services.AddScoped<IDbContext>(_ => new BlogContext(Configuration["Data:Blog:ConnectionString"]));
           services.AddScoped<IBlogRepository>(_ => new BlogRepository(_.GetService<IDbContext>()));
           services.AddScoped<IUnitOfWork>(_ => new UnitOfWork(_.GetService<IDbContext>()));
    }

How it works…

The tests set up the scenarios in which we would want to use a unit of work pattern: reading, updating, rolling back, and committing. The key to this is that these are all separate actions, not dependent on anything before or after them. If the application is web-based, this gives you a powerful tool to tie to the HTTP request so any unfinished work is cleaned up, or to ensure that you do not need to call SaveChanges, since it can happen automatically.

The unit of work was originally created to track the changes made so they could be persisted, and it functions the same way now. We are using a more powerful, but less recognized, feature defining the scope of the unit of work. We gave the ability to control both scope and the changes that are committed in the database in this scenario. We have also put in some clean-up, which will ensure that even in the event of a failure, our unit of work will try to clean up after itself before throwing the error to be handled at a higher level. We do not want to ignore these errors, but we do want to make sure they do not destroy the integrity of our database.

In addition to this tight encapsulation of work against the database, pass in our unit of work to each repository. This enables us to couple multiple object interactions to a single unit of work. This will allow us to write code that's specific to the object, without giving up the shared feature set of the database context. This is an explicit unit of work, but Entity Framework, in the context, defines it to give you an implicit unit of work. If you want to tie this to the HTTP request, rollback on error, or tie multiple data connections together in new and interesting ways, then you will need to code in an explicit implementation such as this one.

This basic pattern will help to streamline data access, and resolve the concurrency issues caused by conflicts in the objects that are affected by a transaction.

There's more…

The unit of work is a concept that is deep at the heart of Entity Framework and adheres, out of the box, to the principles following it. Knowing these principles, and why they are leveraged, will help us use Entity Framework to its fullest without running into the walls built in the system on purpose.

Call per change

There is a cost for every connection to the database. If we were to make a call to keep the state in the database in sync with the state in the application, we would have thousands of calls, each with connection, security, and network overhead. Limiting the number of times that we hit the database not only allows us to control this overhead, but also allows the database software to handle the larger transactions for which it was built.

Interface Segregation Principle

Some might be inclined to ask why we should separate unit of work from the repository pattern. Unit of work is definitely a separate responsibility from repository, and as such it is important to not only define separate classes, but also to ensure that we keep small, clear interfaces. The IDbContext interface is specific in the area of dealing with database connections through an Entity Framework object context. This allows the mocking of a context to give us testability to the lowest possible level.

The IUnitOfWork interface deals with the segregation of work, and ensures that the database persistence happens only when we intend it to, ignorant of the layer under it that does the actual commands. The IRepository interface deals with selecting objects back from any type of storage, and allows us to remove all thoughts of how the database interaction happens from our dependent code base. These three objects, while related in layers, are separate concerns, and therefore need to be separate interfaces.

Refactoring

We have added IUnitOfWork to our layered approach to database communication, and if we have seen anything over the hours of coding, it is code changes. We change it for many reasons, but the bottom line is that code changes often, and we need to make it easy to change. The layers of abstraction that we have added to this solution with IRepository, IUnitOfWork, and IDbContext, have all given us a point at which the change would be minimally painful, and we can leverage the interfaces in the same way. This refactoring to add abstraction levels is a core tenet of clean, extensible code. Removing the concrete implementation details from related objects, and coding to an interface, forces us to encapsulate behavior and abstract our sections of code.

See also

In this chapter:

  • Implementing the repository pattern