This recipe will take us through the process of configuring the relationship between one object and many other objects.
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 One-To-Many References solution in the included source code examples.
Let us get connected to the database using the following steps:
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 ShouldReturnABlogWithPosts() { //Arrange Database.SetInitializer(new Initializer()); var context = new BlogContext(Settings.Default.BlogConnection); //Act var blog = context.Blogs.Include(x => x.Posts).FirstOrDefault(); //Assert Assert.IsNotNull(blog); Assert.IsNotNull(blog.Posts); } } }
BusinessLogic
project, add a new C# class named Blog
to 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; } } }
BusinessLogic
project, add a new C# class named Post
to the following code:using System; 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 Blog Blog { get; set; } } }
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(); } } }
PostMapping
to the Mapping
folder 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(); } } }
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()); 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(); } } }
We start off with a test that ensures that the Posts
property does not come back null from the database after initializing data to it. This will allow us to ensure that our code accomplishes the intended behaviour.
The HasMany()
method is what we are focusing on in this recipe. This method allows us to establish one-to-many relationships in the code. Here, the collection of Posts
that we added to Blog
is ICollection<T>
for usage. All the collections of objects on the "many" side need to be housed this way. This allows Entity Framework to return DbSet<T>
, but we do not have to spread that Entity Framework-specific type (and the dependencies it has) throughout our code.
The WithRequired()
method sets up the dependent side of the relationship, and forces the object relationship to exist. This is mapped to a foreign key column in the database that is not null. This will give an update error if an object is not provided, to prevent the attempt to insert bad data.
The WillCascadeOnDelete()
method allows us to specify that the related objects are in context. When a delete on this object happens, the mapped relationship will be deleted as well. This only applies to objects that are previously loaded by the context and are still in the context tracker. If you need the database side cascades, you must set the property on the foreign key.
When dealing with one-to-many relationships, there are many varieties, and configuring them takes several methods and practices.
More fluent configurations are as follows:
Often, there is more than one way to work through the configuration in a code, as there is to set up a data structure. This variability will also lead to differing opinions on how to define these configurations. We take the "configuration" approach of consolidating configuration to the central objects in the domain model. This allows more configurations to be viewed from those key maps. This has the added benefit of showing you the object interaction at the hubs of the application. You can define the same one-to-many relationship by using HasRequired(x=>x.Blog)
with Many(x=>x.Posts)
, from PostMapping
. This, however, would start spreading out the configuration and makes it harder to maintain.