【譯著】Code First :使用Entity. Framework編程(6)


Chapter6

Controlling Database Location,Creation Process, and Seed Data

第6章

控制數據庫位置,創建過程和種子數據

In previous chapters you have seen how convention and configuration can be used to affect the model and the resulting database schema. In this chapter you will see how the convention and configuration concept applies to the database that is used by Code First.
You’ll learn how Code First conventions select a database and how you can alter this convention or specify the exact database that your context should use. The topics we cover will help you target other database providers, deploy your application, and perform many other database-related tasks.
You’ll also discover how database initializers can be used to control the database creation process and insert seed data into the database. This can be particularly useful when writing automated scenario tests.

前面我們已經提到默認規則和配置可以用於影響模型和數據庫構架。本章你會看到如何使用Code First來控制數據庫。

你將會學到Code First默認配置如何選擇數據庫,也會掌握如何修改默認規則或指定上下文使用真正的數據庫。我們覆蓋的主題將包括指向其他數據庫引擎,部署應用程序,執行數據庫有關的任何等。

你也可以學習到數據庫初始化器可以用於控制數據庫生成過程,添加種子數據到數據庫中等。這在進行自動測試的場景時特別有用。

Controlling the Database Location
控制數據庫位置


So far you have relied on the Code First convention to select which database the application targets. By default, Code First has created the database on localhost\SQLEXPRESS using the fully qualified name of your context class for the database name (i.e.,the namespace plus the class name). There will be times when this won’t be appropriate and you need to override the convention and tell Code First which database to connect to. You can modify or replace the convention used to select a database using Code First connection factories. Alternatively, you can just tell Code First exactly which database to use for a particular context, using the DbContext constructors or your application configuration file.

到目前為止我們都是引用了Code First的默認規則來選擇應用程序的數據庫目標。默認情況,Code First會使用localhost\SQLEXPRESS作為目標數據庫引擎,並使用context類的全名作為數據庫名(即命名空間+類名)。如果不符合要求你需要覆寫默認規則然后告知Code First想要連接到哪個數據庫。你可以Code First的連接工廠來選擇數據庫,修改或替換默認規則。可選擇地,你也可以使用DbContext構造器或應用程序配置文件來告知Code First對某個特定的上下文使用指定的數據庫。

Code First database creation and initialization works with SQL Azure in the same way that it works with any local database. You can see this in action in “Tutorial: Developing a Windows Azure Data Application Using Code First and SQL Azure”. Vendors have begun modifying their database providers to support Code First as well. Be sure to check for this support before trying to use Code First with one of the third-party providers.

小貼士:Code First可以在SQL Azure上使用與任何本地數據庫相同的方法進行創建和初始化工作。你可在http://www.windowsazure.com/zh-cn/develop/overview/找到相關的文章。目前數據庫供應商正在努力修改他們的產品已提供對Code First的支持。在使用Code First與第三方數據庫引擎工作前一定要檢查是否支持。

Controlling Database Location with a Configuration File

使用配置文件控制數據庫位置


The easiest and most definitive way to control the database that your context connects to is via a configuration file. Using the configuration file allows you to bypass all database location–related conventions and specify the exact database you want to use. This approach is particularly useful if you want to change the connection string of your context to point to a production database as you deploy your application.

By default, the connection string that you add to your configuration file should have the same name as your context. The name of the connection string can be either just the type name or the fully qualified type name. In “Controlling Connection String Name with DbContext Constructor” on page 132, you will see how to use a connection string with a name that does not match your context name. Add an App.config file to your BreakAwayConsole application with a BreakAwayContext connection string, as shown in Example 6-1.

控制數據庫連接最簡單也最可靠的方法是使用配置文件。配置文件可以幫你繞過所有與數據庫位置相關的約定,並能指定到你想使用的確切數據庫。這種方法是非常有用的,如果你想改變你的上下文的連接字符串指向一個生產數據庫,為您部署應用程序。

默認情況下,您添加到您的配置文件的連接字符串應該與context有相同的名稱。連接字符串的名稱可以是類型名稱或完全限定的類型名稱。在后面“使用DbContext構造器控制連接字符串名稱”,你會看到如何讓連接字符串的名稱不匹配上下文的名稱。添加一個App.config文件到BreakAwayConsole應用程序,內中包含BreakAwayContext的連接字符串,如例6-1所示。
Example 6-1. Connection string specified in App.config

 

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="BreakAwayContext"
          providerName="System.Data.SqlClient"
          connectionString="Server=.\SQLEXPRESS;
            Database=BreakAwayConfigFile;
            Trusted_Connection=true" />
  </connectionStrings>
</configuration>

For those familiar with creating connection strings when your application uses an EDMX file, notice that this is not an EntityConnection String but simply a database connection string. With Code First, you have no need to reference metadata files or the System.Data.Entity Client namespace.、

小貼士:對熟悉使用EDMX文件創建連接字符串的人,請注意這不是一個EntityConnection String 而是一個簡單的數據庫連接字符串。使用Code First,你不需要引用元數據文件或System.Data.Entity Client 名稱空間。

Modify the Main method so that it calls the InsertDestination method, as shown in Example 6-2.

修改Main方法,調用InsertDestination方法,如代碼6-2所示。

Example 6-2. Main method modified to call InsertDestination

static void Main()
{
  InsertDestination();
}

Run the application, and you will notice that a BreakAwayConfigFile database has been created in your local SQL Express instance (Figure 6-1).

運行應用程序,你會發現,BreakAwayConfigFile數據庫實例已在您的本地SQL Express上創建了(圖6-1)。

image

Code First matched the BreakAwayContext name of your context with the BreakAwayContext connection string in the configuration file. Because an entry was found in the configuration file, the convention for locating a database was not used. The connection string entry could also have been named DataAccess.BreakAwayContext, which is the fully qualified name of the context.
Code First使用配置文件中的BreakAwayContext連接字符串匹配名為BreakAwayContext的上下文。因為在配置文件中發現了一個條目,就不再使用約定來定位數據庫。連接字符串項,也已經被命名為DataAccess.BreakAwayContext,這是上下文的全名。

Controlling Database Name with DbContext Constructor

使用DbContext的構造器控制數據庫名稱

You’ve seen how to set the connection string that your context will use via the configuration file; now let’s look at some ways to control the database connection from code. So far you have just used the default constructor on DbContext, but there are also a number of other constructors available. Most of these are for more advanced scenarios, which will be covered later in this book, but there are two constructors that allow you to affect the database being connected to.

你已經看到如何通過配置文件中的連接字符串設置上下文,現在讓我們來看看使用代碼來控制數據庫連接的方法。到目前為止,您只使用過DbContext的默認構造函數,還有一些其他可用的構造函數可供使用。其中大部分是更高級的方法,這將在這本書中進行介紹,有兩個構造函數允許你影響連接到的數據庫。

If you added a connection string to your configuration file, as shown in the previous section, be sure to remove it before starting this section. Remember that the configuration file overrides everything, including the features you will see in this section.

小貼士:如果您添加了一個連接字符串到您的配置文件,如在上一節所示,開始本節之前,一定要去掉它。記住,配置文件壓倒一切,包括在本節的功能。

DbContext includes a constructor that takes a single string parameter to control the database name. If you use this constructor, the value you supply will be used in place of the fully qualified context name. Add a constructor to BreakAwayContext that accepts a string value for the database name and passes it to the base constructor (Example 6-3). Notice that you are also adding a default constructor to ensure that all the existing code from previous chapters continues to work.

DbContext有一個構造函數使用一個字符串參數來控制數據庫的名稱。如果您使用此構造器,您提供的值將被用來代替context的全名。添加到BreakAwayContext構造函數接受一個數據庫名稱的字符串值,並把它傳遞給基構造器(例6-3)。請注意,您也必須要加入默認的構造器,以確保所有現有的代碼從前面的章節繼續工作。

Example 6-3. Database name constructor added to context

public BreakAwayContext()
{ }
public BreakAwayContext(string databaseName)
  : base(databaseName)
{ }

Modify the Main method to call a new SpecifyDatabaseName method (Example 6-4).

修改Main方法調用 SpecifyDatabaseName方法 (Example 6-4).

Example 6-4. SpecifyDatabaseName method added to application

static void Main()
{
  SpecifyDatabaseName();
}
private static void SpecifyDatabaseName()
{
  using (var context =
    new BreakAwayContext("BreakAwayStringConstructor"))
  {
    context.Destinations.Add(new Destination { Name = "Tasmania" });
    context.SaveChanges();
  }
}

This new method uses the constructor you just added to specify a database name. This name is used instead of the fully qualified name of your context. Run the application and you will see that a database named BreakAwayStringConstructor has been created in your local SQL Express instance.

新方法使用的構造器,就是你剛剛添加的,用於指定數據庫名稱。使用的此名稱,就不是上下文的全名了。運行應用程序,你會看到一個名為BreakAwayStringConstructor數據庫已在您的本地SQL Express實例中創建。

Controlling Connection String Name with DbContext Constructor
使用DbContext構造器控制連接字符串


Earlier in this chapter, you saw that you are able to specify a database to use in the configuration file by adding a connection string with the same name as your context. If you use the DbContext constructor that accepts a database name, Entity ramework
will look for a connection string whose name matches the database name. In other words, with the default constructor, Entity Framework will look for a connection string named BreakAwayContext, but with the constructor used in Example 6-4, it will expect a connection string named BreakAwayStringConstructor.
You can also force the context to get its connection string from the configuration file by supplying name=[connection string name] to this constructor. This way, you don’t need to rely on name matching, since you are explicitly providing a connection string name. If no connection string is found with the specified name, an exception is thrown.

Example 6-5 shows how you can modify the default constructor of breakAwayContext to ensure that the connection string is always loaded from the configuration file.

在本章的前面,你看到,你可以通過在配置文件中加入與你的上下文的名稱相同的連接字符串指定一個數據庫。如果您使用的DbContext構造函數接受一個數據庫名,EF框架就會尋找一個與連接字符串的名稱相匹配的數據庫的名稱。換句話說,默認的構造函數,實體框架會尋找名為BreakAwayContext的連接字符串,而使用與示例6-4中使用的構造函數,它會期望一個名為BreakAwayStringConstructor的連接字符串。
您還可以強制上下文從配置文件中所提供的name= [connection string name]獲取連接字符串。這樣,你就不需要依靠名稱匹配,因為你明確地提供了一個連接字符串。如果沒有找到有具體指定名稱的連接字符串,就會拋出一個異常。
例6-5顯示了如何修改breakAwayContext默認構造器,以確保連接字符串始終是從配置文件加載。

Example 6-5. Constructor defining which connection string should be loaded from  App.config

public BreakAwayContext()
  :base("name=BreakAwayContext")
{ }
Reusing Database Connections
重用數據庫連接


DbContext has another constructor that allows you to supply a DbConnection instance.This can be useful if you have other application logic that works with a DbConnection or if you want to reuse the same connection across multiple contexts. To see this in action, add another constructor to BreakAwayContext that accepts a DbConnection and then passes the DbConnection to the base constructor, as shown in Example 6-6. You’ll also notice that you need to specify a value for the contextOwnsConnection. This argument controls whether or not the context should take ownership of the connection. If set to true, the connection will get disposed along with the context. If set to false,your code will need to take care of disposing the connection.

DbContext另一個構造器,允許您提供一個DbConnection的實例。這可能是有用的,如果你有其他的應用程序邏輯與DbConnection相關,或者如果你想重用在多個環境下的同一個連接。要看到這種行為,添加另一個構造器BreakAwayContext接受一個DbConnection,然后通過DbConnection基構造器傳遞值,如例6-6所示。你還會發現,你需要指定一個contextOwnsConnection的值。此參數控制context是否擁有連接的所有權。如果設置為true,連接將會隨上下文一起被釋放。如果設置為false,您的代碼將需要關注連接的釋放問題。

小貼士:添加這個構造器你需要引用System.Data.Common名稱空間。

Example 6-6. DbConnection constructor added to context

public BreakAwayContext(DbConnection connection)
  : base(connection, contextOwnsConnection: false)
{ }

Modify the Main method to call a new ReuseDbConnection method, as shown in
Example 6-7.

修改Main方法調用新的ReuseDbConnection方法,如Example 6-7所示:

小貼士:你需要引用System.Data.SqlClient名稱空間,因為此代碼使用SqlConnection類型。

Example 6-7. ReuseDbConnection method added to application

static void Main()
{
  ReuseDbConnection();
}
private static void ReuseDbConnection()
{
  var cstr = @"Server=.\SQLEXPRESS;
            Database=BreakAwayDbConnectionConstructor;
            Trusted_Connection=true";
  using (var connection = new SqlConnection(cstr))
  {
    using (var context = new BreakAwayContext(connection))
    {
      context.Destinations.Add(new Destination { Name = "Hawaii" });
      context.SaveChanges();
    }
    using (var context = new BreakAwayContext(connection))
    {
      foreach (var destination in context.Destinations)
      {
        Console.WriteLine(destination.Name);
      }
    }
  }
}

The ReuseDbConnection constructs a SqlConnection and then reuses it to construct two separate BreakAwayContext instances. In the example, the SqlConnection is just constructed from a connection string that is defined in code. However, Code First isn’t concerned with where you got the connection. You could be getting this connection string from a resource file. You may also be using some existing components that give you an existing DbConnection instance.

ReuseDbConnection構造一個SqlConnection,然后重用它來構造兩個單獨的BreakAwayContext實例。在這個例子中,在SqlConnection是在代碼中定義的連接字符串。然而,Code First並不關心你否獲得連接。您可以從資源文件該連接字符串。您也可以使用一些現有的組件,讓您獲得現有的DbConnection的實例。

Controlling Database Location with Connection Factories
使用連接工廠控制數據庫位置

One final option for controlling the database that is used is by swapping out the convention that Code First is using. The convention that Code First uses is available via Database.DefaultConnectionFactory. Connection factories implement the IDbConnectionFactory interface and are responsible for taking the name of a context and creating a DbConnection pointing to the database to be used. Entity Framework includes two connection factory implementations and you can also create your own.

控制所使用的數據庫的一個最后的選擇是通過更換Code First使用默認約一。Code First使用約定是通過Database.DefaultConnectionFactory來進行。連接工廠實現IDbConnectionFactory接口,並負責上下文的命名,並指明為要使用的數據庫創建一個DbConnection。EF框架包含兩個連接工廠實現,你也可以創建自己的。

Working with SqlConnectionFactory
使用SqlConnectionFactory

The default connection factory for Code First is SqlConnectionFactory. This connection factory will use the SQL Client (System.Data.SqlClient) database provider to connect to a database. The default behavior will select a database on localhost\SQLEXPRESS using the fully qualified name of the context type as the database name. Integrated authentication will be used for authenticating with the database server.
You can override parts of this convention by specifying segments of the connection string that are to be set for any connection it creates. These segments are supplied to the constructor of SqlConnectionFactory using the same syntax used in connection strings. For example, if you wanted to use a different database server, you can specify the Server segment of the connection string:

Code First的默認連接工廠是SqlConnectionFactory。此連接工廠將使用SQL Client(System.Data.SqlClient的)數據庫引擎連接到數據庫。默認的行為,將選擇在localhost\ SQLEXPRESS創建數據庫,並使用上下文類型的完全限定名作為數據庫的名稱。集成身份驗證,將用於與數據庫服務器進行身份驗證。
你可以通過指定的連接字符串段,來覆寫默認規則。這些片段使用SqlConnectionFactory構造函數相同的語法,在連接字符串中使用。例如,如果你想使用不同的數據庫服務器,您可以指定服務器段的連接字符串:

Database.DefaultConnectionFactory =
   new SqlConnectionFactory("Server=MyDatabaseServer");

Alternatively, you may want to use different credentials to connect to the database server:

可選地,你也可使用不同的驗證方式來連接數據庫服務器:

Database.DefaultConnectionFactory =
   new SqlConnectionFactory("User=MyUserName;Password=MyPassWord;");

Working with SqlCeConnectionFactory

使用SqlCeConnectionFactory
Entity Framework also includes SqlCeConnectionFactory, which uses SQL Compact Client to connect to SQL Server Compact Edition databases. By default the database file name matches the fully qualified name of the context class and is created in the |ApplicationData| directory.

EF框架還包括SqlCeConnectionFactory,它使用SQL Compact Client 連接到SQL ServerCompact Edition數據庫。默認情況下,數據庫文件名匹配上下文類的完全限定名,創建在| ApplicationData|目錄。(對可執行程序而言,| ApplicationData|位於應用程序所在目錄,對web應用程序,伴於網站根目錄下的的App_Data子目錄內。

Installing SQL Server Compact Edition
Before using SQL Server Compact Edition, you need to install the runtime. The runtime is available as an installer or via NuGet. Install the SqlServerCompact NuGet package to your BreakAwayConsole project. You can install the NuGet package by right-clicking on the References folder in your BreakAwayConsole project and selecting “Add Library Package Reference….” Select “Online” from the left menu and then search for “SqlServerCompact.”

小貼士:安裝SQL Server Compact Edition

在使用SQL Server Compact Edition前,需要進行安裝。可以通過NuGet來進行安裝。安裝SqlServerCompact 的NuGet包的到你的BreakAwayConsole項目的方法是:右鍵單擊項目並選擇:Add Library Package Reference….,從彈出的對話框中選擇Online並查找SqlServerCompact.

Modify the Main method, as shown in Example 6-8, to set the SqlCeConnectionFactory, and then call the InsertDestination method you created back in Chapter 2. The connection factories are included in the System.Data.Entity.Infrastructure namespace, so you will need to add a using for this. Be sure to read the rest of this section before running the code.

修改Main方法,如代碼6-8所示,設置SqlCeConnectionFactory,然后調用InsertDestination方法(第2章創建)。連接工廠包含在System.Data.Entity.Infrastructure名稱空間,需要添加對其的引用。在運行代碼前請讀完本節。

Example 6-8. Changing the default connection factory

 

static void Main()
{
  Database.SetInitializer(
    new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
  Database.DefaultConnectionFactory =
    new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
  InsertDestination();
}

Notice that you need to specify a string that identifies the database provider to use (known as the provider invariant name). This string is chosen by the provider writer to uniquely identify the provider. Most providers keep the same identifier between versions, but SQL Compact uses a different identifier for each version. This is because SQL Compact providers are not backwards-compatible (you can’t use, for example,the 4.0 provider to connect to a 3.5 database). The SqlCeConnectionFactory needs to know what version of the provider to use, so it requires you to supply this string.
If you want to test out this code, you will need to make a small change to your model. Back in Chapter 3, we configured Trip.Identifier to be a database-generated key. Identifier is a GUID property and SQL Server had no problem generating values for us. SQL Compact, however, isn’t able to generate values for GUID columns. If you want to run the application, remove either the Data Annotation or Fluent API call that configures Trip.Identifier as database-generated.
Once you’ve made this change, you can run the application and you will notice that a DataAccess.BreakawayContext.sdf file is created in the output directory of your application (Figure 6-2). Now that you’ve seen SQL Compact in action, go ahead and reenable the configuration to make Trip.Identifier database-generated.

請注意,您需要指定一個字符串,標識數據庫引擎(稱為provider invariant name)。這個字符串是數據庫供應商提供的唯一標識。大多數供應商保持不同版本之間使用相同的標識符,但SQL Compact為每個版本使用不同的標識符。這是因為SQL Compact數據庫引擎並不向后兼容的,例如,您不能使用4.0引擎連接到一個3.5數據庫。SqlCeConnectionFactory需要知道provider使用的版本,所以它需要你提供這個字符串。
如果你想測試一下這個代碼,你需要到你的模型一個小的變化。早在第3章,我們配置Trip.Identifier為數據庫生成的key。標識符是一個GUID屬性,在SQL Server下沒有任何問題。 SQL Compact,不能產生的GUID列的值。如果你想運行該應用程序,刪除或注釋掉Data Annotations或Fluent API配置的Trip.Identifier(作為數據庫生成列)。
一旦你做出了這種變化,你可以運行該應用程序,你會發現一個DataAccess.BreakawayContext.sdf文件是在您的應用程序的輸出目錄(圖6-2)創建。現在,你已經看到SQL默認規則的行為,繼續前進,重新啟用以前的配置,使Trip.Identifier能夠在數據庫里生成。
image

Writing a custom connection factory
寫一個定制的連接工廠

So far you have seen the connection factories that are included in Entity Framework, but you can also write your own by implementing the IDbConnectionFactory Interface.The interface is simple and contains a single CreateConnection method that accepts the context name and returns a DbConnection.
In this section, you’ll build a custom connection factory that is very similar to SqlConnectionFactory, except it will just use the context class name, rather than its fully qualified name for the database. You’ll also build this custom factory so that it will remove the word Context if it’s found in the context name.
Start by adding a CustomConnectionFactory class to your DataAccess project (Example 6-9).

到目前為止,您已經看到,連接工廠已經包含EF框架中,但你也可以通過實現IDbConnectionFactory接口來創建自已的連接工廠。
這個接口很簡單,包含了一個單一的創建連接的方法,它接受上下文的名稱,並返回一個DbConnection。
在本節中,您將構建一個自定義的連接工廠,與SqlConnectionFactory非常相似,但它將只使用上下文類的名稱,而不是使用全名作為數據庫的名稱。您還可以定制這個工廠,當其發現名稱中包含Context字符串時將刪除它。
加入一個CustomConnectionFactory類到DataAccess項目(代碼6-9)。

Example 6-9. Custom connection factory implementation

using System.Data.Common;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using System.Linq;
namespace DataAccess
{
  public class CustomConnectionFactory : IDbConnectionFactory
  {
    public DbConnection CreateConnection(
      string nameOrConnectionString)
    {
      var name = nameOrConnectionString
        .Split('.').Last()
        .Replace("Context", string.Empty);
      var builder = new SqlConnectionStringBuilder
      {
        DataSource = @".\SQLEXPRESS",
        InitialCatalog = name,
IntegratedSecurity = true,
        MultipleActiveResultSets = true
      };
      return new SqlConnection(builder.ToString());
    }
  }
}

The CustomConnectionFactory implementation uses the Split method to take the section of the context name after the final period to use for the database name. It then replaces any instances of the Context word with an empty string. Then it uses SqlConnection StringBuilder to create a connection string that is then used to construct a SqlConnection.
With this method in place, you can modify the Main method to make use of the custom connection factory you just created (Example 6-10). You do so by setting the Custom ConnectionFactory as the DefaultConnectionFactory before other code, which will be using a context.

CustomConnectionFactory使用Split方法取得上下文的名稱的最后一段(以“.”划分)作為數據庫名稱。然后,它將Context字符串替換為空字符串字(如果有的話)。然后,它使用SqlConnection的StringBuilder創建一個連接字符串,將其用於構造一個SqlConnection。
有了這個方法,你可以修改Main方法,使用剛剛創建的自定義連接工廠(例6-10)。這樣就DefaultConnectionFactory或其他代碼之前,讓上下文設置使用自定義的ConnectionFactory。


Example 6-10. Default connection factory set to new custom factory

static void Main()
{
  Database.SetInitializer(
    new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
  Database.DefaultConnectionFactory = new CustomConnectionFactory();
  InsertDestination();
}

Run the application and you will see that a new “BreakAway” database is created on the local SQL Express instance (Figure 6-3). The custom factory you just created has removed the namespace from the database name and also stripped the word “Context” from the end.

運行程序你會在SQL Express實例中發現一個新“BreakAway”數據庫創建了。定制工廠已經替你將數據庫名的名稱空間和后綴Context刪除。

 

image

Working with Database Initialization

數據庫初始化


In Chapter 2, you saw that an initializer can be set for a context type using the Database.SetInitializer method. The initializer you set allowed the database to be dropped and recreated whenever the model changed:

在第2章,你已經學習到可以使用Database.SetInitializer方法來為上下文類型設置初始化。設置初始化器可以清除並在模型變化時重建數據庫:

Database.SetInitializer(
  new DropCreateDatabaseIfModelChanges<BreakAwayContext>());

Initialization involves two main steps. First, the model is created in memory using the Code First conventions and configuration discussed in previous chapters. Second, the database that will be used to store data is initialized using the database initializer that has been set. By default, this initialization will use the model that Code First calculated to create a database schema for you. Initialization will occur one time per application instance; in .NET Framework applications, the application instance is also referred to as an AppDomain. Initialization is triggered the first time that the context is used. Initialization occurs lazily, so creating an instance of the context is not enough to cause initialization to happen. An operation that requires the model must be performed, such as querying or adding entities.

The initialization process is thread-safe, so multiple threads in the same AppDomain can use the same context type. DbContext itself is not threadsafe, so a given instance of the context type must only be used in a single thread.

初始化包括兩個主要步驟。首先,使用Code First在內存中根據默認規則和配置創建模型。其次,使用已設置的數據庫初始化器將用於存儲數據的數據庫初始化。默認情況下,這個初始化將使用Code First創建一個數據庫架構的模型。初始化會發生在每一個.NET Framework應用程序的實例上。應用程序的實例也被稱為一個AppDomain。當上下文被使用時,初始化第一次被引發。初始化是延遲加載的,所以創建一個實例的是不完全滿足初始化發生的條件的。必須執行對模型的操作,如查詢或添加實體才會發生。
初始化過程是線程安全的,所以在同一AppDomain中的多個線程可以使用相同的上下文類型。 DbContext本身不是線程安全的,因此,必須只能在一個單獨的線程中使用一個給定的上下文類型實例。

Controlling When Database Initialization Occurs
在數據庫初始化產生時進行控制

There are situations where you may want to control when initialization occurs, rather than leaving it to happen automatically the first time your context is used in an application instance. Initialization can be triggered using the DbContext.Database.Initialize method. This method takes a single boolean parameter named force. Supplying false will cause the initialization to occur only if it hasn’t yet been triggered in the current AppDomain. Remember that running the initializer once per AppDomain is the default behavior. Setting force to true will cause the initialization process to run even if it has already occurred in the current AppDomain. Because the context also triggers initialization, this code needs to run prior to the context being used in the AppDomain.
Why would you want to manually trigger database initialization? You may want to 
manually trigger initialization so that any errors that occur during model creation and database initialization can be caught and processed in a single place. Another reason  to force initialization to occur would be to front-load the cost of creating a large and/or complex model.
Let’s see this in action. Modify the Main method, adding in code to force database   initialization to occur, and handle any exceptions that occur as a result of building the model (Example 6-11).

有的情況下,您可能希望控制初始化的發生,而不是讓它自動發生在應用程序實例中第一次使用上下文對象時。初始化可以使用DbContext.Database.Initialize方法觸發。這個方法接受一個名為force的布爾參數。該參數為false將導致初始化只發生在尚未在當前AppDomain觸發的情況。請記住,每個AppDomain運行初始化一次,就會執行默認行為一次。force設置為true時將會使初始化過程運行,即使它已經在當前AppDomain發生。因為上下文也觸發初始化,此代碼需要運行在上下文被AppDomain使用之前。
為什么你想手動觸發數據庫初始化?您可能需要通過手動觸發初始化,使模型的創建和數據庫初始化過程中發生的任何錯誤可以被捕獲,並在一個地方處理。強制初始化發生的另一個原因是為了前端加載大型和/或復雜的模型。
讓我們來看看這個行為。修改Main方法,在代碼中加入強制數據庫初始化的配置,處理模型構建時發生的任何異常(代碼6-11)。

Example 6-11. Main method updated to process initialization errors

static void Main()
{
  Database.SetInitializer(
    new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
  using (var context = new BreakAwayContext())
  {
    try
    {
      context.Database.Initialize(force: false);
    }
    catch (Exception ex)
    {
      Console.WriteLine("Initialization Failed...");
      Console.WriteLine(ex.Message);
    }
  }
}

Now we’ll make a change that will cause initialization to fail by asking Code First to map a numeric property to a string column. Doing this will cause the model creation process to fail before Code First even tries to create the database schema.
Modify Activity and add in a Column annotation that specifies a varchar data type to be used for the ActivityId property (Example 6-12).

現在我們做些更改以使用初始化失敗。這個錯誤發生在Code First映射一個數值屬性到字符串列中。這樣做會使模型創建失敗發生在試圖創建數據庫構架之前。

修改Activity類加入一個Data Annotations的Column特性標記指定AcitivityId屬性使用varchar數據類型。(代碼6-12)

Example 6-12. ActivityId mapped to an incompatible database type

public class Activity
{
  [Column(TypeName = "varchar")]
  public int ActivityId { get; set; }
  [Required, MaxLength(50)]
  public string Name { get; set; }
  public List<Trip> Trips { get; set; }
}  

Run the application and the program will display the exception informing us that the  data type that was specified is not valid because of the invalid cast:

運行程序將顯示異常信息,表示不能將整形數據映射到varchar類型:

Initialization Failed...
Schema specified is not valid. Errors:

(122,12) : error 2019: Member Mapping specified is not valid. The type 'Edm.Int32[Nullable=False,DefaultValue=]' of member 'ActivityId' in type 'DataAccess.Activity' is not compatible with 'SqlServer.varchar[Nullable=False,DefaultValue=,MaxLength=8000,Unicode=False,FixedLength=False,StoreGeneratedPattern=Identity]' of member 'ActivityId' in type 'CodeFirstDatabaseSchema.Activity'.

(146,10) : error 2019: Member Mapping specified is not valid. The type 'Edm.Int32[Nul-lable=False,DefaultValue=]' of member 'ActivityId' in type 'DataAccess.Activity' is not compatible with 'SqlServer.varchar[Nullable=False,DefaultValue=,MaxLength=8000,Unicode=False,FixedLength=False]' of member 'Activity_ActivityId' in type 'CodeFirstDatabaseSchema.ActivityTrip'.

Remove the annotation you just added to DestinationId and run the application again.This time there will be no error.

移除剛剛添加到DestinationId上的特性標記,再次運行程序。這次就沒有問題了。

Switching Off Database Initialization Completely
關閉數據庫初始化功能


Of course, not every scenario calls for the database to be automatically initialized, and Entity Framework caters to these situations, too. For example, if you are mapping to an existing database, you probably want Code First to error if it can’t connect to the database, rather than trying to magically create one for you. You can switch off initialization by passing null to Database.SetInitializer:
Database.SetInitializer(null);
When the initializer is set to null, DbContext.Database.Initialize can still be used to  force model creation to occur.

當然,並不是所有場景都需要自動調用初始化,EF框架滿足所有情況。例如,如果你映射到一個現有的數據庫,可能在不能連接到數據庫時需要讓Code First發生錯誤而不是魔法般地創建一下。你可以通過傳遞一個null參數到Database.SetInitializer來關閉初始化功能:

Database.SetInitializer(null);
當初始化器被設置為null后,DbContext.Database.Initialize 仍可用於模型的創建過程.
Database Initializers Included in Entity Framework
將數據庫初始化器包含在EF框架


You’ll notice that Database.SetInitializer accepts an instance of IDatabaseInitializer<TContext>. There are three implementations of this interface included in Entity Framework. These implementations are abstract, so you can derive from them and customize the behavior. We’ll walk through creating your own implementation a little later on.
CreateDatabaseIfNotExists
This is the default initializer that is set for all contexts unless Database.SetInitializer is used to specify an alternative initializer. This is the safest initializer, as the database will never be dropped automatically, causing data loss. We saw in Chapter 2 that if the model is changed from when the database was created, an exception is thrown during initialization:

你會發現,Database.SetInitializer接受IDatabaseInitializer<TContext>的一個實例。在EF框架中有三個針對此接口的實現。這些實現是抽象的,所以你可以從其中派生或自定義行為。后面我們將引導您逐步創建自己的實現。
CreateDatabaseIfNotExists
除非Database.SetInitializer指定了替代的初始化器,所有上下文都會被設置給默認初始化器。這是最安全的初始化,數據庫將永遠不會被自動刪除,造成數據丟失。我們看到在第2章,如果模型是從數據庫時創建后發生的改變,在初始化期間會拋出異常:

The model backing the “BreakAwayContext” context has changed since the data-
base was created. Either manually delete/update the database, or call Database.SetI
nitializer with an IDatabaseInitializer instance. For example, the DropCreateDa
tabaseIfModelChanges strategy will automatically delete and recreate the database,
and optionally seed it with new data.

Because this is the default initializer, you shouldn’t need to set it, but if you find a need to you can use the following code:

由於默認初始化器的存在,如果需要執行下列代碼,你不需要做任何設置:

Database.SetInitializer(
  new CreateDatabaseIfNotExists<BreakAwayContext>());

DropCreateDatabaseWhenModelChanges
You’ve seen this initializer used throughout the previous chapters to make sure the  database always matches the current model. If Code First detects that the model and database do not match, the database will be dropped and recreated so that it matches the current model. This is useful during development, but you obviously wouldn’t want to use this when deploying your application, as it will result in data loss. We’ve already seen the code required to set this initializer:

從前面幾章你已經看到要確保數據庫總是匹配當前的模型.如果Code First檢測到二者不匹配,數據庫就人被刪除並且重新創建以便可以滿足匹配關系.在開發時這很有用,但是顯然不在在程序部署中使用,這樣數的會丟失.我們已經看到這樣的設置初始化的代碼:

Database.SetInitializer(
  new DropCreateDatabaseIfModelChanges<BreakAwayContext>());

DropCreateDatabaseAlways
This initializer will drop and recreate the database regardless of whether the model matches the database or not. At first glance, you may wonder why you would ever want to do that. If you are writing integration tests that exercise your whole application stack, it can be useful to have a way to reset the database to a well-known state before running a test. Modify the Main method as shown in Example 6-13 to
run some code that could represent a test that uses your application to insert a  single Destination.

這一初始化器將不管模型與數據庫匹配與否都會刪除和重建數據庫.你可能會疑惑為什么要這么做.如果你集成測試的整個應用程序,就會需要在運行測試前將數據庫重置到一個已知的狀態.修改Main方法(代碼6-13),運行一些代表測試的代碼,這會在應用程序中插入一個Destinaion.
Example 6-13. Implementation of a pseudo integration test

static void Main()
{
  Database.SetInitializer(
    new DropCreateDatabaseAlways<BreakAwayContext>());
  RunTest();
}
private static void RunTest()
{
  using (var context = new BreakAwayContext())
  {
    context.Destinations.Add(new Destination { Name = "Fiji" });
    context.SaveChanges();
  }
  using (var context = new BreakAwayContext())
  {
    if (context.Destinations.Count() == 1)
    {
      Console.WriteLine(
        "Test Passed: 1 destination saved to database");
    }
    else
    {
      Console.WriteLine(
        "Test Failed: {0} destinations saved to database",
        context.Destinations.Count());
    }
  }
}

Because the initializer is set to drop and recreate the database each time, you know that the database will be empty before the test starts. You won’t always want the database to be empty before running integration tests, and we’ll look at seeding data a little later on. Go ahead and run the application, and we will see that the test passes. So far we have just executed a single test, but normally there would be multiple tests required to test an application. Update the Main method so that it runs the same test twice in a row (Example 6-14).

由於初始化器被設置為每次都刪除並重建,你會知道在測試開始前數據庫是空的.你不用總是去考慮在運行測試前數據庫是否為空,后而我們會看到放置一些種子數據在里面的例子.運行程序,我們會看到測試通過.就目前為止我們只執行一個單一的測試,通常一個應用程序里面需要進行多個測試.更新Main方法以便使其可以在一行里運行兩次測試(代碼6-14):

Example 6-14. Main updated to run the test twice

static void Main(string[] args)
{
  Database.SetInitializer(
    new DropCreateDatabaseAlways<BreakAwayContext>());
  RunTest();
  RunTest();
}

Run the application, and you will see that the first execution of the test method will succeed but the second one will fail, stating that there are two destinations in the database. The second test is failing because the data from the first execution is still in the database. This is happening because AppDomain only runs the initializer once by default.
Earlier in this chapter, you learned that you can use Database.Initialize to force initialization to occur, even if has already happened in the current AppDomain. Modify the RunTest method to include a call to Database.Initialize with force set to true to ensure the database is reset before each test (Example 6-15). Run the application again and you will see both tests now pass. The database is getting dropped and recreated in the well-known state before each execution.

運行程序,你會看到第一個方法通過而第二個失敗,表明數據庫中有兩個destinations.第二個測試失敗的原因是第一次執行的結果已經在數據庫中了.進一步的原因是AppDomaing默認情況每次程序運行只執行一次初始化.

本章前面介紹可以使用Database.Initialize強制初始化,不管當前的AppDomain是否已經初始化過.修改RunTest方法包含一個調用Database.Initialize強制初始化的方法確保每次測試前都會重置數據庫(代碼6-15),再次運行程序你會發現測方式現在通過了在每次測試執行前.數據庫先刪除又以已知的狀態進行重建.

Example 6-15. RunTest updated to force initialization

static void RunTest()
{
  using (var context = new BreakAwayContext())
  {
    context.Database.Initialize(force: true);
    context.Destinations.Add(new Destination { Name = "Fiji" });
    context.SaveChanges();
  }
  using (var context = new BreakAwayContext())
  {
    if (context.Destinations.Count() == 1)
    {
      Console.WriteLine(
        "Test Passed: 1 destination saved to database");
    }
    else
    {
      Console.WriteLine(
        "Test Failed: {0} destinations saved to database",
context.Destinations.Count());
    }
  }
}

Dropping and recreating the database is an easy way to start each test with a well-known state, but it can be expensive if you are running a lot of integration tests. Consider using System.Transactions.TransactionScope as a way to avoid changes being permanently saved to the database during each test.

刪除和重建數據庫是將數據庫狀態保持在一個已知狀態的很容易的方法,但是如果運行一系集成的測試,系統開銷過大.考慮使用System.Transactions.TransactionScope作為避免在每一次測試時永久存儲對數據庫的修改.

Creating a Custom Database Initializer
創建一個定制的數據庫初始化器


So far, you have used the initializers that are included in the Entity Framework API. There may be times when the initialization logic that you want doesn’t align with any of the included initializers. Fortunately Database.SetInitializer accepts the IDatabaseInitializer interface, which you can implement to provide your own logic.

到目前為止,我們一直在使用EF框架中包含的初始化器.有時不想按照已有的初始化器的邏輯進行工作.Database.SetInitializer 接受IDatabaseInitializer 接口,你可以通過實現這個接口來定制邏輯.

As well as writing your own custom initializers, you can also find initializers that other people have created. One example of this is available in the EFCodeFirst.CreateTablesOnly NuGet package. This initializer will allow you to drop and create the tables in an existing database, rather than dropping and creating the actual database itself. This is particularly useful if you are targeting a hosted database where you don’t have permission to drop or create the entire database.

小貼士:除了自已寫定制的初始化器,也可以引用別人創建的.有一個例子EFCodeFirst.CreateTablesOnly NuGet 包.這個初始化器允許你在已經存在的數據庫進行刪除和創建操作,而不是刪除和創建數據庫實體本身.當你將數據庫指向一個宿主數據庫而又沒有權限刪除和創建整個數據庫時特別有用.

There could be any number of reasons you want to implement your own initializer. We are going to look at a simple scenario where the developer will be prompted before the database is dropped and recreated. The Database property exposes a variety of methods to interact with the database such as checking to see if it exists, creating it, or dropping  it. The three initializers that are included in the API contain logic that leverages these methods. You can combine the methods in logic in your own class. That’s what you'll do in this next example. Add the PromptForDropCreateDatabaseWhenModelChages class to your DataAccess project (Example 6-16).

你想實現自己的初始化器的原因可能有很多。我們來看一個簡單的場景:在數據庫刪除並重新創建之前給開發者一個提示。Database屬性暴露了各種方法與數據庫進行交互,可以實現檢查是否存在,是否創建,或是否刪除等功能。API中包含的初始化器包含的邏輯利用了這些方法。在你自己的類,你也可以將這些方法整合在邏輯里。這就是下面這個例子要做的。添加PromptForDropCreateDatabaseWhenModelChages類到您的DataAccess項目(例6-16)。
Example 6-16. Custom initializer

using System;
using System.Data.Entity;
namespace DataAccess
{
  public class PromptForDropCreateDatabaseWhenModelChages<TContext>
    : IDatabaseInitializer<TContext>
    where TContext : DbContext
  {
    public void InitializeDatabase(TContext context)
    {
      // If the database exists and matches the model
      // there is nothing to do
      var exists = context.Database.Exists();
      if (exists && context.Database.CompatibleWithModel(true))
      {
        return;
      }
      // If the database exists and doesn't match the model
      // then prompt for input
      if (exists)
      {
        Console.WriteLine
          ("Existing database doesn't match the model!");
        Console.Write
          ("Do you want to drop and create the database? (Y/N): ");
        var res = Console.ReadKey();
        Console.WriteLine();
        if (!String.Equals(
          "Y",
          res.KeyChar.ToString(),
          StringComparison.OrdinalIgnoreCase))
        {
          return;
        }
        context.Database.Delete();
      }
      // Database either didn't exist or it didn't match
      // the model and the user chose to delete it
      context.Database.Create();
    }
  }
}

The PromptForDropCreateDatabaseWhenModelChages class implements a single InitializeDatabase method. First, it checks if the database exists and matches the current model. If it does, there is nothing else to be done and the initializer returns. If the database exists but doesn’t match the current model, you will be prompted to see if you want to drop and create the database. If you decide not to recreate the database, the initializer returns and Entity Framework will attempt to run against the existing database schema. If you do decide to recreate the database, the existing database is dropped. The final line of code simply creates the database and is only reached if the database didn’t exist or if we chose to recreate the database.
The custom initializer now needs to be registered with the Entity Framework; modify the Main method to take care of this (Example 6-17). You’ll notice that we’re also updating Main so that it calls the InsertDestination method that we wrote back in Chapter 2.

PromptForDropCreateDatabaseWhenModelChages類實現單一的InitializeDatabase方法。首先,它檢查數據庫是否存在以及是否與當前的模型相匹配。如果是這樣,什么也不做,初始化器返回。如果該數據庫存在,但不匹配當前的模型,會提示你是否想刪除和創建數據庫。如果您決定不重新創建數據庫,初始化器返順,EF框架將嘗試按現有的數據庫模式再次運行。如果您決定重新創建數據庫,現有的數據庫將被刪除。最后一行代碼簡單地創建數據庫中,只會在數據庫不存在,或者我們選擇重新創建數據庫才會得到執行。
自定義的初始器在需要在EF框架內注冊;修改Main方法(例6-17)。你會注意到Main方法調用了我們在第2章更新的InsertDestination方法:

Example 6-17. Custom initializer registered in Main

 

static void Main()
{
  Database.SetInitializer(new
    PromptForDropCreateDatabaseWhenModelChages<BreakAwayContext>());
  InsertDestination();
}

Let’s go ahead and change the model so that it no longer matches the database. Modify the Destination class by adding a MaxLength annotation to the Name property:

我們對模型作些修改使其不再與數據庫匹配.修改Destinaton類中的Name屬性,在其上附加一個Data Annotations標記:MaxLength.

[MaxLength(200)]
public string Name { get; set; }

Now run the application, and you will be prompted, asking if you want to drop and Create the database. Answer no (N) to tell our custom initializer to leave the database alone this time. You’ll notice that the application still completes successfully. This is because the changes you made don’t prevent Entity Framework from being able to use the current model to access the out-of-date database schema. Entity Framework expects that Destination names should be 200 characters or less. Since the database didn’t change, it doesn’t enforce max length, so it’s happy with the insert statement that Entity Framework is sending to the database.
Now let’s make a change that will affect the insert statement. Modify the Destination class to include a new TravelWarnings property:

現在運行的應用程序,將提示您,詢問您是否要刪除並創建數據庫。答否(N),告訴我們的自定義初始化器不理會數據庫。你會注意到,應用程序仍然成功完成。這是因為您所做的更改不會阻止EF框架使用當前模型訪問過時的數據庫架構。EF框架預期Destination Names應為200個字符或更少。由於數據庫沒有改變,它沒有強制執行的最大長度,所以EF框架給它發送的INSERT語句可以執行。
現在,讓我們做出改變,會影響INSERT語句。修改目標類,包括一個新的TravelWarnings屬性:

public string TravelWarnings { get; set; }


Run the application again. As before, you’ll be prompted, asking if you want to drop and create the database. Select not to recreate the database again, and this time you will get a DbUpdateException. You’ll need to drill through the inner exceptions to find the actual cause of the error (Figure 6-4).
The inner exception of the top-level exception is an UpdateException, and the inner exception of that is a SqlException. The SqlException finally has the message that explains what happened: “Invalid column name 'TravelWarnings'.” The problem is that Entity Framework is trying to execute the SQL shown in Example 6-18, but the TravelWarnings column doesn’t exist in the database.

再次運行應用程序。像以前一樣,你會被提示,詢問您是否要刪除並創建數據庫。選擇不創建數據庫,這個時候你會得到一個DbUpdateException。你需要通過內部異常鏈去找到錯誤的真正原因(圖6-4)。
頂層異常的內部異常是UpdateException,該內部異常是一個SQLException。最后的SQLException的消息,說明發生了什么:“無效的列名稱”TravelWarnings,“問題發生的原因是EF框架試圖執行示例6-18中所示的SQL語句,但TravelWarnings列在數據庫中不存在。

image

Example 6-18. Invalid SQL being executed

insert [dbo].[Destinations]([Name], [Country], [Description],
             [TravelWarnings], [Photo])
values (@0, @1, @2, null, null)
select [DestinationId]
from [dbo].[Destinations]
where @@ROWCOUNT > 0 and [DestinationId] = scope_identity()

Run the application again, but this time select to drop and recreate the database when prompted. The application will now execute successfully.

再次運行程序,這次選擇刪除並重建數據庫,程序成功執行.

Setting Database Initializers from a Configuration File
在配置文件中設置數據庫初始化器


Setting initializers in code is an easy way to get started while developing, but when it’s time to deploy your application, you probably want to have an easier way to set them without modifying code. It’s highly unlikely you want to deploy your application with the DropCreateDatabaseIfModelChanges initializer set in production! Add an appSettings section to the config file of your BreakAwayConsole project that includes the initializer setting shown in Example 6-19.

在代碼中設置的初始化是一種簡單的方法,但部署用程序時,您可能希望有一個更簡單的方式設置,而無需修改代碼。想要應用程序部署設置DropCreateDatabaseIfModelChanges的初始化器,這是極不可能的!將appSettings節添加到BreakAwayConsole項目的配置文件中,其中包括了初始化器的設置,見示例6-19中所示。
Example 6-19. Initializer set in configuration file

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="DatabaseInitializerForType DataAccess.BreakAwayContext, DataAccess"
     value="System.Data.Entity.DropCreateDatabaseIfModelChanges`1
           [[DataAccess.BreakAwayContext, DataAccess]], EntityFramework" />
</appSettings>
</configuration>

小貼士:代碼示例有一行斷裂的代碼,在實際的App.config文件中應該刪除.value值必須在同一行才能工作.

There is a lot going on in the line of configuration, so let’s break down how it is structured. The key section always starts off with DatabaseInitializerForType followed by a space, then the assembly qualified name of the context that the initializer is being set for. In our case that is DataAccess.BreakAwayContext, DataAccess, which simply means the DataAccess.BreakAwayContext type that is defined in the DataAccess assembly. The value section is the assembly qualified name of the database initializer to be used. It looks complex because we are using a generic type; we are setting DropCreateDatabaseIfModelChanges<BreakAwayContext> defined in the EntityFramework assembly.
Also modify the Main method so that it no longer sets an initializer in code:

還應有很多配置行,我們打破配置結構來分別研究。關鍵部分開始於DatabaseInitializerForType,后跟一個空格,然后配置正確的上下文名稱以便初始化器能夠為其設置。在我們的例子就是DataAccess.BreakAwayContext,DataAccess,僅僅意味着DataAccess.BreakAwayContext類型的定義是在DataAccess程序集。Value部分是配置數據庫初始化器要使用名稱。它看起來復雜,因為我們使用泛型類型,我們使用了EF框架程序集中定義的DropCreateDatabaseIfModelChanges<BreakAwayContext> 方法來進行設置。
還需要修改Main方法,以便它不再設置在代碼中的初始化:

 

static void Main()
{
  InsertDestination();
}

Now make a change to the model so that you can test that the entry in our configuration file is being used. Modify the Destination class to include a new ClimateInfo property:

現在對模型作些改變以便測試配置文件是否得到應用.修改Destination類包含一個新的ClimateInfor屬性:

public string ClimateInfo { get; set; }

Run the application, and you will see that the database gets dropped and recreated with the new ClimateInfo column.

運行程序,你會看到數據庫被刪除重建,新增ClimateInfo列,
Now if you want to deploy your application, you may want to change the initializer to CreateDatabaseIfNotExists so that you never incur automatic data loss. You may also be working with a DBA who is going to create the database for you. If the database is being created outside of the Code First workflow, you will want to switch off  database initialization altogether. You can do that by changing the configuration file to specify Disabled for the initializer (Example 6-20).
現在,如果你要部署你的應用程序,你可能變更初始化器為CreateDatabaseIfNotExists,以便永遠不會導致數據丟失。您也可能工作在別人為您創建的DBA上,如果數據庫在Code First工作流之外創建,你會想禁用數據庫的初始化。你可以通過改變配置文件來指定初始化器的禁用(代碼6-20).

Example 6-20. Initializer disabled in configuration file

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="DatabaseInitializerForType DataAccess.BreakAwayContext, DataAccess"
         value="Disabled" />
  </appSettings>
</configuration>

Now that we’ve explored setting database initializers in a config file, be sure to remove any settings that you have added.

現在我探索了有關在配置文件設置初始化器的方法,請移除已經添加的任何設置.

Using Database Initializers to Seed Data

數據庫數據庫初始化器添加種子數據
In this chapter, you have seen how database initializers can be used to control how and when Code First creates the database. So far, the database that Code First creates has always been empty, but there are situations where you may want Code First to create your database with some seed data. You may have some lookup tables that have a predefined set of data, such as Gender or Country. You may just want some sample data in your database while you are working locally so that you can see how your application behaves.
Another scenario where seed data can be useful is running integration tests. In the previous section, we wrote a test that relied on an empty database; now let’s write one that relies on a database containing some well-known data.
Let’s start by writing the test you are going to run. Modify the Main method to run a test that verifies there is a Destination entry for “Great Barrier Reef” in our database (Example 6-21). Be sure you have removed any settings you added to the config file in the previous section.

在本章中,你已經看到數據庫的初始化可以被用來控制Code First何時以及如何創建數據庫。到目前為止,Code First創建的數據庫一直是空的,但也有一些需要Code First創建一些種子數據的情況。您可能有一些預定義的數據,如性別或國家的查找表。或者你可能只是想在本地工作時,在數據庫中放一些示例數據,從而可以看到應用程序的行為。
種子數據可以用另一種情況是運行集成測試。在上一節中,我們寫了一個測試,依靠的是一個空的數據庫,現在讓我們進行一個依賴於包含一些已知數據的數據庫的測試。
讓我們開始寫要運行的測試。修改Main方法來運行測試,以驗證“Great Barrier Reef”是否為數據庫中的Destination條目(例6-21).確保您已經刪除在上一節添加到任何設置的配置文件。

Example 6-21. Implementation of pseudo test reliant on seed data

 

static void Main()
{
  Database.SetInitializer(
    new DropCreateDatabaseAlways<BreakAwayContext>());
  GreatBarrierReefTest();
}
static void GreatBarrierReefTest()
{
  using (var context = new BreakAwayContext())
  {
    var reef = from destination in context.Destinations
                where destination.Name == "Great Barrier Reef"
                select destination;
    if (reef.Count() == 1)
    {
      Console.WriteLine(
        "Test Passed: 1 'Great Barrier Reef' destination found");
    }
    else
    {
      Console.WriteLine(
        "Test Failed: {0} 'Great Barrier Reef' destinations found",
        context.Destinations.Count());
    }
 }
}

Run the application, and you will see that the test fails, stating that there are no entries for “Great Barrier Reef” in the database. This makes sense, because you set the DropCreateDatabaseAlways initializer, which will create and empty the database for us.
What the test really needs is a variation of DropCreateDatabaseAlways that will insert  some seed data after it has created the database. The three initializers that are included in the Entity Framework are not sealed, meaning you can create your own initializer that derives from one of the included ones. All three of the included initializers also include a Seed method that is virtual (Overridable in Visual Basic), meaning it can be overridden. The seed method has an empty implementation, but the initializers will call it at the appropriate time to insert seed data that you provide.
To check out this feature, add a DropCreateBreakAwayWithSeedData class to your DataAccess project. The key to providing the seed data is to override the initializer’s Seed method, as shown in Example 6-22.

運行應用程序,你會看到測試失敗,說明“Great Barrier Reef”在數據庫中沒有任何條目與之匹配。這是有道理的,因為你設置了DropCreateDatabaseAlways初始化,這將創建和清空數據庫。
測試真正需要的是,在創建了數據庫后,將插入一些種子數據,能夠DropCreateDatabaseAlways的變化來實現。包括在EF框架中的三個初始化器不是sealed的,這意味着你可以通過派生其中之一來創建自己的初始化器。所有三個初始化器都包括一個名為Seed的abstract方法(在Visual Basic中為Overridable),這意味着它可以被覆寫。Seed方法有一個空的實現,但是,初始化器可以在適當的時候插入您提供的種子數據。
要檢查此功能,您的DataAccess項目添加DropCreateBreakAwayWithSeedData類。提供種子數據的關鍵是覆寫初始化種子的方法,如例6-22所示。

Example 6-22. Initializer with seed data implemented

using System.Data.Entity;
using Model;
namespace DataAccess
{
  public class DropCreateBreakAwayWithSeedData :
   DropCreateDatabaseAlways<BreakAwayContext>
  {
    protected override void Seed(BreakAwayContext context)
    {
      context.Destinations.Add(new Destination
           { Name = "Great Barrier Reef" });
      context.Destinations.Add(new Destination
           { Name = "Grand Canyon" });
    }
  }
}

Notice that there is no call to context.SaveChanges() at the end of the Seed method in Example 6-24. The base Seed method will call that for you after the code in your custom method has been executed. If you let Visual Studio’s editor auto-implement the override method for you, it will include a call to base.Seed(context). You can leave that in if you like, but be sure to let it be the last line of code in the method.

小貼士:注意我們在這里沒有調用SaveChanges方法.Seed的基方法會在定制方法之后調用.如果你讓VS的編輯器自動實現覆寫方法,就會包括一個對base.Seed(context)的調用.你可以不去管他,但是記住要將這行代碼放在方法的最后一行.

Now that you have created an initializer that will insert seed data, it needs to be registered with Entity Framework so that it will be used. This is achieved in same way that we registered the included initializers earlier—via the Database.SetInitializer
method.

現在你已經創建了一個能夠插入種子數據的初始化器,它需要在EF框架中注冊后才能被使用.這可以以我們在前面包含初始化器相同的方式進行--通過Database.SetInitializer方法

Modify the Main method so that DropCreateBreakAwayWithSeedData is registered (Example 6-23).

修改Main方法以注冊DropCreateBreakAwayWithSeedData類:
Example 6-23. Initializer with seed data is registered

static void Main()
{
  Database.SetInitializer(new DropCreateBreakAwayWithSeedData());
  GreatBarrierReefTest();
}

Run the application again, and the test will pass this time because Code First is now using DropCreateBreakAwayWithSeedData to initialize the database. Because this initializer derives from DropCreateDatabaseAlways, it will drop the database and recreate and empty one. The Seed method that you overrode will then be called and the seed data you specified is inserted into the newly created database each time.

再次運行應用程序,本次測試通過,因為Code First現在使用DropCreateBreakAwayWithSeedData初始化數據庫。由於此初始化派生自DropCreateDatabaseAlways,它會刪除數據庫並重新創建一個空數據庫。覆寫的Seed方法,隨后被調用,您指定的種子數據插入到了新創建的數據庫里。

 

The Seed method in Example 6-24 is a great first look at seeding the database but somewhat simplistic. You can insert various types of data and related data as well. For an example of an efficient LINQ method used to insert entire graphs of related data in Seed, check out my blog post, Seeding a Database with Code First.

代碼6-24中的Seed方法或許有些簡單化,但這讓我們很好地觀察了這個方法的創建過程。可以插入各類數據及相關數據。例如使用一個有效率的LINQ方法利用Code First檢查我的博客,將相關文章的全部圖片作為種子插入數據庫。

Using Database Initialization to Further Affect Database Schema

使用數據庫初始化進一步影響數據庫構架


In addition to seeding a database when Code First creates it, you may want to affect the database in ways that can’t be done with configurations or data seeding. For example, you may want to create an Index on the Name field of the Lodgings table to speed up searches by name.
You can achieve this by calling the DbContext.Database.ExecuteSqlCommand method along with the SQL to create the index inside the Seed method. Example 6-24 shows the modified Seed method that forces this Index to be created before the data is inserted.

除了使用Code First在數據庫中創建種子數據以外,你也可不使用配置或種子數據達到相同目的.你如,你可以想創建Lodgings表中Name字段的索引以加快使用name查詢的速度.

你可以通過調用DbContext.Database.ExecuteSqlCommand 方法來達到目的,這個方法會在Seed方法內部構造創建索引的SQL語句.代碼6-24顯示了對Seed方法的修改,強制數據插入時創建索引.

Example 6-24. Using the ExecuteSqlCommand to add an Index to the database

protected override void Seed(BreakAwayContext context)
{
  context.Database.ExecuteSqlCommand
   ("CREATE INDEX IX_Lodgings_Name ON Lodgings (Name)");
  context.Destinations.Add(new Destination
       { Name = "Great Barrier Reef" });
  context.Destinations.Add(new Destination
       { Name = "Grand Canyon" });
}
Summary
小結


In this chapter you saw how Code First interacts with the database by default, and how you can override this default behavior. You’ve learned how to control the database that Code First connects to and how that database is initialized. You’ve also seen how database initializers can be used in scenario tests to insert seed data into the database as it is initialized.
Throughout this book, you have seen how Code First creates a model based on your
domain classes and configuration. You’ve then seen how Code First locates and initializes the database that the model will be used to access. In the next chapter, you will learn about some advanced concepts that you probably won’t use regularly, but you may find useful from time to time.

在這一章中,你看到了默認情況Code First如何與數據庫進行交互,也學習到如何覆寫此默認行為。你已經學會了如何控制數據庫,Code First連接到數據庫時是如何初始化的。您還學到如何將數據庫的初始化用於情景測試中,如何在初始化時插入種子數據。
在這本書中,你已經看到Code First根據您的域類和配置創建了一個模型,然后,你也看到Code First是如何定義和初始化被模型用來訪問的數據庫。在下一章中,您將學習一些不太常用的先進的理念,但這些理念有時會很有用。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM