Creating many-to-many maps

This recipe allows us to map a set of objects to another set of objects without constraining either side.

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 Many-To-Many 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, 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 ShouldReturnABlogWithAuthors()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
          //Act
          var post = context.Posts.Include(x => x.Authors).FirstOrDefault();
          //Assert
          Assert.IsNotNull(post);
          Assert.IsTrue(post.Authors.Count == 1);
        }
      }
    }
  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)
        {
          AuthorDetail authorDetail = new AuthorDetail() { Bio = "Test", Email = "Email", Name = "Testing" };
          Post item = new Post() { Content = "Test", PostedDate = DateTime.Now, Title = "Test", Authors = new List<AuthorDetail>{authorDetail} };
          context.Set<Blog>().Add(new Blog()
          {
            Posts = new List<Post>() { item },
            AuthorDetail = authorDetail,
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "Test Blog"
          });
          context.SaveChanges();
        }
      }
    }
  3. Add a new C# class named Post to the BusinessLogic project with the following code:
    using System;
    using System.Collections.Generic;
    namespace BusinessLogic
    {
      public class Post
      {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public DateTime PostedDate { get; set; }
        public ICollection<AuthorDetail> Authors { get; set; }
      }
    }
  4. Add another C# class named AuthorDetail to the BusinessLogic project with the following code:
    using System.Collections.Generic;
    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; }
        public ICollection<Post> Posts { get; set; }
      }
    }
  5. Add another 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 AuthorDetail AuthorDetail { get; set; }
        public ICollection<Post> Posts { get; set; }
      }
    }
  6. 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);
          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);
          this.HasMany(x => x.Posts).WithRequired(x => x.Blog).WillCascadeOnDelete(false);
        }
      }
    }
  7. Add a new C# class to the Mapping folder named PostMapping with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    namespace DataAccess.Mappings
    {
      public class PostMapping : EntityTypeConfiguration<Post>
      {
        public PostMapping()
        {
          this.ToTable("Posts");
          this.HasKey(x => x.Id);
          this.Property(x => x.Id).HasColumnName("PostId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
          this.Property(x => x.Content).HasColumnName("Body").IsMaxLength();
          this.Property(x => x.PostedDate).HasColumnName("PostedDate");
          this.Property(x => 
            x.Title).HasColumnName("Title").IsMaxLength();
          this.HasMany(x => x.Authors).WithMany(x => x.Posts);
        }
      }
    }
  8. Add another C# class named AuthorDetailMapping to the Mapping 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();
        }
      }
    }
  9. Modify the BlogContext class to include the new mappings and DbSet<T> for each type, 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)
        {
          if (Database.Exists() && !Database.CompatibleWithModel(false)) Database.Delete();
          if (!Database.Exists()) Database.Create();
        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          modelBuilder.Configurations.Add(new AuthorDetailMapping());
          modelBuilder.Configurations.Add(new PostMapping());
          base.OnModelCreating(modelBuilder);
        }
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { 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();
        }
      }
    }
  10. Run our test, and see how it works.

How it works...

Our solution starts off, as always, with a test that defines the behaviour that we wish to code. We focus on this test at the beginning of each recipe to reaffirm the importance of test-driven code that only accomplishes the goal and nothing more.

Secondly, we set up our domain objects. The simple version is that Blog has many posts and Post has many Tags, but Tag also has many posts. This can create some interesting options for the database for reference, but for the objects, it is a very simple task of putting collections into each object.

The HasMany() method starts on one side of this many-to-many relationship. It tells the code that it is looking for a related set of objects.

This WithMany() method loops back to Post, which allows us to find a tag, and then find all the posts that it is used in. This not only creates some power for searching the application, but also creates multiple cascade paths for the cascading deletes. That is why WillCascadeOnDelete(false) had to be used in the Blog configuration. Any cascaded deletes that hit this will cause errors and, therefore, have to be turned off. Unrelated cascades are fine though.

There's more...

Many-to-many relationships in the database are fairly simple in the configuration. There are very few extras here.

Payload

If you need any kind of payload on many-to-many relationships, you must define an intermediate type. For instance, if you have a menu with tasks and menu items with a sort order in the many-to-many relationship tables, then you must define the relationship table as an entity to use the payload.

More fluent configuration

Map() : This allows you to configure many-to-many relationship with columns not surfaced in the object model.

See also

In this chapter:

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

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