[渣譯文] 使用 MVC 5 的 EF6 Code First 入門 系列:MVC程序中實體框架的Code First遷移和部署

這是微軟官方SignalR 2.0教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這裏是第五篇:MVC程序中實體框架的Code First遷移和部署程序員

原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application數據庫

譯文版權全部,謝絕全文轉載——但您能夠在您的網站上添加到該教程的連接。windows

到目前爲止,應用程序已經能夠在您本地機器上正常地運行。但若是您想將它發佈在互聯網上以便更多的人來使用,您須要將程序部署到WEB服務器。在本教程中你會將Contoso大學應用程序部署到Windows Azure網站的雲中。服務器

本教程包含如下章節:架構

  • 啓用Code First遷移。遷移功能可以使您沒必要刪除並從新建立數據庫的數據架構而進行更改數據模型並部署你的更改到生產環境下。
  • 將應用程序部署到Windows Azure。該步驟是可選的,你能夠跳過本步驟繼續剩餘的教程。

咱們建議使用源代碼管理的持續集成過程部署,但本教程並不包含那些主題。更多的信息請參見source controlBuilding Real-World Cloud Apps with Windows Azuremvc

啓用Code First遷移

當你進行新應用程序的開發時,你的數據模型會頻繁地變更。而且隨着每一次變更都會使數據模型與數據庫脫節。你已經成功配置了實體框架讓其在每一次你變動數據模型時自動刪除並從新建立數據庫。當您添加、刪除或更改實體類或者更改你的DbContext類時,從新運行應用程序會使它自動刪除已經存在的數據庫並建立一個和當前數據模型相匹配的數據庫。而且填充測試數據。app

這種方法在保持數據模型和數據庫架構同步方面作得很是好,直到你準備將應用程序部署到生產環境。當應用程序開始生產並存儲生產數據,你固然不想由於數據模型的變動而丟失成產數據(好比添加一個新列)。Code First Migrations功能解決了這個問題。經過啓用Code First遷移來更新數據庫架構,而不是刪除和重建數據庫。在本教程中,您會部署該應用程序,並準備啓用遷移。框架

  1. 禁用以前教程中你在Web.Config中設定的初始設定項。
      <entityFramework>
        <!--<contexts>
          <context type="ContosoUniversity.DAL.SchoolContext,ContosoUniversity">
            <databaseInitializer type="ContosoUniversity.DAL.SchoolInitializer,ContosoUniversity"></databaseInitializer>
          </context>
        </contexts>-->
        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
          <parameters>
            <parameter value="v11.0" />
          </parameters>
        </defaultConnectionFactory>
        <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
        </providers>
      </entityFramework>
  2. 一樣在Web.config中,更改數據庫鏈接字符串的名爲成ContosoUniversity2。
      <connectionStrings>
        <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\ContosoUniversity1.mdf" providerName="System.Data.SqlClient" />
      </connectionStrings>

    此更改設置該項目的第一次遷移將建立一個新的數據庫,這不是必須的,但您稍後將看到爲這麼這樣作是一個不錯的主意。asp.net

  3. 從工具菜單上,單擊NuGet程序包管理器,單擊程序包管理器控制檯。
  4. 在控制檯中輸入如下命令:
    enable-migrations
    add-migration InitialCreate 


    enable-migrations命令將在項目中建立一個遷移文件夾。同時文件夾中包含一個Configuration.cs文件,你能夠編輯該文件來配置遷移。
    若是你在上一步中沒有更改數據庫名稱,遷移將找到現有的數據庫並自動執行add-migration命令,這沒有關係。它只是意味着你不會在部署數據庫以前運行遷移代碼的測試。以後當您運行update-database將不會作任何改變由於數據庫已經存在。

    如同以前教程中,Configuration類中一樣包含Seed方法。ide

    internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext>
        {
            public Configuration()
            {
                AutomaticMigrationsEnabled = false;
                ContextKey = "ContosoUniversity.DAL.SchoolContext";
            }
    
            protected override void Seed(ContosoUniversity.DAL.SchoolContext 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" }
                //    );
                //
            }
        }

    Seed方法的目的是使您在Code First建立或更新數據庫後插入或更新測試數據。當數據庫每次建立和更新數據庫架構時將調用該方法。

設置Seed方法

當您每次更改數據模型後,刪除和從新建立數據庫時你可使用初始類的Seed方法來插入測試數據。由於每次模型更改數據庫後,數據庫將被刪除,全部的測試數據都將丟失。在Code First前一種,測試數據在數據庫更改後是保留的。因此在Seed方法中包含測試數據一般不是必要的。事實上,你並不想要在使用遷移部署數據庫到生產環境時讓Seed方法來插入測試數據,由於Seed方法會在生產環境中調用。在這種狀況下,只有真正須要時,才使用Seed方法來在生產環境中插入數據。例如你可能想要在部署到生產環境時在Deparment表中包含實際部門的名稱。

對於本教程,您將使用遷移來部署。但爲了讓你可以跟容易地看到程序功能是如何無需人工操做而插入數據的,咱們將使用Seed方法來插入測試數據。

  1. 使用下面的代碼替換Configuration.cs文件的內容:
      1 namespace ContosoUniversity.Migrations
      2 {
      3     using ContosoUniversity.Models;
      4     using System;
      5     using System.Collections.Generic;
      6     using System.Data.Entity;
      7     using System.Data.Entity.Migrations;
      8     using System.Linq;
      9 
     10     internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext>
     11     {
     12         public Configuration()
     13         {
     14             AutomaticMigrationsEnabled = false;
     15             ContextKey = "ContosoUniversity.DAL.SchoolContext";
     16         }
     17         protected override void Seed(ContosoUniversity.DAL.SchoolContext context)
     18         {
     19             var students = new List<Student>
     20             {
     21                 new Student { FirstMidName = "Carson",   LastName = "Alexander", 
     22                     EnrollmentDate = DateTime.Parse("2010-09-01") },
     23                 new Student { FirstMidName = "Meredith", LastName = "Alonso",    
     24                     EnrollmentDate = DateTime.Parse("2012-09-01") },
     25                 new Student { FirstMidName = "Arturo",   LastName = "Anand",     
     26                     EnrollmentDate = DateTime.Parse("2013-09-01") },
     27                 new Student { FirstMidName = "Gytis",    LastName = "Barzdukas", 
     28                     EnrollmentDate = DateTime.Parse("2012-09-01") },
     29                 new Student { FirstMidName = "Yan",      LastName = "Li",        
     30                     EnrollmentDate = DateTime.Parse("2012-09-01") },
     31                 new Student { FirstMidName = "Peggy",    LastName = "Justice",   
     32                     EnrollmentDate = DateTime.Parse("2011-09-01") },
     33                 new Student { FirstMidName = "Laura",    LastName = "Norman",    
     34                     EnrollmentDate = DateTime.Parse("2013-09-01") },
     35                 new Student { FirstMidName = "Nino",     LastName = "Olivetto",  
     36                     EnrollmentDate = DateTime.Parse("2005-08-11") }
     37             };
     38             students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
     39             context.SaveChanges();
     40 
     41             var courses = new List<Course>
     42             {
     43                 new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3, },
     44                 new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3, },
     45                 new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3, },
     46                 new Course {CourseID = 1045, Title = "Calculus",       Credits = 4, },
     47                 new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4, },
     48                 new Course {CourseID = 2021, Title = "Composition",    Credits = 3, },
     49                 new Course {CourseID = 2042, Title = "Literature",     Credits = 4, }
     50             };
     51             courses.ForEach(s => context.Courses.AddOrUpdate(p => p.Title, s));
     52             context.SaveChanges();
     53 
     54             var enrollments = new List<Enrollment>
     55             {
     56                 new Enrollment { 
     57                     StudentID = students.Single(s => s.LastName == "Alexander").ID, 
     58                     CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
     59                     Grade = Grade.A 
     60                 },
     61                  new Enrollment { 
     62                     StudentID = students.Single(s => s.LastName == "Alexander").ID,
     63                     CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
     64                     Grade = Grade.C 
     65                  },                            
     66                  new Enrollment { 
     67                     StudentID = students.Single(s => s.LastName == "Alexander").ID,
     68                     CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
     69                     Grade = Grade.B
     70                  },
     71                  new Enrollment { 
     72                      StudentID = students.Single(s => s.LastName == "Alonso").ID,
     73                     CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
     74                     Grade = Grade.B 
     75                  },
     76                  new Enrollment { 
     77                      StudentID = students.Single(s => s.LastName == "Alonso").ID,
     78                     CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
     79                     Grade = Grade.B 
     80                  },
     81                  new Enrollment {
     82                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
     83                     CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
     84                     Grade = Grade.B 
     85                  },
     86                  new Enrollment { 
     87                     StudentID = students.Single(s => s.LastName == "Anand").ID,
     88                     CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
     89                  },
     90                  new Enrollment { 
     91                     StudentID = students.Single(s => s.LastName == "Anand").ID,
     92                     CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
     93                     Grade = Grade.B         
     94                  },
     95                 new Enrollment { 
     96                     StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
     97                     CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
     98                     Grade = Grade.B         
     99                  },
    100                  new Enrollment { 
    101                     StudentID = students.Single(s => s.LastName == "Li").ID,
    102                     CourseID = courses.Single(c => c.Title == "Composition").CourseID,
    103                     Grade = Grade.B         
    104                  },
    105                  new Enrollment { 
    106                     StudentID = students.Single(s => s.LastName == "Justice").ID,
    107                     CourseID = courses.Single(c => c.Title == "Literature").CourseID,
    108                     Grade = Grade.B         
    109                  }
    110             };
    111 
    112             foreach (Enrollment e in enrollments)
    113             {
    114                 var enrollmentInDataBase = context.Enrollments.Where(
    115                     s =>
    116                          s.Student.ID == e.StudentID &&
    117                          s.Course.CourseID == e.CourseID).SingleOrDefault();
    118                 if (enrollmentInDataBase == null)
    119                 {
    120                     context.Enrollments.Add(e);
    121                 }
    122             }
    123             context.SaveChanges();
    124         }
    125     }
    126 }

    Seed方法使用數據庫上下文對象做爲輸入參數,並在代碼中使用該對象來添加新實體到數據庫。對於每一個實體類型,代碼建立一個新實體的集合並將它們添加到適當的DbSet屬性,而後將更改保存到數據庫。在每組實體後馬上調用SaveChanges方法並非必須的,但這樣作能夠在出現問題時讓你更容易地定位問題的根源。

    大多數插入對象的語句是使用AddOrUpdate方法來執行"upsert"操做。由於你每次執行更新數據庫命令時Seed方法都會運行,一般在每一個遷移後你不能只是插入數據。由於您試圖添加的行有可能在建立數據庫後的第一次遷移中已經存在。"upsert"操做能夠防止你試圖添加一個已經存在的行,可是它會重寫你在測試階段對數據進行的修改。你或許不但願這種狀況在某些數據表中發生:在某些狀況下你可能但願保留你在測試階段對測試數據所進行的更改。在這種狀況下,你須要作一個條件插入操做:僅當它不存在時插入行。Seed方法同時使用以上兩種方法。
    第一個傳遞給AddOrUpdate方法的參數,一個指定的屬性是用來檢查否行已經存在。對於您提供的測試學生數據,LastName屬性能夠被用做檢查在每一個列表中實體是不是惟一的。
    context.Students.AddOrUpdate(p => p.LastName, s)

     此代碼假定LastName是惟一的。若是您手動添加具備重複LastName的學生,你就會獲得一個「序列包含多個元素」的異常。
    有關如何處理容易數據,請參閱Seeding and Debugging Entity Framework (EF) DBs。有關AddOrUpdate方法的更多信息,請參閱Take care with EF 4.3 AddOrUpdate Method
    建立Enrollment實體的代碼假定你在學生集合中的實體已經擁有ID值,雖然你沒有在建立集合的代碼中設置該值。

                     new Enrollment { 
                         StudentID = students.Single(s => s.LastName == "Alonso").ID,
                        CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                        Grade = Grade.B 
                     },

    你在這裏可使用ID值,由於當你爲學生集合調用SaveChanges方法時,ID值被設置。當實體被插入到數據庫時,實體框架會自動獲取該實體的主鍵值而且更新內存中實體上的ID屬性。
    添加每一個Enrollment實體到Enrollments實體集合的代碼不會使用AddOrUpdate方法,它會檢查每個實體是否存在,若是不存在,則插入該實體。這種方法將保留經過使用應用程序UI對成績所作的修改。代碼遍歷Enrollment列表中的每一個成員。若是在數據庫中沒有該成員,就向數據庫中添加它。當你第一次更新數據庫時,該數據庫是空的,因此集合中的每一個enrollment實體都將被添加。

                foreach (Enrollment e in enrollments)
                {
                    var enrollmentInDataBase = context.Enrollments.Where(
                        s =>
                             s.Student.ID == e.StudentID &&
                             s.Course.CourseID == e.CourseID).SingleOrDefault();
                    if (enrollmentInDataBase == null)
                    {
                        context.Enrollments.Add(e);
                    }
                }
  2. 生成項目。

執行第一次遷移

當您執行add-migration命令時,遷移將生成代碼用來建立數據庫。該代碼一樣在Migrations文件夾中,在文件名爲<時間戳>_InitalCreate.cs的文件中。該類中的Up方法將按照數據模型實體集來建立數據庫表格,Down方法用來刪除它們。

namespace ContosoUniversity.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.Course",
                c => new
                    {
                        CourseID = c.Int(nullable: false),
                        Title = c.String(),
                        Credits = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.CourseID);
            
            CreateTable(
                "dbo.Enrollment",
                c => new
                    {
                        EnrollmentID = c.Int(nullable: false, identity: true),
                        CourseID = c.Int(nullable: false),
                        StudentID = c.Int(nullable: false),
                        Grade = c.Int(),
                    })
                .PrimaryKey(t => t.EnrollmentID)
                .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
                .ForeignKey("dbo.Student", t => t.StudentID, cascadeDelete: true)
                .Index(t => t.CourseID)
                .Index(t => t.StudentID);
            
            CreateTable(
                "dbo.Student",
                c => new
                    {
                        ID = c.Int(nullable: false, identity: true),
                        LastName = c.String(),
                        FirstMidName = c.String(),
                        EnrollmentDate = c.DateTime(nullable: false),
                    })
                .PrimaryKey(t => t.ID);
            
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
            DropForeignKey("dbo.Enrollment", "CourseID", "dbo.Course");
            DropIndex("dbo.Enrollment", new[] { "StudentID" });
            DropIndex("dbo.Enrollment", new[] { "CourseID" });
            DropTable("dbo.Student");
            DropTable("dbo.Enrollment");
            DropTable("dbo.Course");
        }
    }
}

遷移調用Up方法來實現數據模型所作的更改。當你輸入一個命令回滾更新,遷移將調用Down方法。

這是您輸入add-migration InitialCreate命令時建立的初始遷移。參數(在該示例中是InitialCreate)用於文件的名稱,固然也能夠是任意你想要的其餘名稱。一般你會選擇一個單詞或短語來總結遷移中所作的改變。例如您能夠能會命名以後的遷移爲"AddDeparmentTable"。

若是你建立了一個在數據庫已經存在的狀況下的遷移,則生成的的數據庫建立代碼不會運行。由於數據庫已經和數據模型匹配。將應用程序部署到另外一個還沒有建立數據庫的環境時,代碼纔會運行以建立數據庫。因此最好是提早測試一下。這就是爲何以前你更改了鏈接字符串中數據庫的名稱,以便遷移能夠從零開始建立一個新的數據庫。

  1. 在程序包管理器控制檯中,輸入如下命令:
    update-database

     

    update-database命令運行Up方法來建立數據庫,而後運行Seed方法來填充數據庫。一樣的過程會在將程序部署到生產環境下發生,您將會在下一節看到。
  2. 使用服務器資管管理器來檢查數據庫,驗證裏面的數據和程序同以前同樣運行正常。

部署到Windows Azure

由於咱沒有Windows Azure的試用帳號,因此這部分翻譯就跳過了……反正也不影響學習的。

總結

在本節中你看到了如何使用Code First遷移,在下一節中你會開始進入高級主題,擴展數據模型。

做者信息

  Tom Dykstra - Tom Dykstra是微軟Web平臺及工具團隊的高級程序員,做家。

相關文章
相關標籤/搜索