In this recipe, we will learn to define restrictions on the number of related objects that are defined.
We will be using the 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 Collection Property Validation solution in the included source code examples.
Let's get connected to the database using the following steps:
ValidationTests
to the test project. We make a test that connects to the database and adds an object. This will test whether the configuration and our validation code are properly separated:using System; using System.Collections.Generic; using System.Data.Entity.Validation; using System.Linq; using System.Text; using System.Text.RegularExpressions; using BusinessLogic; using DataAccess; using DataAccess.Database; using Microsoft.VisualStudio.TestTools.UnitTesting; using Test.Properties; using System.Data.Entity; namespace Test { [TestClass] public class ValidationTest { [TestMethod] [ExpectedException(typeof(DbEntityValidationException))] public void ShouldErrorOnBelowMinimumPosts() { //Arrange var init = new Initializer(); var context = new BlogContext(Settings.Default.BlogConnection); init.InitializeDatabase(context); var blog = new Blog() { Creationdate = DateTime.Now, ShortDescription = "Test", Title = "Test", Posts = new List<Post>() { new Post() }.ToArray() }; //Act context.Set<Blog>().Add(blog); context.SaveChanges(); //Assert Assert.Fail("Didn't Error"); } [TestMethod] [ExpectedException(typeof(DbEntityValidationException))] public void ShouldErrorOnAboveMaximumPosts() { //Arrange var init = new Initializer(); var context = new BlogContext(Settings.Default.BlogConnection); init.InitializeDatabase(context); var blog = new Blog() { Creationdate = DateTime.Now, ShortDescription = "Test", Title = "Test", Posts = new List<Post>() { new Post(), new Post(), new Post(), new Post() }.ToArray() }; //Act context.Set<Blog>().Add(blog); context.SaveChanges(); //Assert Assert.Fail("Didn't Error"); } } }
DataAccess
project in the Database
folder with the following code to set up the data:using System; 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", Posts = new Post[]{new Post()} }); context.SaveChanges(); } } }
BusinessLogic
project, add a new C# class named Blog
with the following code:using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; 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 double Rating { get; set; } [MaxLength(3, ErrorMessage = "Cannot have more than 3 posts"),MinLength(2, ErrorMessage = "Must Have at least 2 posts")] public Post[] Posts { get; set; } } }
Post
to the BusinessLogic
project with the following code:namespace BusinessLogic { public class Post { public int Id { get; set; } } }
Mapping
folder to the DataAccess
project, and 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"); } } }
PostMapping
to the 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.HasKey(x => x.Id); this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } }
BlogContext
class to contain the new mappings 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 PostMapping()); base.OnModelCreating(modelBuilder); } 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 by defining a test that codifies our intent to limit the maximum number of posts to 3
, and the minimum number to 2
. This will allow us to test our implementation for completeness, and will make sure that we only write the code required to accomplish the goals.
The Blog
object has a collection of Post
objects that is restricted by the MaxLength
and MinLength
attributes. This shows that we can layer on validation attributes, and they will both get enforced. This enables us to compose a complex set of restrictions that are simple yet powerful when used in combination.
The mapping and blog context allow us to specify the structure restrictions through the mappings, and wire them into the blog context for communication.
When business rules require that certain sets of data have relationships with x
number of minimum or maximum objects. Here are some best practices:
If we specify the restriction that all blogs must have at least two, but no more than three posts, then we have limited our reusability of the blog. If anyone wants to use this object outside our environment, then we need to spend some design time thinking about how our data validation will affect them, whether positively and negatively.