Mapping one table to many objects

This allows us to map multiple objects from a single table, which will separate large load situations and allow for better performance.

Getting ready

We will be using 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 to connect to the data and update it.

Open the Improving Single Table To Multiple Object Maps solution in the included source code examples.

How to do it...

Let us get connected to the database using the following steps:

  1. We start by adding a new unit test named MappingTest to the Test project. We make a test that connects to the database and retrieves an object. This will test the configuration and ensure that the model matches the database schema using the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    using System.Data.Entity;
    namespace Test
    {
      [TestClass]
      public class MappingTest
      {
        [TestMethod]
        public void ShouldReturnABlogWithLogo()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
          //Act
          var post = context.Blogs.FirstOrDefault();
          //Assert
          Assert.IsNotNull(post);
          Assert.IsNotNull(post.BlogLogo);
        }
      }
    }
  2. Add a new C# class named Initializer to the DataAccess project in the Database folder, with the following code:
    using System;
    using System.Collections.Generic;
    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",
            BlogLogo = new BlogLogo(){Logo = new byte[0]}
          });
          context.SaveChanges();
        }
      }
    }
  3. Add a new C# class named Blog to the BusinessLogic project, with the following code:
    using System;
    using System.Collections.Generic;
    using DataAccess;
    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 virtual BlogLogo BlogLogo { get; set; }
      }
    }
  4. Add another C# class named BlogLogo to the BusinessLogic project, with the following code:
    namespace BusinessLogic
    {
      public class BlogLogo
      {
        public int Id { get; set; }
        public byte[] Logo { get; set; }
      }
    }
  5. Now that we have our domain objects, we want to 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).HasMaxLength(175);
          this.HasRequired(x => x.BlogLogo).WithRequiredPrincipal();
        }
      }
    }
  6. Add another C# class named BlogLogoMapping to the Mapping folder, with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    namespace DataAccess.Mappings
    {
      public class BlogLogoMapping : EntityTypeConfiguration<BlogLogo>
      {
        public BlogLogoMapping()
        {
          this.ToTable("Blogs");
          this.HasKey(x => x.Id);
          this.Property(x => x.Id)
         .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
          .HasColumnName("BlogId");
        }
      }
    }
  7. Modify BlogContext to include Blog DbSet<>, and the new configurations, 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());
          modelBuilder.Configurations.Add(new BlogLogoMapping());
          base.OnModelCreating(modelBuilder);
        }
        public DbSet<Blog> Blogs { get; set; }
        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();
        }
      }
    }
  8. Run our test, and see how it works.

How it works...

We start this solution with a test to make sure that we are accomplishing the goals and nothing more. For this solution, the goal is to split a table into multiple objects, so that we can load each of them independently, as needed, without a large data pull. This is often used to separate large images stored in SQL from the data that they are normally stored with.

We added our blog and the logo of our blog to give a representation of this scenario. BlogLogo is a varbinary(max) in the database, and could take sizable time to load. The key point to note, in the domain objects, is that the virtual keyword is on BlogLogo, which will allow for the lazy loading of the logo when needed, but not before.

The mappings that allow for the splitting of a table are fairly straightforward. It requires a one-to-one relationship, of which the blog is the principal. The logo has no navigation property to get to the blog that holds it, as it was not needed in our code. This leads us to the HasRequired() and WithRequiredPrinciple() methods for the navigation property.

Also, note how the key is configured the same on both the objects, that is, this and ToTable(); this splits the table into separate objects. The required one-to-one relationship is to enforce that no one tries to insert BlogLogo without a blog attached to it. If you are just dealing with objects whose needs may be uncertain to you, you might not know until the context throws an exception to let you know.

There's more...

With splitting tables, you need to make sure that the need is there only for the sake of performance, and not just idle architecting.

The cost

The additional overhead of creating this type of mapping, and the knowledge that we force the future developers to have, is a major concern. We have forced developers to know that BlogLogo is related to the Blog class, and must be created with one, but that restriction does not exist in our code. This native knowledge is one of the main reasons that the onboarding process for legacy systems is so long. While writing new applications, there are other ways to solve this, such as a shared primary key association .

Shared primary key association

Sharing a table through one-to-one relationships that hit it is a fairly simple scenario and can serve to dissect tables. If we pulled BlogLogo into a separate table, and marked it as a one-to-one relationship that is required from Blog but is optional from BlogLogo, we would create a scenario in which we could query directly for BlogLogo. This would allow us to reuse that data without the need to query only parts of BlogTable.

See also

In this chapter:

  • Creating one-to-one maps
..................Content has been hidden....................

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