Creating reusable queries

In this recipe, we will be working to create reusable queries that are defined outside of the data context and are specific to an object type.

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 Reusable Queries 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 test the 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;
    using System.Linq;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using DataAccess.Queries;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    
    namespace Test
    {
      [TestClass]
      public class QueryTests
      {
        [TestMethod]
        public void ShouldReturnRecordsFromTheDatabase()
        {
          //Arrange
          var init = new Initializer();
          var context = new 
            BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
          IBlogRepository repo = new BlogRepository(context);
                
    
          //Act
          var items = repo.Set<Blog>().FilterByBlogName("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 = "Testing",
              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. 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();
        }
      }
    }
  6. Add a new folder named Queries to the DataAccess project and add a new C# class to it named BlogQueries with the following code:
    using System.Linq;
    
    namespace DataAccess.Queries
    {
      public static class BlogQueries
      {
        public static IQueryable<Blog> FilterByBlogName
          (this IQueryable<Blog> items, string name)
        {
          return items.Where(x => x.Title.Contains(name));
        }
      }
    }
  7. Run our test and it will work.

How it works...

As we usually do, we start with a test that defines what we would like to accomplish. In this case, we want to seed a database with several records and then query those records for an expected return. The database initializer that we add inserts three records, two of which meet the requirements of our filter and one that does not.

We then set up our blog object, its mappings, and the object context, so we have a solid way to interact with the database. This will form the basis of the database communication and the framework for accessing the records that need to be filtered.

We then leverage a language feature of extension methods to layer on our queries without bloating our repository with every data query. This does two things, it lets us target a specific type, and it restricts the usage of that type. These queries will only be available on the types that use them and nowhere else.

Notice that we use an IQueryable<T> here. This is done to allow us to compose multiple statements together before translating it into an SQL statement that will be executed. If we want to limit this only to in memory objects, we can force it to be an array or a list.

There's more...

When using extension methods there are some things to keep in mind so you create a consistent and valuable library of queries.

Extension methods

Extension methods allow us to extend behavior onto a type without modifying that type or any of its inheritance chain. These methods are brought into scope at the namespace level. Therefore, we must add the using statement to have access to these.

Naming conflict

We can use extension methods to extend behavior to an existing type, but not to override it. The compiler gives priority to instance methods. Therefore, it will never call an extension method with the same signature as an instance method. It is also possible, though strongly discouraged, to have two extension methods with a same name, same parameters, and both in scope.

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