Creating one-to-one maps

This recipe will allow us to map the direct relationships between two objects into the foreign keys that will hold this relationship 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 and updating data.

Open the Improving One-To-One References 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. Use 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.Include(x => x.AuthorDetail).FirstOrDefault();
          //Assert
          Assert.IsNotNull(blog);
          Assert.IsNotNull(blog.AuthorDetail);
        }
      }
    }
  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()
          {
            AuthorDetail = new AuthorDetail() { Bio = "Test", Email = "Email", Name = "Testing" },
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "Test Blog"
          });
          context.SaveChanges();
        }
      }
    }
  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; }
        public AuthorDetail AuthorDetail { get; set; }
      }
    }
  4. In the Business Logic project, add a new C# class named AuthorDetail with the following code:
    namespace BusinessLogic
    {
      public class AuthorDetail
      {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Bio { get; set; }
      }
    }
  5. 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");
          this.HasRequired(x => x.AuthorDetail).WithOptional().WillCascadeOnDelete();
        }
      }
    }
  6. Add a new C# class named PostMapping to the Mappings folder with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    namespace DataAccess.Mappings
    {
      public class AuthorDetailMapping : EntityTypeConfiguration<AuthorDetail>
      {
        public AuthorDetailMapping()
        {
          this.ToTable("AuthorDetails");
          this.HasKey(x => x.Id);
          this.Property(x => 
            x.Id).HasColumnName("AuthorDetailId")
              .HasDatabaseGeneratedOption
                 (DatabaseGeneratedOption.Identity);
          this.Property(x => x.Bio).HasColumnType("Text").IsMaxLength();
          this.Property(x => x.Email).HasMaxLength(100).IsRequired();
          this.Property(x => x.Name).HasMaxLength(100).IsRequired();
        }
      }
    }
  7. Modify the BlogContext class with the following code to add the collections and mappings for our objects:
    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());
          modelBuilder.Configurations.Add(new AuthorDetailMapping());
          base.OnModelCreating(modelBuilder);
        }
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<AuthorDetail> AuthorDetails { 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...

When we map a one-to-one relationship in Entity Framework, we are not only relating the objects but also defining the foreign key. This allows us to set up the relationships, but we have to be careful when we configure the relationship as the configuration determines how the foreign key looks.

The HasRequired() method defines the foreign key as a required constraint. If we try to commit an insert without this object, it will fail. This maps to a non-nullable key column on the schema side of the Blogs table.

The WithOptional() method configures the targeted property type as having a 0:1 relationship to the current type. When we use this in combination with the HasRequired() method, this creates a 1 to 0..1 relationship in the database. Configuring the relationship this way allows us to insert AuthorDetail without blog.

The WillCascadeOnDelete() method deletes related AuthorDetailwhen blog is deleted. This allows us to clean up the object graphs with a single delete statement, and avoid littering the database with orphaned data that holds no reference to any other objects.

There's more...

When we are mapping a one-to-one relationship, there are many options to consider, but the largest concern should be the object graph. When we query these objects, we have to be certain that we get all the related data that we need.

Using more fluent configurations

More fluent configurations are as follows:

  • HasOptional(): It configures a nullable relationship.
  • Map(): It allows for the configuration of relationships using columns not exposed in the object model.
  • WithRequiredPrinciple(): It configures a one-to-one relationship that is required on both the sides, and the object being configured is the principle object.
  • WithRequiredDependant(): It configures a one-to-one relationship that is required on both the sides, and the object being configured is the dependant object.

Considering eager versus lazy

Entity Framework allows us to either eager load the related objects (to explicitly decide when to load these objects), or to lazy load the objects (allow the framework to load these objects when they are needed). This gives us a choice between having the ability to reduce the total number of calls to the database tightly, while possibly increasing the amount of data retrieved, and performing a larger number of queries overall without worrying about the loading code. In Window forms and small-platform (less than 100 installs) applications, lazy loading may make sense for ease of coding, but for anything that has to be used in a web page, serialized over WCF, or large platform applications, you need the explicit nature of eager loading. The Include() method has been expanded to include the usage of lambdas to make this area of Entity Framework less dependent on magic strings.

See also

In this chapter:

  • Creating one-to-many maps

In Chapter 5, Improving Entity Framework with Query Libraries::

  • Implementing composed queries
..................Content has been hidden....................

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