Thursday, August 22, 2013

Building Booky SPA - Building Booky Entity Framework Code First

The main steps to building Entity Framework Code First is to:

  1. Create POCO model classes 
  2. Create a derived type that inherits from DbContext which includes DbSet properties 

(System.Data.Entity.DbSet<TEntity>) for each POCO model class, and optionally override the methods we want to customize to create our own DbContext implementation. We will use the domain model from booky. Booky has the concept of Bookmarks, Tags, Users and Boards.

Creating a Domain Model

Booky requires the following relationships based on how the data is best modeled. Booky has the concept of a user where each User has one to many Bookmarks. This is written as One User to many Bookmarks (one to many). Booky has the concepts of Boards which are like groups that allow you to group a list of Bookmarks that are associated with a Board. Booky has one Board to many Bookmarks which is written as One Board to many Bookmarks (one to many). Booky has the concept of each Bookmark being assigned many Tags which are shared among all Bookmarks. Booky has a Many to Many relationship between Tags and Bookmarks (Many to Many).

Booky Code First Class Diagram



Let's review a code example of the booky domain model.

Bookmark POCO Sample:

 public class Bookmark  
 {  
   #region Fields  
   [Key]  
   public int BookmarkId { get; set; }  
   [Required, MaxLength(128)]  
    public string Title { get; set; }  
   [Required, MaxLength(512)]  
   public string Url { get; set; }  
   public DateTime DtCreated { get; set; }  
   public bool Read { get; set; }  
   public bool Starred { get; set; }  
   [Required, MaxLength(128)]  
   public string Description { get; set; }  
   public int UserId { get; set; }  
   public User User { get; set; }  
   public virtual ICollection<Tag> Tags { get; set; }  
   public Board Board { get; set; }  
   #endregion  
   #region ctor  
   public Bookmark()  
   {  
     Tags = new HashSet<Tag>();  
   }  
   #endregion  
 }  

User POCO Sample:

 public class User  
 {  
   [Key]  
   public int UserId { get; set; }  
   [Required, MaxLength(16)]  
   public string Username { get; set; }  
   [Required]  
   public string PasswordHash { get; set; }  
   [Required, MaxLength(256)]  
   public string Email { get; set; }  
   public DateTime DtCreated { get; set; }  
   public DateTime? DtLastUpdated { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public User()  
   {   
     Bookmarks = new HashSet<Bookmark>();      
   }  
 }  

Board POCO Sample:

 public class Board  
 {  
   [Key]  
   public int BoardId { get; set; }  
   [Required, MaxLength(128)]  
   public string Title { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public Board()  
   {  
     Bookmarks = new HashSet<Bookmark>();  
   }  
 }  

Tag POCO Sample:

 public class Tag  
 {  
   [Key]  
   public int TagId { get; set; }  
   [Required, MaxLength(64)]  
   public string Title { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public Tag()  
   {  
     Bookmarks = new HashSet<Bookmark>();  
   }  
 }  

}

Note there are certain fields above which are Required; this allows for validation via attribute decoration. If you require a different primary key field name you can decorate your primary key with the [Key] attribute. You have the option to have a custom name or use the default convention which is done by specifying Id as the primary key.

Create a derived type BookyDbContext 

Creating a derived type BookyDbContext is as simple as inheriting from DbContext, creating a constructor that specifies a connection string ("Sql_CE" in our case) passing this parameter to the base class. Next the requirement is for database table names to not be pluralized and when the model changes I want to Drop and the Create Database. There are model builder configurations being added that will be touched upon later. 

BookyDbContext Code Sample:

public class BookyDbContext : DbContext 
{
    public BookyDbContext()
    : base(nameOrConnectionString: "Sql_CE") { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
            // Use singular table names
       modelBuilder.Conventions.Remove();
       Database.SetInitializer(new DropCreateDatabaseIfModelChanges());

       modelBuilder.Configurations.Add(new BookmarkConfiguration());
       modelBuilder.Configurations.Add(new TagConfiguration());
    }

    public DbSet Bookmarks { get; set; }

    public DbSet Users { get; set; }

    public DbSet Tags { get; set; }

    public DbSet Boards { get; set; }
}

You need a DbSet Property for each model class.

Using EntityTypeConfiguration to Create Model Relationships 


class BookmarkConfiguration : EntityTypeConfiguration
{
    internal BookmarkConfiguration()
    {
        this.HasMany(b => b.Tags)
            .WithMany(t => t.Bookmarks)
            .Map(mc =>
            {
                mc.MapLeftKey("BookmarkId");
                mc.MapRightKey("TagId");
                mc.ToTable("BookmarkTag");
            });

        this.HasOptional(bd => bd.Board)
            .WithMany(b => b.Bookmarks)
            .Map(x => x.MapKey("BoardId"));
    }

}

internal class TagConfiguration : EntityTypeConfiguration
{
    internal TagConfiguration()
    {
        this.HasMany(t => t.Bookmarks).WithMany(b => b.Tags).Map(
            mc =>
                {
                    mc.MapLeftKey("TagId");
                    mc.MapRightKey("BookmarkId");
                    mc.ToTable("BookmarkTag");
                });
    }
}

Assure you have these classes configured in BookyDbContext

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Use singular table names
    modelBuilder.Conventions.Remove();
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges());

    modelBuilder.Configurations.Add(new BookmarkConfiguration());
    modelBuilder.Configurations.Add(new TagConfiguration());

}

Sample Code to Add/Update/Delete/Read data


public Bookmark GetById(int id)
{
    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.BookmarkId == id);
        return bookmark;
    }
}

public IEnumerable GetAll(int userId)
{
    using (var context = new BookyDbContext())
    {
        return context.Bookmarks.Where(o => o.UserId == userId).ToList();
    }
}

public List GetPagedEntity(int skip, int take, int userId)
{
    using (var context = new BookyDbContext())
    {
        return context.Bookmarks.Where(o => o.UserId == userId).OrderBy(o => o.Title).Skip(skip).Take(take).ToList();
    }
}

public Bookmark GetBookmarkByUri(string Uri)
{
    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.Url == Uri);

        return bookmark;
    }
}

public Bookmark Delete(int id, int userId)
{
    //var bookmark = GetById(id, userId);

    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.BookmarkId == id);
               
            context.Bookmarks.Remove(bookmark);
            context.SaveChanges();  
         
        return bookmark;
    }
}

public Bookmark Add(Bookmark item, int userId)
{
    using (var context = new BookyDbContext())
    {
        Tag tag = SaveDefaultTagIfNotExists(context);
        item.DtCreated = DateTime.Now;
        item.UserId = userId;
        item.Tags.Add(tag);
        context.Bookmarks.Add(item);
        context.SaveChanges();

        Board board = this.SaveDefaultBoardIfNotExists(context, item);
    }

    return item;
}

private Tag SaveDefaultTagIfNotExists(BookyDbContext context)
{
    //Check if the tag exists first, if not then add the tag
    var existTag = context.Tags.FirstOrDefault(o => o.Title == "General");

    if (existTag != null)
    {
        return existTag;
    }
    else
    {
        var tag = new Tag();
        tag.Title = "General";
        context.Tags.Add(tag);
        context.SaveChanges();
        return tag;
    }
}

private Board SaveDefaultBoardIfNotExists(BookyDbContext context, Bookmark bookmark)
{
    //Check if the tag exists first, if not then add the tag
    var existBoard = context.Boards.FirstOrDefault(o => o.Title == "General");

    if (existBoard != null)
    {
        existBoard.Bookmarks.Add(bookmark);
        context.SaveChanges();
        return existBoard;
    }
    else
    {
        var board = new Board();
        board.Title = "General";
        board.Bookmarks.Add(bookmark);
        context.Boards.Add(board);
        context.SaveChanges();
        return board;
    }
}

public Bookmark Update(Bookmark item, int userId)
{
    var bookmark = GetById(item.BookmarkId, userId);
    using (var context = new BookyDbContext())
    {
        if (bookmark != null)
        {
            bookmark.Title = item.Title;
            bookmark.Description = item.Description;
            bookmark.Read = item.Read;
            bookmark.Starred = item.Starred;
            bookmark.Url = item.Url;
       
            context.SaveChanges();
        }
    }

    return item;
}


No comments:

Post a Comment