Tag Archives: Home Library - Page 2

Migrations v.s DatabaseInitializer

An index of the HomeLibrary application posts can be found here.

As I mentioned in my previous post, I ran into an interesting situation with Entity Framework 6. It went a little like this…

I kicked off my data layer with some domain objects, a HomeLibraryContext class (deriving from DbContext) and a HomeLibraryInitializer class which inherits from DropCreateDatabaseAlways. The HomeLibraryInitializer contains a Seed method which inserts some data into the newly created database.

I also decided that I was going to use EntityFramework Migrations to enable me to make changes to the database as I evolve my domain model. To that end, I enabled migrations on the HomeLibrary.EF project using the Package Manager Console and created the first Migration:

internal sealed class Configuration : DbMigrationsConfiguration<Db.HomeLibraryContext>
{
	public Configuration()
	{
		AutomaticMigrationsEnabled = false;
	}

	protected override void Seed(Db.HomeLibraryContext context)
	{
		new List<Person>
			{
				new Person {FirstName = "Terry", LastName = "Halpin"},
				new Person {FirstName = "Alan", LastName = "Turing"}
			}.ForEach(p => context.People.Add(p));

		context.SaveChanges();
	}
}

The initial Migration:

public partial class Initial : DbMigration
{
	public override void Up()
	{
		CreateTable(
			"dbo.BookCovers",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					BookId = c.Int(nullable: false),
					Edition = c.Int(nullable: false),
					Cover = c.Binary(maxLength: 4000),
				})
			.PrimaryKey(t => t.Id)
			.ForeignKey("dbo.Books", t => t.BookId, cascadeDelete: true)
			.Index(t => t.BookId);
		
		CreateTable(
			"dbo.Books",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					Title = c.String(nullable: false, maxLength: 4000),
					Edition = c.Int(nullable: false),
					PublisherId = c.Int(nullable: false),
					TypeOfBook = c.Int(nullable: false),
				})
			.PrimaryKey(t => t.Id)
			.ForeignKey("dbo.Publishers", t => t.PublisherId, cascadeDelete: true)
			.Index(t => t.PublisherId);
		
		CreateTable(
			"dbo.People",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					Email = c.String(nullable: false, maxLength: 4000),
					IsAuthor = c.Boolean(nullable: false),
					FirstName = c.String(nullable: false, maxLength: 4000),
					LastName = c.String(nullable: false, maxLength: 4000),
					Sobriquet = c.String(maxLength: 4000),
				})
			.PrimaryKey(t => t.Id);
		
		CreateTable(
			"dbo.Lendings",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					BookId = c.Int(nullable: false),
					BorrowerId = c.Int(nullable: false),
					DateLent = c.DateTime(nullable: false),
					DueDate = c.DateTime(),
					ReturnDate = c.DateTime(),
				})
			.PrimaryKey(t => t.Id)
			.ForeignKey("dbo.Books", t => t.BookId, cascadeDelete: true)
			.ForeignKey("dbo.People", t => t.BorrowerId, cascadeDelete: true)
			.Index(t => t.BookId)
			.Index(t => t.BorrowerId);
		
		CreateTable(
			"dbo.Comments",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					BookId = c.Int(nullable: false),
					CommentText = c.String(nullable: false, maxLength: 4000),
				})
			.PrimaryKey(t => t.Id)
			.ForeignKey("dbo.Books", t => t.BookId, cascadeDelete: true)
			.Index(t => t.BookId);
		
		CreateTable(
			"dbo.Publishers",
			c => new
				{
					Id = c.Int(nullable: false, identity: true),
					Name = c.String(nullable: false, maxLength: 4000),
				})
			.PrimaryKey(t => t.Id);
		
		CreateTable(
			"dbo.PersonBooks",
			c => new
				{
					Person_Id = c.Int(nullable: false),
					Book_Id = c.Int(nullable: false),
				})
			.PrimaryKey(t => new { t.Person_Id, t.Book_Id })
			.ForeignKey("dbo.People", t => t.Person_Id, cascadeDelete: true)
			.ForeignKey("dbo.Books", t => t.Book_Id, cascadeDelete: true)
			.Index(t => t.Person_Id)
			.Index(t => t.Book_Id);
		
	}
	
	public override void Down()
	{
		DropForeignKey("dbo.Books", "PublisherId", "dbo.Publishers");
		DropForeignKey("dbo.BookCovers", "BookId", "dbo.Books");
		DropForeignKey("dbo.Comments", "BookId", "dbo.Books");
		DropForeignKey("dbo.Lendings", "BorrowerId", "dbo.People");
		DropForeignKey("dbo.Lendings", "BookId", "dbo.Books");
		DropForeignKey("dbo.PersonBooks", "Book_Id", "dbo.Books");
		DropForeignKey("dbo.PersonBooks", "Person_Id", "dbo.People");
		DropIndex("dbo.Books", new[] { "PublisherId" });
		DropIndex("dbo.BookCovers", new[] { "BookId" });
		DropIndex("dbo.Comments", new[] { "BookId" });
		DropIndex("dbo.Lendings", new[] { "BorrowerId" });
		DropIndex("dbo.Lendings", new[] { "BookId" });
		DropIndex("dbo.PersonBooks", new[] { "Book_Id" });
		DropIndex("dbo.PersonBooks", new[] { "Person_Id" });
		DropTable("dbo.PersonBooks");
		DropTable("dbo.Publishers");
		DropTable("dbo.Comments");
		DropTable("dbo.Lendings");
		DropTable("dbo.People");
		DropTable("dbo.Books");
		DropTable("dbo.BookCovers");
	}
}

I then started running into difficulties. Strange things were happening and the SQL Server Compact database was not responding to my Migrations commands in the way I expected. So, I turned to Google.

As it turns out, there are two options for seeding the database using Code First and they are mutually exclusive:

  1. The original EF way of creating an Initializer which inherits from DropCreateDatabaseAlways or DropCreateDatabaseIfModelChanges. You can see the code for this option in my last post.
  2. Using Migrations, which uses the seed method in the Configuration file which inherits from DbMigrationsConfiguration

For this project, I preferred the original EF way as I have found it much simpler to work with. I am, however, going to try and have my cake and eat it. When I made this decision, I found that all I had to do to disable Migrations was to exclude the Migrations directory from my solution. However, if I do want to change the schema again and use Migrations to create a Migration, I can just include that folder again, comment out the code which sets the DatabaseInitializer and create a new Migration based on the current state of the domain classes. Let’s see whether this approach continues to work!

Home Library

An index of the HomeLibrary application posts can be found here.

This year I have set a little project for myself. A bit of background first. Last year I released a project called WinformsMVP which is an MVP framework for the Winforms platform. The example code that I provided with the source was quite trivial. Previous to that, in my first year as a programmer, I created a Winforms application which assisted my study for the Winforms MCTS certification. I thought it would be nice to take that application and re-implement it using the WinformsMVP framework. I will also take the opportunity to use up-to-date tools like Entity Framework 6 (code first) and perhaps a few other little utilities which I have come across in my travels.

I have already made a start on the application which I am going to call Home Library. It is basically an application which individuals can use to track their lendings of books to others. I’ve lost many books over the years where they have not been returned and I did not track who I lent it to. This application helped me address that and it was fun to make.

The code for this project is available at this GitHub repository. I plan to tag the code at various milestones and the tag for the current state of the code is called DataAccessAndDomain_1. Mind you, that doesn’t mean I won’t go back and re-factor code. But I think tagging it at various milestones will be helpful as I make blog posts with regards to the project, as it progresses.

I don’t have much in the way of Business Analysis skills; and in any case, I am the client and subject-matter expert here. So I got to work and created the domain classes which I used to generate my database. The database schema looks like this (diagram generated using the Entity Framework Power Tools): Home Library Schema

To give you an idea of the domain classes, here are a couple which I have created:

public class Book
{
	public int Id { get; set; }
	public string Title { get; set; }
	public Edition Edition { get; set; }
	public Publisher Publisher { get; set; }
	public int PublisherId { get; set; }
	public BookType TypeOfBook { get; set; }

	public virtual ICollection<Person> Authors { get; set; }
	public virtual ICollection<Comment> Comments { get; set; }
	public virtual ICollection<BookCover> Covers { get; set; }
	public virtual ICollection<Lending> Lendings { get; set; }
}
public class Person
{
	public int Id { get; set; }
	public string Email { get; set; }
	public bool IsAuthor { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string Sobriquet { get; set; }

	public virtual ICollection<Lending> Lendings { get; set; }
	public virtual ICollection<Book> Books { get; set; }
}

Those classes are in a separate project called HomeLibrary.Model.
You can see in the book class I have created a couple of enums. As Entity Framework 6 supports enums, it made sense to use them for abstractions which represented a finite number of options. The BookType enum looks like this:

public enum BookType
{
	TextBook = 0,
	Novel = 1
};

For the classes which will do the actual querying, I created a separate project called HomeLibrary.Model.EF. The HomeLibraryContext (which inherits from DbContext) is as follows:

public class HomeLibraryContext : DbContext
{
    //  DbSets go here
    public DbSet<Book> Books { get; set; }
    public DbSet<BookCover> BookCovers { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<Lending> Lendings { get; set; }
    public DbSet<Person> People { get; set; }
    public DbSet<Publisher> Publishers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //  set up the Publisher's table
        modelBuilder.Entity<Publisher>().HasKey(p => p.Id);
        modelBuilder.Entity<Publisher>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Publisher>().Property(p => p.Name).IsRequired().IsVariableLength();
            
        //  set up the People table
        modelBuilder.Entity<Person>().HasKey(p => p.Id);
        modelBuilder.Entity<Person>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Person>().Property(p => p.Email).IsRequired().IsVariableLength();
        modelBuilder.Entity<Person>().Property(p => p.FirstName).IsRequired().IsVariableLength();
        modelBuilder.Entity<Person>().Property(p => p.LastName).IsRequired().IsVariableLength();
        modelBuilder.Entity<Person>().Property(p => p.Sobriquet).IsOptional().IsVariableLength();

        //  set up the Comment table
        modelBuilder.Entity<Comment>().HasKey(p => p.Id);
        modelBuilder.Entity<Comment>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Comment>().Property(c => c.CommentText).IsRequired().IsVariableLength();

        //  set up the Book table
        modelBuilder.Entity<Book>().HasKey(p => p.Id);
        modelBuilder.Entity<Book>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Book>().Property(c => c.Title).IsRequired().IsVariableLength();

        modelBuilder.Entity<BookCover>().HasKey(p => p.Id);
        modelBuilder.Entity<BookCover>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        modelBuilder.Entity<Lending>().HasKey(p => p.Id);
        modelBuilder.Entity<Lending>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);


        base.OnModelCreating(modelBuilder);
    }
}

You can see that I have opted for using the Fluent API for setting up the various tables rather than the Attribute-based API. Otherwise, it is a very straightforward context.

I faced an interesting scenario with seeding which I will leave for a separate blog post. The upshot of it was that I elected to do the database creation and seeding using the original EF Code First method of inheriting from a class which implements IDatabaseInitializer. As I wanted to drop and re-create the database each time I ran the application during development, I opted to inherit from DropCreateDatabaseAlways. The HomeLibraryInitializer looks like:

public class HomeLibraryInitializer : DropCreateDatabaseAlways<HomeLibraryContext>
{
    protected override void Seed(HomeLibraryContext context)
    {
        new List<Person> { new Person { FirstName = "Terry", LastName = "Halpin", Email = "hi", IsAuthor = false},
            new Person { FirstName = "Alan", LastName = "Turing", Email = "hi", IsAuthor = false }
            , }.ForEach(p => context.People.Add(p));

        context.SaveChanges();

        base.Seed(context);
    }
}

Stay tuned for future posts!