Building a Blog Redux - Entity Framework Code First (Part 3)

Tuesday, April 10, 2012

This is the third post in a series of posts about how I went about building my blogging application.

  1. Building a Blog Redux - Why Torture Myself (Part 1)
  2. Building a Blog Redux - The tools for the trade (Part 2)

As I stated in my last post in the series, I really like what the ADO.Net team has done with the Entity Framework tool. I'm sure there are better ORM tools out there but this works well for what I need and want when CRUD operations.

Installing

With the inception of Nuget, getting Entity Framework installed is almost an after thought. I think it might actually be installed in an MVC 3 internet application by default, but you will still need to go into NuGet and upgrade the assemblies to get the latest version. As I write this post they are up to version 4.3.1. The latest release has the Code First Migration feature which allows you to update an already existing database without dropping it and then recreating it, but as of yet I have not have had a reason to use that feature.

To get to the NuGet package I right clicked on the web project and then selected the "Manage NuGet Packages" option.

Mange NuGet Packages

After that I updated the NuGet Entity Framework package.

Entity Framework 431

Entities and Context

Once the framework was installed, I could then start building entities. Now, I should say that I prefer my entities to be plain ole CLR objects (POCO). That is, I don't want my objects to have any type of decorations on them that would cause them to be dependent on external frameworks or other code in general. The reason being, is that should I ever want to switch to another Framework or re-architect the application somehow, I don't want to have to go in modify these classes removing the dependencies.

The latest versions of Entity Framework allows you to accomplish this with Entity Type Configurations. I will show you that in a but let's look at the Post entity.


using System;
using System.Collections.Generic;
 
namespace AviBlog.Core.Entities
{
    public class Post
    {
        public int Id { getset; }
        public virtual Blog Blog { getset; }
        public virtual UserProfile User { getset; }
        public string Title { getset; }
        public string Description { getset; }
        public string PostContent { getset; }
        public DateTime DateCreated { getset; }
        public DateTime? DateModified { getset; }
        public DateTime? DatePublished { getset; }
        public bool IsPublished { getset; }
        public string Slug { getset; }
        public bool IsDeleted { getset; }
        public virtual ICollection<Category> Categories { getset; }
        public virtual ICollection<Tag> Tags { getset; }
        public virtual ICollection<Trackback> Trackbacks { getset; }
        public Guid UniqueId { getset; }
    }
}

Nothing crazy here. Just a POCO class with no dependencies on anything. If I wanted to I could switch out Entity Framework and I would not have any issues with this class. That's good. That's what I want.

Another thing to notice here is that Entity Framework has a convention that says, if you name a property "Id" it will automatically assume that this is the primary key for the table and will make it so in the corresponding table.

To reference other tables like Blog and the collection of Categories, I had to set those property as virtual so Entity Framework can go in and overide those property with corresponding data from the SQL tables. When, in this case, my Post class has a collection of Categories and my Category class has a collection Posts, Entity Framework will create a joining SQL table to allow a many-to-many relationship.

Aviblog SQL Table sample

To customize these properties so that the SQL table fields have specific information, I created a context class as well as for each table I created a configuration class to address the specifics so let's look at those.


using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using AviBlog.Core.Context.Configurations;
using AviBlog.Core.Entities;
 
namespace AviBlog.Core.Context
{
    public class BlogContext : DbContextIDbConnectionFactory
    {
        protected BlogContext()
        {
        }
 
        public BlogContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
        }
 
        public DbSet<Blog> Blogs { getset; }
        public DbSet<Post> Posts { getset; }
        public DbSet<Category> Categories { getset; }
        public DbSet<HtmlFragment> HtmlFragments { getset; }
        public DbSet<Tag> Tags { getset; }
        public DbSet<UserProfile> UserProfiles { getset; }
        public DbSet<UserRole> UserRoles { getset; }
        public DbSet<Trackback> Trackbacks { getset; }
        public DbSet<HtmlFragmentLocation> HtmlFragmentLocations { getset; }
        public DbSet<Setting> Settings { getset; }
        public DbSet<PingService> PingServices { getset; }
        public DbSet<StopWord> StopWords { getset; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new PostConfiguration());
            modelBuilder.Configurations.Add(new BlogConfiguration());
            modelBuilder.Configurations.Add(new CategoryConfiguration());
            modelBuilder.Configurations.Add(new HtmlFragementConfiguration());
            modelBuilder.Configurations.Add(new TagConfiguration());
            modelBuilder.Configurations.Add(new UserProfileConfiguration());
            modelBuilder.Configurations.Add(new UserRoleConfiguration());
            modelBuilder.Configurations.Add(new TrackbackConfiguration());
            modelBuilder.Configurations.Add(new HtmlFragmentLocationConfiguration());
            modelBuilder.Configurations.Add(new SettingsConfiguration());
            modelBuilder.Configurations.Add(new PingServiceConfiguration());
            modelBuilder.Configurations.Add(new StopWordConfiguration());
            base.OnModelCreating(modelBuilder);
        }
 
        public DbConnection CreateConnection(string nameOrConnectionString)
        {
            return new SqlConnection(nameOrConnectionString);
        }
    }
}

The context class is derived from the DBContext base class which has all the magic to wire my SQL tables up. To specify field lengths and other fields attributes each entity has a configuration class which is wired up on the OnModelCreating function which is overridden from the base class. The DBSet<> properties is what maps the specified entity to the SQL table.

Let's look at one of the configuration classes.


using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;
using AviBlog.Core.Entities;
 
namespace AviBlog.Core.Context.Configurations
{
    public class PostConfiguration : EntityTypeConfiguration<Post>
    {
        public PostConfiguration()
        {
            Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(x => x.PostContent).IsRequired();
            Property(x => x.Title).IsRequired();
            Property(x => x.Title).HasMaxLength(200);
            Property(x => x.Slug).HasMaxLength(500);
        }
    }
}

When Entity Framework builds your tables, it will look at this configuration code and will build out the SQL tables accordingly. You cal also do other things like map your entity to an existing table or if you happen to have properties in your entity that you don't want to map to a table you could tell the framework to ignore that property.

Once the tables are set up I could then run code to create the database. I did this with an initializer class that I could call both from the application startup event or a test class. I have both, but pointing to different databases. The initializer code will create the database and then seed the tables with any initial data you would like the database to have.


using
 System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using AviBlog.Core.Context;
using AviBlog.Core.Encryption;
using AviBlog.Core.Entities;
 
namespace AviBlog.Web.Tests.Initializers
{
    public class BlogDbInitializer : DropCreateDatabaseAlways<BlogContext>
    {
        protected override void Seed(BlogContext context)
        {
            var enc = new EncryptionHelper();
            var blog = new Blog
                           {
                               BlogName = "Steven Moseley's Ruminations",
                               IsActive = true,
                               IsPrimary = true,
                               SubHead = "About that art of writing code, plus other minutiae"
                           };
            var user = new UserProfile
                           {
                               FirstName = "Steven",
                               LastName = "Moseley",
                               UserName = "admin",
                               Blog = blog,
                               IsActive = true
                           };
 
            var users = new List<UserProfile> {user};
            var roleAdmin = new UserRole
                                {
                                    RoleName = "Admin",
                                    UserProfiles = users
                                };
            var roleUser = new UserRole
                               {
                                   RoleName = "Aviblog User"
                               };
            var tags = new List<Tag> { new Tag{ TagName = "tag" }};
            var post = new Post
                           {
                               Blog = blog,
                               DateCreated = DateTime.Now,
                               DateModified = DateTime.Now,
                               Description = "description",
                               IsDeleted = false,
                               IsPublished = true,
                               PostContent = "post content",
                               Title = "title",
                               User = user,
                               Slug = "slug",
                               Tags = tags,
                               UniqueId = Guid.NewGuid()
                           };
            
            var posts = new List<Post> {post};
            var category = new Category
                               {
                                   CategoryName = "Test",
                                   Posts = posts
                               };

            context.Blogs.Add(blog);
            context.Posts.Add(post);
            context.UserProfiles.Add(user);           
            context.Categories.Add(category);
            context.UserRoles.Add(roleAdmin);
            context.UserRoles.Add(roleUser);
            
            base.Seed(context);
        }
    }
}

Inheriting from the DropCreateDatabaseAlways class, will tell Entity Framework to drop your database and recreated it if there is a change in any of the entities, so its good to backup or have a copy of your development database when you run this code. There are other options and you can also pass in null to this routine and Entity Framework will skip this step.

Finally, lets look at one of the repository classes I created.


using System.Configuration;
using System.Linq;
using AviBlog.Core.Context;
using AviBlog.Core.Entities;
 
namespace AviBlog.Core.Repositories
{
    public class BlogSiteRepository : IBlogSiteRepository
    {
        private readonly BlogContext _context;
 
        public BlogSiteRepository()
        {
            string connection = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
            _context = new BlogContext(connection);
        }
 
        public IQueryable<Blog> GetAllBlogs()
        {
            return _context.Blogs.AsQueryable();
        }
 
        public string Add(Blog blog)
        {
            _context.Blogs.Add(blog);
            _context.SaveChanges();
            _context.Dispose();
            return string.Empty;
        }
 
        public string DeleteBlog(int id)
        {
            var blog = _context.Blogs.FirstOrDefault(x => x.Id == id);
            if (blog == nullreturn "The specified blog could not be found.";
            _context.Blogs.Remove(blog);
            _context.SaveChanges();
            _context.Dispose();
            return string.Empty;
        }
 
        public string Save(Blog blog)
        {
            var temp = _context.Blogs.FirstOrDefault(x => x.Id == blog.Id);
            if (temp == nullreturn "The specified blog could not be found.";
            temp.BlogName = blog.BlogName;
            temp.HostName = blog.HostName;
            temp.IsActive = blog.IsActive;
            temp.IsPrimary = blog.IsPrimary;
            temp.SubHead = blog.SubHead;
            _context.SaveChanges();
            _context.Dispose();
            return string.Empty;
        }
 
        public Blog GetBlogId(int selectedBlogId)
        {
            return _context.Blogs.Find(selectedBlogId);
        }
    }
}

This code has all the basic functions for all the CRUD operations.  I'm thinking i'll refactor this code and use the Unit of Work pattern later on. Haven't dont it yet, but I'll write about that in an upcoming post.

As always you can see all of the code up on Github so checkit out, fork it make changes and let me know how I can improve it.

 

comments powered by Disqus