Code First Approach Headlines and Tips

From Logic Wiki
Jump to: navigation, search


http://www.entityframeworktutorial.net/

One To One / Zero Relationship

public class Student
   {
       public Student() { }

       public int StudentId { get; set; }
       public string StudentName { get; set; }
      // Ref to StudentAddress class
       public virtual StudentAddress StudentAddress { get; set; }

   }
    
   public class StudentAddress 
   {
       [Key, ForeignKey("Student")]
       public int StudentId { get; set; }
        
       public string Address1 { get; set; }
       public string Address2 { get; set; }
       public string City { get; set; }
       public int Zipcode { get; set; }
       public string State { get; set; }
       public string Country { get; set; }
       // Ref to Student Class
       public virtual Student Student { get; set; }
   }

One To Many Relationship

public class Student
   {
       public Student()
       { 
       
       }
       public int StudentID { get; set; }
       public string StudentName { get; set; }
       public DateTime DateOfBirth { get; set; }
       public byte[]  Photo { get; set; }
       public decimal Height { get; set; }
       public float Weight { get; set; }
       
       //Navigation property (Many)
       public Standard Standard { get; set; }
   }
   public class Standard
   {
       public Standard()
       { 
       
       }
       public int StandardId { get; set; }
       public string StandardName { get; set; }
   
       //Collection navigation property (One)
       public IList<Student> Students { get; set; }
  
    }

Default code first convention for relationship automatically inserts foreign key with <navigation property Name>_<primary key property name of navigation property type> e.g. Standard_StandardId.

If you specify classname + ID syntax as foreign key it creates StandardID instead of Standard_StandardId. ie :

//Foreign key for Standard
       public int StandardId { get; set; }
       public Standard Standard { get; set; }

If you modify data type of StandardId property from int to Nullable<int> in the Student class above it creates nullable foreign key column in the Students table.


Many To Many Relationship

Student class should have a collection navigation property for Course, and Course should have a collection navigation property for student, which will create a Many-to-Many relationship between student and course as shown below:

 public class Student
   {
       public Student() { }

       public int StudentId { get; set; }
       [Required]
       public string StudentName { get; set; }

       public int StdandardId { get; set; }
       
       public virtual ICollection<Course> Courses { get; set; }
   }
       
   public class Course
   {
       public Course()
       {
           this.Students = new HashSet<Student>();
       }

       public int CourseId { get; set; }
       public string CourseName { get; set; }

       public virtual ICollection<Student> Students { get; set; }
   }


Database Initialization Strategies in Code-First

There are four different database Initialization strategies:

CreateDatabaseIfNotExists:

This is default initializer. As name suggests, it will create the database if none exists as per the configuration. However, if you change the model class and then run the application with this initializer, then it will throw an exception.

DropCreateDatabaseIfModelChanges:

This initializer drops an existing database and creates a new database, if your model classes (entity classes) have been changed. So you don't have to worry about maintaining your database schema, when your model classes change.

DropCreateDatabaseAlways:

As the name suggests, this initializer drops an existing database every time you run the application, irrespective of whether your model classes have changed or not. This will be useful, when you want fresh database, every time you run the application, while you are developing the application.

Custom DB Initializer:

You can also create your own custom initializer, if any of the above don't satisfy your requirements or you want to do some other process that initializes the database using above initializer.

To use one of the above DB initialization strategies, you have to set the DB Initializer using Database class in Context class as following:

   public class SchoolDBContext: DbContext 
   {
       
       public SchoolDBContext(): base("SchoolDBConnectionString") 
       {
           Database.SetInitializer<SchoolDBContext>(new CreateDatabaseIfNotExists<SchoolDBContext>());

           //Database.SetInitializer<SchoolDBContext>(new DropCreateDatabaseIfModelChanges<SchoolDBContext>());
           //Database.SetInitializer<SchoolDBContext>(new DropCreateDatabaseAlways<SchoolDBContext>());
           //Database.SetInitializer<SchoolDBContext>(new SchoolDBInitializer());
       }
       public DbSet<Student> Students { get; set; }
       public DbSet<Standard> Standards { get; set; }
   }

You can also create your custom DB initializer, by inheriting one of the intializers as shown below:

   public class SchoolDBInitializer :  CreateDatabaseIfNotExists<SchoolDBContext>
   {
       protected override void Seed(SchoolDBContext context)
       {
           base.Seed(context);
       }
   }

Set db initializer in the configuration file:

You can also set db initializer in the configuration file. For example, to set default initializer in app.config:


   <?xml version="1.0" encoding="utf-8" ?>
   <configuration>
     <appSettings>
       <add key="DatabaseInitializerForType SchoolDataLayer.SchoolDBContext, SchoolDataLayer"         
           value="System.Data.Entity.DropCreateDatabaseAlways`1SchoolDataLayer.SchoolDBContext, SchoolDataLayer, EntityFramework" />
     </appSettings>
   </configuration>

You can set custom db initializer as following:

   <?xml version="1.0" encoding="utf-8" ?>
   <configuration>
     <appSettings>    
       <add key="DatabaseInitializerForType SchoolDataLayer.SchoolDBContext, SchoolDataLayer"
            value="SchoolDataLayer.SchoolDBInitializer, SchoolDataLayer" />
     </appSettings>
   </configuration>
       

Turn off DB Initializer in Code-First:

You can also turn off DB initializer of your application. Suppose for the production environment you don't want to lose existing data, then you can turn off initializer as shown in the following:

   public class SchoolDBContext: DbContext 
   {
       public SchoolDBContext() : base("SchoolDBConnectionString")
       {            
           //Disable initializer
           Database.SetInitializer<SchoolDBContext>(null);
       }
       public DbSet<Student> Students { get; set; }
       public DbSet<Standard> Standards { get; set; }
   }

You can also turn off the initializer in the configuration file, for example:

   <?xml version="1.0" encoding="utf-8" ?>
   <configuration>
     <appSettings>    
       <add key="DatabaseInitializerForType SchoolDataLayer.SchoolDBContext, SchoolDataLayer"
            value="Disabled" />
     </appSettings>
   </configuration>

Seed Database in Code-First:

You can insert data into your database tables during the database initialization process. This will be important if you want to provide some test data for your application or to provide some default master data for your application.

To seed data into your database, you have to create custom DB initializer, as you created in DB Initialization Strategy, and override the Seed method. The following example shows how you can provide default data for the Standard table while initializing the School database:

   public class SchoolDBInitializer : DropCreateDatabaseAlways<SchoolDBContext>
   {
       protected override void Seed(SchoolDBContext context)
       {
           IList<Standard> defaultStandards = new List<Standard>();

           defaultStandards.Add(new Standard() { StandardName = "Standard 1", Description = "First Standard" });
           defaultStandards.Add(new Standard() { StandardName = "Standard 2", Description = "Second Standard" });
           defaultStandards.Add(new Standard() { StandardName = "Standard 3", Description = "Third Standard" });

           foreach (Standard std in defaultStandards)
               context.Standards.Add(std);

           base.Seed(context);
       }
   }

Now, set this db initializer class in context class as below.

   public class SchoolContext: DbContext 
   {
       public SchoolContext(): base("SchoolDBConnectionString") 
       {
           Database.SetInitializer<SchoolContext>(new SchoolDBInitializer<SchoolContext>());
       }
       public DbSet<Student> Students { get; set; }
       public DbSet<Standard> Standards { get; set; }
   }

Migration in Code-First:

Entity framework 4.3 has introduced migration that automatically updates database schema, when your model changes without losing any existing data or other database objects. It uses new database initializer called MigrateDatabaseToLatestVersion.

There are two kinds of Migration:

Automated Migration:

As you know, you don’t have a database when you start writing Code-First application. For example, we start writing an application with Student and Course entity classes. Before running the application which does not have its database created yet, you have to enable automated migration by running the ‘enable-migrations’ command in Package Manager Console as shown below:

First, open the package manager console from Tools → Library Package Manager → Package Manager Console and then run "enable-migrations –EnableAutomaticMigration:$true" command (make sure that the default project is the project where your context class is)

Once the command runs successfully, it creates an internal sealed Configuration class in the Migration folder in your project: If you open this and see the class shown below, then you will find AutomaticMigrationsEnabled = true in the constructor.


   internal sealed class Configuration : DbMigrationsConfiguration<SchoolDataLayer.SchoolDBContext>
   {
       public Configuration()
       {
           AutomaticMigrationsEnabled = true;
       }

       protected override void Seed(SchoolDataLayer.SchoolDBContext context)
       {
           //  This method will be called after migrating to the latest version.

           //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
           //  to avoid creating duplicate seed data. E.g.
           //
           //    context.People.AddOrUpdate(
           //      p => p.FullName,
           //      new Person { FullName = "Andrew Peters" },
           //      new Person { FullName = "Brice Lambson" },
           //      new Person { FullName = "Rowan Miller" }
           //    );
           //
       }
   }
       

You also need to set the database initializer in the context class with the new db initialization strategy MigrateDatabaseToLatestVersion as shown below:

   public class SchoolDBContext: DbContext 
   {
       public SchoolDBContext(): base("SchoolDBConnectionString") 
       {
           Database.SetInitializer(new MigrateDatabaseToLatestVersion<SchoolDBContext, SchoolDataLayer.Migrations.Configuration>("SchoolDBConnectionString"));
            
       }

       public DbSet<Student> Students { get; set; }
       public DbSet<Course> Courses { get; set; }
        
       protected override void OnModelCreating(DbModelBuilder modelBuilder)
       {
            base.OnModelCreating(modelBuilder);
       }
    }
       

As you can see in the code shown above, we have passed the Configuration class name, which was created by command, along with context class name.

Now you are set for automated migration. It will automatically take care of migration, when you change the model. Run the application and look at the created database: You will find that it has created one system table __MigrationHistory along with other tables. This is where automated migration maintains the history of database changes.

Now let’s add a Standard entity class. Run the application again and you will see that it has automatically created a Standard table.

Wait a minute, this work if you add new entity class or remove entity class, but what about adding or removing properties from the entity class? To try that, let’s remove the Description property from Standard class and run the application.

Oops…. an error message will appear:

This is because you will lose data in description column, if you remove it from the Standard class. So to handle this kind of scenario, you have to set AutomaticMigrationDataLossAllowed = true in the configuration class constructor (Configuration.cs->public Configuration()), along with AutomaticMigrationsEnabled = true.

Note: You can find more information about parameters that we can pass to the enable-migrations command using the "get-help enable-migrations" command. For more detailed help use "get-help enable-migrations –detailed"

Code-based Migration:

Code based migration is useful when you want more control on migration i.e. set default value of the column etc.

Code-First has two commands for code based migration:

  1. Add-migration: It will scaffold the next migration for the changes you have made to your domain classes
  2. Update-database: It will apply pending changes to the database based on latest scaffolding code file you create using "Add-Migration" command

Assume that you have Student and Course entity classes initially and you want to use code based migration for your application. So before running commands above, you must enable migration for your application, by using the enable-migrations commands. These are in package manger that we used previously for automatic migration. This will create a configuration file, as was the case with automated migration. Also, you need to set the database initializer in the context class:


public class SchoolDBContext: DbContext

   {
       public SchoolDBContext(): base("SchoolDBConnectionString") 
       {
           Database.SetInitializer(new MigrateDatabaseToLatestVersion<SchoolDBContext, SchoolDataLayer.Migrations.Configuration>("SchoolDBConnectionString"));
       }

       public DbSet<Student> Students { get; set; }
       public DbSet<Course> Courses { get; set; }
       
       protected override void OnModelCreating(DbModelBuilder modelBuilder)
       {
           base.OnModelCreating(modelBuilder);
       }
   }
       

Now, you have to create a scaffold code file which consists of your database requirement from your existing domain classes. You can do this by running the “add-migration" command in the package manger. (from Tools → Library Package Manager → Package Manager Console). You will have to pass the name parameter, which will be part of code file name.

Add-Migration command Syntax:

   Add-Migration [-Name] <String> [-Force]
     [-ProjectName <String>] [-StartUpProjectName <String>]
     [-ConfigurationTypeName <String>] [-ConnectionStringName <String>]
     [-IgnoreChanges] [<CommonParameters>]

   Add-Migration [-Name] <String> [-Force]
     [-ProjectName <String>] [-StartUpProjectName <String>]
     [-ConfigurationTypeName <String>] -ConnectionString <String>
     -ConnectionProviderName <String> [-IgnoreChanges] [<Common Parameters>]

You can see that this command has created a new file in the Migration folder with the name parameter you passed to the command with a timestamp prefix:

After creating the file above using the add-migration command, you have to update the database. You can create or update the database using the “update-database” command. You can use –verbose to see what’s going on in the database:

Update-Database command syntax:

 Update-Database [-SourceMigration <String>]
     [-TargetMigration <String>] [-Script] [-Force] [-ProjectName <String>]
     [-StartUpProjectName <String>] [-ConfigurationTypeName <String>]
     [-ConnectionStringName <String>] [<CommonParameters>]

   Update-Database [-SourceMigration <String>] [-TargetMigration <String>]
     [-Script] [-Force] [-ProjectName <String>] [-StartUpProjectName <String>]
     [-ConfigurationTypeName <String>] -ConnectionString <String>
     -ConnectionProviderName <String> [<CommonParameters>]

At this point, the database will be created or updated.

Now suppose you added more domain classes. So before running the application, you have to create a scaffold file for new classes, by executing the "Add-Migration" command. Once it creates the file, update the database using the Update-Database command. So in this way you have to repeat the Add-Migration and Update-Database command each time you make any changes in your domain classes.

Rollback Database change:

Suppose you want to roll back the database schema to any of the previous states, then you can use Update-database command with –TargetMigration parameter as shown below:

update-database -TargetMigration:"First School DB schema"

Use the "get-migration" command to see what migration has been applied.

Note: Use the "get-help" command for add-migration and update-database command in order to see what are the parameters we can pass with this command.