Increasing performance with code access

In this recipe, we will be creating a library which ensures that the queries which will be executed in the database are limited to the data access layer. It limits the queries by creating a clear boundary beyond which IQueryable<T> is not exposed, only allowing access to the business layer to an IEnumerable<T>. This ensures that the business layer cannot modify the queries in such a way as to make them non-performant, while still giving them complete access to the data.

Getting ready

We will be using the NuGet package manager to install the Entity Framework 4.1 assemblies.

The package installer can be found at http://nuget.org.

We will also be using a database for connecting to and updating data.

Open the Improving Performance with Code Access solution in the included source code examples.

How to do it...

Carry out the following steps in order to accomplish this recipe.

  1. We will start by adding a new unit test named QueryTests to the test project. We will make a test that connects to the database and retrieves a couple of records with a reusable query using the following code:
    using System.Linq;
    using BusinessLogic.Queries;
    using DataAccess;
    using DataAccess.Database;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    
    namespace Test
    {
      [TestClass]
      public class QueryTests
      {
        [TestMethod]
        public void ShouldReturnRecordsFromTheDatabaseByName()
        {
          //Arrange
          var init = new Initializer();
          var context = new
            BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
          IBlogRepository repo = new BlogRepository(context);
          
    
          //Act
          var items = repo.GetBlogsByName("Test");
                
          //Assert
          Assert.AreEqual(2, items.Count());
        }
      }
    
    }
  2. Add an initializer to the DataAccess project Database folder with the following code to set up data:
    using System;
    using System.Data.Entity;
    using BusinessLogic;
    namespace DataAccess.Database
    {
    
      public class Initializer : DropCreateDatabaseAlways<BlogContext>
      {
        public Initializer()
        {
                
        }
        protected override void Seed(BlogContext context)
        {
          context.Set<Blog>().Add(new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "not this one",
            Title = "Test Blog"
          });
          context.Set<Blog>().Add(new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "Test Blog 2"
          });
          context.Set<Blog>().Add(new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "not Blog"
          });
          context.SaveChanges();
        }
    
      }
    }
  3. In the BusinessLogic project add a new C# class named Blog with the following code:
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Text.RegularExpressions;
    
    namespace BusinessLogic
    {
      public class Blog
      {
        public int Id { get; set; }
        public DateTime Creationdate { get; set; }
        public string ShortDescription { get; set; }
        public string Title { get; set; }
        public double Rating { get; set; }
      }
    }
  4. Add a Mapping folder to the DataAccess project and then add a BlogMapping class to the folder with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    
    namespace DataAccess.Mappings
    {
      public class BlogMapping : EntityTypeConfiguration<Blog>
      {
        public BlogMapping()
        {
        this.ToTable("Blogs");
        this.HasKey(x => x.Id);
        this.Property(x => x.Id).HasDatabaseGeneratedOption
          (DatabaseGeneratedOption.Identity)
            .HasColumnName("BlogId");
    
         this.Property(x => x.Title).IsRequired()
           .HasMaxLength(250);
         this.Property(x => x.Creationdate)
           .HasColumnName("CreationDate").IsRequired();
         this.Property(x => x.ShortDescription)
           .HasColumnType("Text").IsMaxLength()
              .IsOptional().HasColumnName("Description");
        }
    
      }
    }
  5. Add a new interface in the BusinessLogic project named IBlogRepository with the following code:
    namespace DataAccess
    {
      public interface IBlogRepository
      {
        void RollbackChanges();
        void SaveChanges();
      }
    }
  6. Modify the BlogContext class to contain the new mappings for Blogs with the following code:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Mappings;
    
    namespace DataAccess
    {
      public class BlogContext : DbContext, IUnitOfWork
      {
        public BlogContext(string connectionString)
          : base(connectionString)
        {
    
        }
    
        protected override void OnModelCreating
          (DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          base.OnModelCreating(modelBuilder);
        }
    
        public IQueryable<T> Find<T>() where T : class
        {
          return this.Set<T>();
        }
    
        public void Refresh()
        {
          this.ChangeTracker.Entries().ToList()
            .ForEach(x=>x.Reload());
        }
    
        public void Commit()
        {
          this.SaveChanges();
        }
      }
    }
  7. Add a new folder named Queries to the BusinessLogic project and add a new C# class to it named BlogQueries with the following code:
    using System.Collections.Generic;
    using System.Linq;
    using DataAccess;
    
    namespace BusinessLogic.Queries
    {
      public static class BlogQueries
      {
        public static IEnumerable<Blog> GetBlogsByName(this IBlogRepository repo, string name)
        {
          return ((InternalBlogRepository)repo)
            .Set<Blog>().FilterByBlogName(name).ToArray();
        }
    
        public static IQueryable<Blog>
          FilterByBlogName(this IQueryable<Blog>
            items, string name )
        {
          return items.Where(x => x.Title.Contains(name));
        }
      }
    }
  8. Add a new class to the Queries folder in BusinessLogic named InternalBlogContext with the following code:
    using System.Linq;
    
    namespace BusinessLogic.Queries
    {
      public abstract class InternalBlogRepository
      {
        public abstract IQueryable<T> Set<T>() where T : class;
      }
    }
  9. Modify the BlogRepository class with the following code:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using BusinessLogic.Queries;
    
    namespace DataAccess
    {
      public class BlogRepository
        : InternalBlogRepository, IBlogRepository
      {
        private readonly IUnitOfWork _context;
    
        public BlogRepository(IUnitOfWork context)
        {
          _context = context;
        }
    
        public override IQueryable<T> Set<T>()
        {
          return _context.Find<T>();
        }
    
        public void RollbackChanges()
        {
          _context.Refresh();
            }
    
        public void SaveChanges()
          {
            try
            {
              _context.Commit();
          }
          catch (Exception)
          {
            RollbackChanges();
          throw;
          }
                
        }
      }
    }
  10. Run our test and it will work.

How it works...

We start our solution by setting up a test which allows us to communicate with a database and return objects without having the ability to query directly. This test seeds the database with an initializer and then invokes a query that returns a set of data without surfacing how it does that.

We set up our blog entity, the database schema mapping, and the blog context as a basis for the database communication. These pieces allow us to translate our language integrated queries into SQL statements.

We then create a query that accepts an IBlogRepository and casts it to an InternalBlogRepository to get a set of items and return it. This cast allows us to grab a set of entities but not surface that into the interface that is widely used. We also limit the return type of the query to an IEnumerable<> instead of an IQueryable<>, so the returned enumerable cannot be used to modify the query in the other layers. This simple convention will indicate that data access is meant to be performed in the query libraries. If we need more control and want to tie this control more closely, then we can move our InternalBlogRepository and the queries to the data access project, and mark the InternalBlogRepository as internal. This will ensure that it cannot be used elsewhere.

There's more...

When limiting access to code and trying to force certain programming behaviors, we should be sure of some things.

Developer discipline

As professional developers, we need to have a certain amount of discipline. There are ways around almost all code access restrictions with some being more complex than others. These pathways are there but we must avoid using them and try to adhere to the prescribed architecture. If we disagree with it, then there are ways to address that, but do not just start subverting it as that will only hurt you and the code base in the end.

Likewise, when we are serving as architects, we should always be mindful that we want our developers to "fall into the pit of success." That is to say we want the easiest way to do something to be the right way to do something. If it is not the easiest way, then we are subverting our own architecture from the outset because of how it is designed. One of the benefits brought to us by test driven development is that we always start by using the component that we will be writing, even before we write it. This keeps us focused on the consumer of our libraries. If the test is difficult to write, then the code will also be difficult.

Cost of architecture

When we start down the road of restricting the architecture, we need to be aware of the cost that it will incur. Making sure that all queries are defined in a testable and reusable fashion is a goal to strive for. However, do not think that it comes without cost. Productivity and learning curve are going to suffer slightly due to this, so be aware and do not make these choices lightly.

See also

In this chapter:

  • Improving entity and library reuse recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset