Multi-Tenant Migrations With Entity Framework 6.0 (Code First Model)

Introduction

Up to Entity Framework 5.0, there were only single migrations possible to manage a single DbContext (user model) per physical database. Now Entity Framework 6.0 supports multiple model migrations per physical database.

This feature of Entity Framework is formerly known as "Multi-Tenant Migrations" or "Multiple Contexts per Database".

Generally a multi-tenant database is able to isolate various groups of tables that might to be used for different purposes or may be used for different applications. There are multiple ways to make database multi-tenant. One of the common methods is to use a different schema name for a different group of tables.

Entity Framework Code First model migration allows us to create a new database or update the existing database based on model classes. Entity Framework 5.0 Code First migration only supports one DbContext per physical database. Entity Framework 6.0 onward, Code First migration supports multiple DbContexts per single physical database.

The following is the procedure to migrate a Code First model.


Syntax


Step 1
  • enable-migrations -ContextTypeName -DbContext-Name-with-Namespaces-MigrationsDirectory:-Migrations-Directory-Name-
Step 2
  • Add-Migration -configuration -DbContext-Migrations-Configuration-Class-with-Namespaces-Migrations-Name-
Step 3
  • Update-Database -configuration -DbContext-Migrations-Configuration-Class-with-Namespaces -Verbose
Important Note

The Connection String must be the same as the ADO.NET Connection String and also the name provided must be “System.Data.SqlClient” instead of “System.Data.EntityClient” that is used in a Database First model.
  1. <configuration>  
  2. …..  
  3. …..  
  4. <connectionStrings>  
  5. <add name="Entities" connectionString="Data Source=serverName;Initial Catalog=DatabaseName;Persist Security Info=True;User ID=sa;Password=password ;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/>  
  6. </connectionStrings>  
  7. </configuration>  
Example

Suppose I have two DbContexts (say EmployeeContext and OderContext) within the same project. The Model classes and DBContext class code is given below.

Model Classes (Employee context)
  1. public class DepartmentMaster  
  2. {  
  3. [Key]  
  4. public int DepartmentId { getset; }  
  5. public string Code { getset; }  
  6. public string Name { getset; }  
  7. public List<EmployeeMaster> Employees { getset; }  
  8. }  
  9.   
  10. public class EmployeeMaster  
  11. {  
  12. [Key]  
  13. public int Employee { getset; }  
  14. public string Code { getset; }  
  15. public string Name { getset; }  
  16. public int DepartmentId { getset; }  
  17. public DepartmentMaster Department { getset; }  
  18. }  
Employee DbContext

  1. public class EmployeeContext : DbContext  
  2. {  
  3. public EmployeeContext()  
  4. base("name=Entities")  
  5. {  
  6.   
  7. }  
  8. public DbSet<DepartmentMaster> Departments { getset; }  
  9. public DbSet<EmployeeMaster> Employees { getset; }  
  10. }  
Model Classes (Order context)
  1. public class OrderMaster  
  2. {  
  3. [Key]  
  4. public int OrderId { getset; }  
  5. public DateTime OrderDate { getset; }  
  6. public double TotalAmount { getset; }  
  7. public List<OrderDetails> OrderDetails { getset; }  
  8. }  
  9.   
  10. public class OrderDetails  
  11. {  
  12. [Key]  
  13. public int OrderDetailId { getset; }  
  14. public int OrderId { getset; }  
  15. public string ItemName { getset; }  
  16. public int Quantity { getset; }  
  17. public string UnitPrice { getset; }  
  18. public double Amount { getset; }  
  19. public OrderMaster Order { getset; }  
  20. }  
Order context
  1. public class OrderContext : DbContext  
  2. {  
  3. public OrderContext()  
  4. base("name=Entities")  
  5. {  
  6.   
  7. }  
  8. public DbSet<OrderMaster> Orders { getset; }  
  9. public DbSet<OrderDetails> OrderDetails { getset; }  
  10. }  
Graphical representation of relationship




Code First migration Procedure


For migrating, the following commands must run using the Package Manager console of Visual Studio (2012 or 2013).

Step 1
  • Enable migration for DbContext
enable-migrations -ContextTypeName MultipleContextsperDatabase.Model.EmployeeContext -MigrationsDirectory:EmployeeMigrations

Output on Package Manager Console



As a result of Step 1, the Configuration class is added within the EmployeeMigration folder.



Configuration class definition
  1. internal sealed class Configuration : DbMigrationsConfiguration<MultipleContextsperDatabase.Model.EmployeeContext>  
  2. {  
  3. public Configuration()  
  4. {  
  5. AutomaticMigrationsEnabled = false;  
  6. MigrationsDirectory = @"EmployeeMigrations";  
  7. }  
  8.   
  9. protected override void Seed(MultipleContextsperDatabase.Model.EmployeeContext context)  
  10. {  
  11.   
  12. }  
  13. }  
Step 2: Add migration configuration

Add-Migration -configuration MultipleContextsperDatabase.EmployeeMigrations.Configuration InitialEmployee

Output on Package Manager Console



As a result of Step 2, the Db migration file is generated within the “EmployeeMigration” folder. This file has a name suffix as the Migrations name followed by underscore and a unique generated number. This file has all the entities to be created in the database.



Migration file Definition

  1. public partial class InitialEmployee : DbMigration  
  2. {  
  3. public override void Up()  
  4. {  
  5. CreateTable(  
  6. "dbo.DepartmentMasters",  
  7. c => new  
  8. {  
  9. DepartmentId = c.Int(nullable: false, identity: true),  
  10. Code = c.String(),  
  11. Name = c.String(),  
  12. })  
  13. .PrimaryKey(t => t.DepartmentId);  
  14.   
  15. CreateTable(  
  16. "dbo.EmployeeMasters",  
  17. c => new  
  18. {  
  19. Employee = c.Int(nullable: false, identity: true),  
  20. Code = c.String(),  
  21. Name = c.String(),  
  22. DepartmentId = c.Int(nullable: false),  
  23. })  
  24. .PrimaryKey(t => t.Employee)  
  25. .ForeignKey("dbo.DepartmentMasters", t => t.DepartmentId, cascadeDelete: true)  
  26. .Index(t => t.DepartmentId);  
  27.   
  28. }  
  29.   
  30. public override void Down()  
  31. {  
  32. DropForeignKey("dbo.EmployeeMasters""DepartmentId""dbo.DepartmentMasters");  
  33. DropIndex("dbo.EmployeeMasters"new[] { "DepartmentId" });  
  34. DropTable("dbo.EmployeeMasters");  
  35. DropTable("dbo.DepartmentMasters");  
  36. }  
  37. }  
Step 3: Update database

Update-Database -configuration:MultipleContextsperDatabase.EmployeeMigrations.Configuration -Verbose

Output on Package Manager Console

 
After executing the command above a new something will be created (or update the existing database) that is supplied in the initial catalog property of the Connection String.



A migration history table is a table that stores the details of migrations applied to the database in a Code First model. It means that the migration history table contains all the migration changes to the database. The default name of this table is _MigrationHistory and it is created when a first migration is applied to the database. In Entity Framework 5.0, this is a system table (If application uses MSSQL). In the Entity Framework 6.0, this table is no longer marked as a system table.

Output of migration history table


The same procedure as described above must be followed for OderDbContext.

The following command must be run one by one by using the Package Manager console of Visual Studio (2012 or 2013).

enable-migrations -ContextTypeName:MultipleContextsperDatabase.Model.OrderContext -MigrationsDirectory:OrderMigrations

Add-Migration -configuration MultipleContextsperDatabase.OrderMigrations.Configuration InitialOrder

Update-Database -configuration:MultipleContextsperDatabase.OrderMigrations.Configuration -Verbose

Configuration class definition (generated by Step-1)
  1. internal sealed class Configuration : DbMigrationsConfiguration<MultipleContextsperDatabase.Model.OrderContext>  
  2. {  
  3. public Configuration()  
  4. {  
  5. AutomaticMigrationsEnabled = false;  
  6. MigrationsDirectory = @"OrderMigrations";  
  7. }  
  8.   
  9. protected override void Seed(MultipleContextsperDatabase.Model.OrderContext context)  
  10. {  
  11.   
  12. }  
  13. }  
Migrations file Definition (Generated by Step-2)
  1. public partial class InitialOrder : DbMigration  
  2. {  
  3. public override void Up()  
  4. {  
  5. CreateTable(  
  6. "dbo.OrderDetails",  
  7. c => new  
  8. {  
  9. OrderDetailId = c.Int(nullable: false, identity: true),  
  10. OrderId = c.Int(nullable: false),  
  11. ItemName = c.String(),  
  12. Quantity = c.Int(nullable: false),  
  13. UnitPrice = c.String(),  
  14. Amount = c.Double(nullable: false),  
  15. })  
  16. .PrimaryKey(t => t.OrderDetailId)  
  17. .ForeignKey("dbo.OrderMasters", t => t.OrderId, cascadeDelete: true)  
  18. .Index(t => t.OrderId);  
  19.   
  20. CreateTable(  
  21. "dbo.OrderMasters",  
  22. c => new  
  23. {  
  24. OrderId = c.Int(nullable: false, identity: true),  
  25. OrderDate = c.DateTime(nullable: false),  
  26. TotalAmount = c.Double(nullable: false),  
  27. })  
  28. .PrimaryKey(t => t.OrderId);  
  29.   
  30. }  
  31.   
  32. public override void Down()  
  33. {  
  34. DropForeignKey("dbo.OrderDetails""OrderId""dbo.OrderMasters");  
  35. DropIndex("dbo.OrderDetails"new[] { "OrderId" });  
  36. DropTable("dbo.OrderMasters");  
  37. DropTable("dbo.OrderDetails");  
  38. }  
  39. }  
Table generate by the step-3





Output of migration history table




Undo/Rollback DbContexts Migrations

We can also roll back database changes using the following command.

Update-Database -configuration MultipleContextsperDatabase.EmployeeMigrations.Configuration -TargetMigration:"201407221118333_InitialEmployee" –verbose

Update-Database -configuration MultipleContextsperDatabase.OrderMigrations.Configuration -TargetMigration:"201407221128534_InitialOrder" -verbose

Output on Package Manager Console



Summary
 
Entity Framework has many new features, Multi-Tenant migration is one of them. With this feature we can get the same functionality as a multi-tenant database. I hope this will help you.