Improving property maps

This recipe will allow us to map a property and attach some metadata to it, to guide the connection to the related table and the column in the database.

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 for connecting to the data and updating it.

Open the Improving Property 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 ShouldReturnABlogWithAuthorDetails()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
          //Act
          var blog = context.Blogs.FirstOrDefault();
          //Assert
          Assert.IsNotNull(blog);
        }
      }
    }
  2. Add an initializer to the DataAccess project in the Database folder, with the following code, to set up the data:
    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"
          });
          base.Seed(context);
        }
      }
    }
  3. In the BusinessLogic project, add a new C# class named Blog, with the following code:
    using System;
    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; }
      }
    }
  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);
          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 and a DbSet property 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    {
      public BlogContext(string connectionString)
      : base(connectionString)
      {
      }
      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
        modelBuilder.Configurations.Add(new BlogMapping());
        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();
      }
    }
  6. Run our test, and see how it works.

How it works...

Our solution starts with a test, as always, to set up the intended behaviour of mapping the properties to the database correctly. It is pivotal to the structure of our code, as this is how we validate the database schema. This also allows us to set up data for the test.

We create an object with a couple of properties that are similar but not exactly the same as the database object. We then create a mapping class that inherits from the EntityTypeConfiguration<T> class. This inheritance gives us the fluent configuration Application Programming Interface (API) that we will leverage. The conventions will cover some of the configuration for us but leave us open to the issues caused by renaming. If we refactor a property name to make the program more concise, it will break the convention and will no longer talk to the database. This seems like a huge vulnerability that we should avoid. We make sure that every property has a HasColumnName() configuration. This takes us a bit more time but is worth doing for the trouble it saves us on risk and supportability.

The object needs to know the table in which we would like to save this, and as such, we have the ToTable() method that allows us to configure this.

Once the table is configured, the first property that we need to specify is the key. We can use HasKey() method to set the property or the properties that will be the key. Once we have that defined, we want to use Property() to attach a HasColumnName() method to the key, so that we control the name that it gets generated with. Then, we attach the HasDatabaseGeneratedOption() method to allow us to configure the identity column, so that we do not try to set this property, and overwrite it in the database. Trying to do it without this setting would throw an error.

The columns as well as each of the properties in our database are marked either nullable or non-nullable. The fluent API gives us this ability through the IsRequired and IsOptional methods. These two methods not only mark the column as nullable or not nullable in the schema for generation, but also validate this on the program side before sending the bad data to the database for an SQL error.

The Title property has a limited length in our database, and we need to communicate it to the configuration. The HasMaxLength method not only allows us to set this up, but also gives us the program-side validation.

The ShortDescription property is not stored as the default string storage varchar but as a text field that allows for the maximum length of SQL. This can be communicated through the use of the HasColumnType() and IsMaxLength() methods. The HasColumnType() method allows you to specify the type that a property should be stored as. However, if the property cannot be converted to that type, it will show an error and fail.

There's more...

There are several more areas that are less frequently used but definitely need to be mentioned. The following section supplies more detail about why you may want to specify the configuration even while matching the convention, the tools that help to generate these mappings from a database, and the explanation for the approach to storing the mappings.

Mapping storage

There are two main thought processes involved in the storage of mappings. First is the idea that they should be stored in a class in the same file as the domain object. This not only allows for the easy changing of the mappings when the object changes, but also pushes the storage mechanism dependencies into our core layer. If we want to change the storage mechanism at some point, we will have to change our core code too. This does not sit well with us, so we opt for the second option, that is, the storage of the mappings in a folder close to the context declaration. This allows us to keep the implementation specifics in one place, and also keeps our dependencies within Entity Framework out of the core programming logic.

More fluent configurations

The following is a list of the more fluent configurations:

  • ToTable(tableName,schemaName): It allows us to connect the objects to the tables and schemas that do not share their naming convention with our objects.
  • HasColumnOrder(): It specifies the order for the key while using a composite key.
  • IsUnicode(): It specifies that the text is Unicode-compliant.
  • IsConcurrencyToken(): It specifies the column to be the token that is used in determining the optimistic concurrency for parallel processings.

Finding tools to help

In 2010, the Entity Framework team announced the community technology preview of Entity Framework Code First Power Toys. This software package is a Visual Studio extension that allows for the generation of true Plain Old CLR Objects (POCOs) and their mappings, based on an existing database schema. These tools are not aware of the schema at this moment, but are otherwise fairly full-featured. They create a type for each table, so that most of the cases will have to modify the generated code. The generation is not meant for long-term usage, but a one-time help to overcome the overhead of writing hundreds of mapping tables while starting in an existing application.

See also

In this chapter:

  • Creating one-to-one maps

In Chapter 1, Improving Entity Framework in the Real World:

  • Creating databases from the code
..................Content has been hidden....................

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