Code First :使用Entity. Framework编程(6) ----转发 收藏
Chapter6
Controlling Database Location,Creation Process, and Seed Data
第6章
控制數(shù)據(jù)庫位置,創(chuàng)建過程和種子數(shù)據(jù)
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.
前面我們已經(jīng)提到默認(rèn)規(guī)則和配置可以用于影響模型和數(shù)據(jù)庫構(gòu)架。本章你會(huì)看到如何使用Code First來控制數(shù)據(jù)庫。
你將會(huì)學(xué)到Code First默認(rèn)配置如何選擇數(shù)據(jù)庫,也會(huì)掌握如何修改默認(rèn)規(guī)則或指定上下文使用真正的數(shù)據(jù)庫。我們覆蓋的主題將包括指向其他數(shù)據(jù)庫引擎,部署應(yīng)用程序,執(zhí)行數(shù)據(jù)庫有關(guān)的任何等。
你也可以學(xué)習(xí)到數(shù)據(jù)庫初始化器可以用于控制數(shù)據(jù)庫生成過程,添加種子數(shù)據(jù)到數(shù)據(jù)庫中等。這在進(jìn)行自動(dòng)測試的場景時(shí)特別有用。
Controlling the Database Location
控制數(shù)據(jù)庫位置
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的默認(rèn)規(guī)則來選擇應(yīng)用程序的數(shù)據(jù)庫目標(biāo)。默認(rèn)情況,Code First會(huì)使用localhost\SQLEXPRESS作為目標(biāo)數(shù)據(jù)庫引擎,并使用context類的全名作為數(shù)據(jù)庫名(即命名空間+類名)。如果不符合要求你需要覆寫默認(rèn)規(guī)則然后告知Code First想要連接到哪個(gè)數(shù)據(jù)庫。你可以Code First的連接工廠來選擇數(shù)據(jù)庫,修改或替換默認(rèn)規(guī)則。可選擇地,你也可以使用DbContext構(gòu)造器或應(yīng)用程序配置文件來告知Code First對(duì)某個(gè)特定的上下文使用指定的數(shù)據(jù)庫。
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上使用與任何本地?cái)?shù)據(jù)庫相同的方法進(jìn)行創(chuàng)建和初始化工作。你可在http://www.windowsazure.com/zh-cn/develop/overview/找到相關(guān)的文章。目前數(shù)據(jù)庫供應(yīng)商正在努力修改他們的產(chǎn)品已提供對(duì)Code First的支持。在使用Code First與第三方數(shù)據(jù)庫引擎工作前一定要檢查是否支持。
Controlling Database Location with a Configuration File
使用配置文件控制數(shù)據(jù)庫位置
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.
控制數(shù)據(jù)庫連接最簡單也最可靠的方法是使用配置文件。配置文件可以幫你繞過所有與數(shù)據(jù)庫位置相關(guān)的約定,并能指定到你想使用的確切數(shù)據(jù)庫。這種方法是非常有用的,如果你想改變你的上下文的連接字符串指向一個(gè)生產(chǎn)數(shù)據(jù)庫,為您部署應(yīng)用程序。
默認(rèn)情況下,您添加到您的配置文件的連接字符串應(yīng)該與context有相同的名稱。連接字符串的名稱可以是類型名稱或完全限定的類型名稱。在后面“使用DbContext構(gòu)造器控制連接字符串名稱”,你會(huì)看到如何讓連接字符串的名稱不匹配上下文的名稱。添加一個(gè)App.config文件到BreakAwayConsole應(yīng)用程序,內(nèi)中包含BreakAwayContext的連接字符串,如例6-1所示。
Example 6-1. Connection string specified in App.config
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.、
小貼士:對(duì)熟悉使用EDMX文件創(chuàng)建連接字符串的人,請(qǐng)注意這不是一個(gè)EntityConnection String 而是一個(gè)簡單的數(shù)據(jù)庫連接字符串。使用Code First,你不需要引用元數(shù)據(jù)文件或System.Data.Entity Client 名稱空間。
Modify the Main method so that it calls the InsertDestination method, as shown in Example 6-2.
修改Main方法,調(diào)用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).
運(yùn)行應(yīng)用程序,你會(huì)發(fā)現(xiàn),BreakAwayConfigFile數(shù)據(jù)庫實(shí)例已在您的本地SQL Express上創(chuàng)建了(圖6-1)。
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的上下文。因?yàn)樵谂渲梦募邪l(fā)現(xiàn)了一個(gè)條目,就不再使用約定來定位數(shù)據(jù)庫。連接字符串項(xiàng),也已經(jīng)被命名為DataAccess.BreakAwayContext,這是上下文的全名。
Controlling Database Name with DbContext Constructor
使用DbContext的構(gòu)造器控制數(shù)據(jù)庫名稱
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.
你已經(jīng)看到如何通過配置文件中的連接字符串設(shè)置上下文,現(xiàn)在讓我們來看看使用代碼來控制數(shù)據(jù)庫連接的方法。到目前為止,您只使用過DbContext的默認(rèn)構(gòu)造函數(shù),還有一些其他可用的構(gòu)造函數(shù)可供使用。其中大部分是更高級(jí)的方法,這將在這本書中進(jìn)行介紹,有兩個(gè)構(gòu)造函數(shù)允許你影響連接到的數(shù)據(jù)庫。
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.
小貼士:如果您添加了一個(gè)連接字符串到您的配置文件,如在上一節(jié)所示,開始本節(jié)之前,一定要去掉它。記住,配置文件壓倒一切,包括在本節(jié)的功能。
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有一個(gè)構(gòu)造函數(shù)使用一個(gè)字符串參數(shù)來控制數(shù)據(jù)庫的名稱。如果您使用此構(gòu)造器,您提供的值將被用來代替context的全名。添加到BreakAwayContext構(gòu)造函數(shù)接受一個(gè)數(shù)據(jù)庫名稱的字符串值,并把它傳遞給基構(gòu)造器(例6-3)。請(qǐng)注意,您也必須要加入默認(rèn)的構(gòu)造器,以確保所有現(xiàn)有的代碼從前面的章節(jié)繼續(xù)工作。
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方法調(diào)用 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.
新方法使用的構(gòu)造器,就是你剛剛添加的,用于指定數(shù)據(jù)庫名稱。使用的此名稱,就不是上下文的全名了。運(yùn)行應(yīng)用程序,你會(huì)看到一個(gè)名為BreakAwayStringConstructor數(shù)據(jù)庫已在您的本地SQL Express實(shí)例中創(chuàng)建。
Controlling Connection String Name with DbContext Constructor
使用DbContext構(gòu)造器控制連接字符串
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.
在本章的前面,你看到,你可以通過在配置文件中加入與你的上下文的名稱相同的連接字符串指定一個(gè)數(shù)據(jù)庫。如果您使用的DbContext構(gòu)造函數(shù)接受一個(gè)數(shù)據(jù)庫名,EF框架就會(huì)尋找一個(gè)與連接字符串的名稱相匹配的數(shù)據(jù)庫的名稱。換句話說,默認(rèn)的構(gòu)造函數(shù),實(shí)體框架會(huì)尋找名為BreakAwayContext的連接字符串,而使用與示例6-4中使用的構(gòu)造函數(shù),它會(huì)期望一個(gè)名為BreakAwayStringConstructor的連接字符串。
您還可以強(qiáng)制上下文從配置文件中所提供的name= [connection string name]獲取連接字符串。這樣,你就不需要依靠名稱匹配,因?yàn)槟忝鞔_地提供了一個(gè)連接字符串。如果沒有找到有具體指定名稱的連接字符串,就會(huì)拋出一個(gè)異常。
例6-5顯示了如何修改breakAwayContext默認(rèn)構(gòu)造器,以確保連接字符串始終是從配置文件加載。
Example 6-5. Constructor defining which connection string should be loaded from? App.config
public BreakAwayContext():base("name=BreakAwayContext") { }Reusing Database Connections
重用數(shù)據(jù)庫連接
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另一個(gè)構(gòu)造器,允許您提供一個(gè)DbConnection的實(shí)例。這可能是有用的,如果你有其他的應(yīng)用程序邏輯與DbConnection相關(guān),或者如果你想重用在多個(gè)環(huán)境下的同一個(gè)連接。要看到這種行為,添加另一個(gè)構(gòu)造器BreakAwayContext接受一個(gè)DbConnection,然后通過DbConnection基構(gòu)造器傳遞值,如例6-6所示。你還會(huì)發(fā)現(xiàn),你需要指定一個(gè)contextOwnsConnection的值。此參數(shù)控制context是否擁有連接的所有權(quán)。如果設(shè)置為true,連接將會(huì)隨上下文一起被釋放。如果設(shè)置為false,您的代碼將需要關(guān)注連接的釋放問題。
小貼士:添加這個(gè)構(gòu)造器你需要引用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方法調(diào)用新的ReuseDbConnection方法,如Example 6-7所示:
小貼士:你需要引用System.Data.SqlClient名稱空間,因?yàn)榇舜a使用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構(gòu)造一個(gè)SqlConnection,然后重用它來構(gòu)造兩個(gè)單獨(dú)的BreakAwayContext實(shí)例。在這個(gè)例子中,在SqlConnection是在代碼中定義的連接字符串。然而,Code First并不關(guān)心你否獲得連接。您可以從資源文件該連接字符串。您也可以使用一些現(xiàn)有的組件,讓您獲得現(xiàn)有的DbConnection的實(shí)例。
Controlling Database Location with Connection Factories
使用連接工廠控制數(shù)據(jù)庫位置
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.
控制所使用的數(shù)據(jù)庫的一個(gè)最后的選擇是通過更換Code First使用默認(rèn)約一。Code First使用約定是通過Database.DefaultConnectionFactory來進(jìn)行。連接工廠實(shí)現(xiàn)IDbConnectionFactory接口,并負(fù)責(zé)上下文的命名,并指明為要使用的數(shù)據(jù)庫創(chuàng)建一個(gè)DbConnection。EF框架包含兩個(gè)連接工廠實(shí)現(xiàn),你也可以創(chuàng)建自己的。
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的默認(rèn)連接工廠是SqlConnectionFactory。此連接工廠將使用SQL Client(System.Data.SqlClient的)數(shù)據(jù)庫引擎連接到數(shù)據(jù)庫。默認(rèn)的行為,將選擇在localhost\ SQLEXPRESS創(chuàng)建數(shù)據(jù)庫,并使用上下文類型的完全限定名作為數(shù)據(jù)庫的名稱。集成身份驗(yàn)證,將用于與數(shù)據(jù)庫服務(wù)器進(jìn)行身份驗(yàn)證。
你可以通過指定的連接字符串段,來覆寫默認(rèn)規(guī)則。這些片段使用SqlConnectionFactory構(gòu)造函數(shù)相同的語法,在連接字符串中使用。例如,如果你想使用不同的數(shù)據(jù)庫服務(wù)器,您可以指定服務(wù)器段的連接字符串:
Alternatively, you may want to use different credentials to connect to the database server:
可選地,你也可使用不同的驗(yàn)證方式來連接數(shù)據(jù)庫服務(wù)器:
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數(shù)據(jù)庫。默認(rèn)情況下,數(shù)據(jù)庫文件名匹配上下文類的完全限定名,創(chuàng)建在| ApplicationData|目錄。(對(duì)可執(zhí)行程序而言,| ApplicationData|位于應(yīng)用程序所在目錄,對(duì)web應(yīng)用程序,伴于網(wǎng)站根目錄下的的App_Data子目錄內(nèi)。
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前,需要進(jìn)行安裝。可以通過NuGet來進(jìn)行安裝。安裝SqlServerCompact 的NuGet包的到你的BreakAwayConsole項(xiàng)目的方法是:右鍵單擊項(xiàng)目并選擇:Add Library Package Reference….,從彈出的對(duì)話框中選擇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所示,設(shè)置SqlCeConnectionFactory,然后調(diào)用InsertDestination方法(第2章創(chuàng)建)。連接工廠包含在System.Data.Entity.Infrastructure名稱空間,需要添加對(duì)其的引用。在運(yùn)行代碼前請(qǐng)讀完本節(jié)。
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.
請(qǐng)注意,您需要指定一個(gè)字符串,標(biāo)識(shí)數(shù)據(jù)庫引擎(稱為provider invariant name)。這個(gè)字符串是數(shù)據(jù)庫供應(yīng)商提供的唯一標(biāo)識(shí)。大多數(shù)供應(yīng)商保持不同版本之間使用相同的標(biāo)識(shí)符,但SQL Compact為每個(gè)版本使用不同的標(biāo)識(shí)符。這是因?yàn)镾QL Compact數(shù)據(jù)庫引擎并不向后兼容的,例如,您不能使用4.0引擎連接到一個(gè)3.5數(shù)據(jù)庫。SqlCeConnectionFactory需要知道provider使用的版本,所以它需要你提供這個(gè)字符串。
如果你想測試一下這個(gè)代碼,你需要到你的模型一個(gè)小的變化。早在第3章,我們配置Trip.Identifier為數(shù)據(jù)庫生成的key。標(biāo)識(shí)符是一個(gè)GUID屬性,在SQL Server下沒有任何問題。 SQL Compact,不能產(chǎn)生的GUID列的值。如果你想運(yùn)行該應(yīng)用程序,刪除或注釋掉Data Annotations或Fluent API配置的Trip.Identifier(作為數(shù)據(jù)庫生成列)。
一旦你做出了這種變化,你可以運(yùn)行該應(yīng)用程序,你會(huì)發(fā)現(xiàn)一個(gè)DataAccess.BreakawayContext.sdf文件是在您的應(yīng)用程序的輸出目錄(圖6-2)創(chuàng)建。現(xiàn)在,你已經(jīng)看到SQL默認(rèn)規(guī)則的行為,繼續(xù)前進(jìn),重新啟用以前的配置,使Trip.Identifier能夠在數(shù)據(jù)庫里生成。
Writing a custom connection factory
寫一個(gè)定制的連接工廠
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).
到目前為止,您已經(jīng)看到,連接工廠已經(jīng)包含EF框架中,但你也可以通過實(shí)現(xiàn)IDbConnectionFactory接口來創(chuàng)建自已的連接工廠。
這個(gè)接口很簡單,包含了一個(gè)單一的創(chuàng)建連接的方法,它接受上下文的名稱,并返回一個(gè)DbConnection。
在本節(jié)中,您將構(gòu)建一個(gè)自定義的連接工廠,與SqlConnectionFactory非常相似,但它將只使用上下文類的名稱,而不是使用全名作為數(shù)據(jù)庫的名稱。您還可以定制這個(gè)工廠,當(dāng)其發(fā)現(xiàn)名稱中包含Context字符串時(shí)將刪除它。
加入一個(gè)CustomConnectionFactory類到DataAccess項(xiàng)目(代碼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方法取得上下文的名稱的最后一段(以“.”劃分)作為數(shù)據(jù)庫名稱。然后,它將Context字符串替換為空字符串字(如果有的話)。然后,它使用SqlConnection的StringBuilder創(chuàng)建一個(gè)連接字符串,將其用于構(gòu)造一個(gè)SqlConnection。
有了這個(gè)方法,你可以修改Main方法,使用剛剛創(chuàng)建的自定義連接工廠(例6-10)。這樣就DefaultConnectionFactory或其他代碼之前,讓上下文設(shè)置使用自定義的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.
運(yùn)行程序你會(huì)在SQL Express實(shí)例中發(fā)現(xiàn)一個(gè)新“BreakAway”數(shù)據(jù)庫創(chuàng)建了。定制工廠已經(jīng)替你將數(shù)據(jù)庫名的名稱空間和后綴Context刪除。
Working with Database Initialization
數(shù)據(jù)庫初始化
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章,你已經(jīng)學(xué)習(xí)到可以使用Database.SetInitializer方法來為上下文類型設(shè)置初始化。設(shè)置初始化器可以清除并在模型變化時(shí)重建數(shù)據(jù)庫:
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.
初始化包括兩個(gè)主要步驟。首先,使用Code First在內(nèi)存中根據(jù)默認(rèn)規(guī)則和配置創(chuàng)建模型。其次,使用已設(shè)置的數(shù)據(jù)庫初始化器將用于存儲(chǔ)數(shù)據(jù)的數(shù)據(jù)庫初始化。默認(rèn)情況下,這個(gè)初始化將使用Code First創(chuàng)建一個(gè)數(shù)據(jù)庫架構(gòu)的模型。初始化會(huì)發(fā)生在每一個(gè).NET Framework應(yīng)用程序的實(shí)例上。應(yīng)用程序的實(shí)例也被稱為一個(gè)AppDomain。當(dāng)上下文被使用時(shí),初始化第一次被引發(fā)。初始化是延遲加載的,所以創(chuàng)建一個(gè)實(shí)例的是不完全滿足初始化發(fā)生的條件的。必須執(zhí)行對(duì)模型的操作,如查詢或添加實(shí)體才會(huì)發(fā)生。
初始化過程是線程安全的,所以在同一AppDomain中的多個(gè)線程可以使用相同的上下文類型。 DbContext本身不是線程安全的,因此,必須只能在一個(gè)單獨(dú)的線程中使用一個(gè)給定的上下文類型實(shí)例。
Controlling When Database Initialization Occurs
在數(shù)據(jù)庫初始化產(chǎn)生時(shí)進(jìn)行控制
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).
有的情況下,您可能希望控制初始化的發(fā)生,而不是讓它自動(dòng)發(fā)生在應(yīng)用程序?qū)嵗械谝淮问褂蒙舷挛膶?duì)象時(shí)。初始化可以使用DbContext.Database.Initialize方法觸發(fā)。這個(gè)方法接受一個(gè)名為force的布爾參數(shù)。該參數(shù)為false將導(dǎo)致初始化只發(fā)生在尚未在當(dāng)前AppDomain觸發(fā)的情況。請(qǐng)記住,每個(gè)AppDomain運(yùn)行初始化一次,就會(huì)執(zhí)行默認(rèn)行為一次。force設(shè)置為true時(shí)將會(huì)使初始化過程運(yùn)行,即使它已經(jīng)在當(dāng)前AppDomain發(fā)生。因?yàn)樯舷挛囊灿|發(fā)初始化,此代碼需要運(yùn)行在上下文被AppDomain使用之前。
為什么你想手動(dòng)觸發(fā)數(shù)據(jù)庫初始化?您可能需要通過手動(dòng)觸發(fā)初始化,使模型的創(chuàng)建和數(shù)據(jù)庫初始化過程中發(fā)生的任何錯(cuò)誤可以被捕獲,并在一個(gè)地方處理。強(qiáng)制初始化發(fā)生的另一個(gè)原因是為了前端加載大型和/或復(fù)雜的模型。
讓我們來看看這個(gè)行為。修改Main方法,在代碼中加入強(qiáng)制數(shù)據(jù)庫初始化的配置,處理模型構(gòu)建時(shí)發(fā)生的任何異常(代碼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).
現(xiàn)在我們做些更改以使用初始化失敗。這個(gè)錯(cuò)誤發(fā)生在Code First映射一個(gè)數(shù)值屬性到字符串列中。這樣做會(huì)使模型創(chuàng)建失敗發(fā)生在試圖創(chuàng)建數(shù)據(jù)庫構(gòu)架之前。
修改Activity類加入一個(gè)Data Annotations的Column特性標(biāo)記指定AcitivityId屬性使用varchar數(shù)據(jù)類型。(代碼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:
運(yùn)行程序?qū)@示異常信息,表示不能將整形數(shù)據(jù)映射到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上的特性標(biāo)記,再次運(yùn)行程序。這次就沒有問題了。
Switching Off Database Initialization Completely
關(guān)閉數(shù)據(jù)庫初始化功能
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.
當(dāng)然,并不是所有場景都需要自動(dòng)調(diào)用初始化,EF框架滿足所有情況。例如,如果你映射到一個(gè)現(xiàn)有的數(shù)據(jù)庫,可能在不能連接到數(shù)據(jù)庫時(shí)需要讓Code First發(fā)生錯(cuò)誤而不是魔法般地創(chuàng)建一下。你可以通過傳遞一個(gè)null參數(shù)到Database.SetInitializer來關(guān)閉初始化功能:
Database.SetInitializer(null); 當(dāng)初始化器被設(shè)置為null后,DbContext.Database.Initialize 仍可用于模型的創(chuàng)建過程.Database Initializers Included in Entity Framework
將數(shù)據(jù)庫初始化器包含在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:
你會(huì)發(fā)現(xiàn),Database.SetInitializer接受IDatabaseInitializer<TContext>的一個(gè)實(shí)例。在EF框架中有三個(gè)針對(duì)此接口的實(shí)現(xiàn)。這些實(shí)現(xiàn)是抽象的,所以你可以從其中派生或自定義行為。后面我們將引導(dǎo)您逐步創(chuàng)建自己的實(shí)現(xiàn)。
CreateDatabaseIfNotExists
除非Database.SetInitializer指定了替代的初始化器,所有上下文都會(huì)被設(shè)置給默認(rèn)初始化器。這是最安全的初始化,數(shù)據(jù)庫將永遠(yuǎn)不會(huì)被自動(dòng)刪除,造成數(shù)據(jù)丟失。我們看到在第2章,如果模型是從數(shù)據(jù)庫時(shí)創(chuàng)建后發(fā)生的改變,在初始化期間會(huì)拋出異常:
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:
由于默認(rèn)初始化器的存在,如果需要執(zhí)行下列代碼,你不需要做任何設(shè)置:
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:
從前面幾章你已經(jīng)看到要確保數(shù)據(jù)庫總是匹配當(dāng)前的模型.如果Code First檢測到二者不匹配,數(shù)據(jù)庫就人被刪除并且重新創(chuàng)建以便可以滿足匹配關(guān)系.在開發(fā)時(shí)這很有用,但是顯然不在在程序部署中使用,這樣數(shù)的會(huì)丟失.我們已經(jīng)看到這樣的設(shè)置初始化的代碼:
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.
這一初始化器將不管模型與數(shù)據(jù)庫匹配與否都會(huì)刪除和重建數(shù)據(jù)庫.你可能會(huì)疑惑為什么要這么做.如果你集成測試的整個(gè)應(yīng)用程序,就會(huì)需要在運(yùn)行測試前將數(shù)據(jù)庫重置到一個(gè)已知的狀態(tài).修改Main方法(代碼6-13),運(yùn)行一些代表測試的代碼,這會(huì)在應(yīng)用程序中插入一個(gè)Destinaion.
Example 6-13. Implementation of a pseudo integration test
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).
由于初始化器被設(shè)置為每次都刪除并重建,你會(huì)知道在測試開始前數(shù)據(jù)庫是空的.你不用總是去考慮在運(yùn)行測試前數(shù)據(jù)庫是否為空,后而我們會(huì)看到放置一些種子數(shù)據(jù)在里面的例子.運(yùn)行程序,我們會(huì)看到測試通過.就目前為止我們只執(zhí)行一個(gè)單一的測試,通常一個(gè)應(yīng)用程序里面需要進(jìn)行多個(gè)測試.更新Main方法以便使其可以在一行里運(yùn)行兩次測試(代碼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.
運(yùn)行程序,你會(huì)看到第一個(gè)方法通過而第二個(gè)失敗,表明數(shù)據(jù)庫中有兩個(gè)destinations.第二個(gè)測試失敗的原因是第一次執(zhí)行的結(jié)果已經(jīng)在數(shù)據(jù)庫中了.進(jìn)一步的原因是AppDomaing默認(rèn)情況每次程序運(yùn)行只執(zhí)行一次初始化.
本章前面介紹可以使用Database.Initialize強(qiáng)制初始化,不管當(dāng)前的AppDomain是否已經(jīng)初始化過.修改RunTest方法包含一個(gè)調(diào)用Database.Initialize強(qiáng)制初始化的方法確保每次測試前都會(huì)重置數(shù)據(jù)庫(代碼6-15),再次運(yùn)行程序你會(huì)發(fā)現(xiàn)測方式現(xiàn)在通過了在每次測試執(zhí)行前.數(shù)據(jù)庫先刪除又以已知的狀態(tài)進(jìn)行重建.
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.
刪除和重建數(shù)據(jù)庫是將數(shù)據(jù)庫狀態(tài)保持在一個(gè)已知狀態(tài)的很容易的方法,但是如果運(yùn)行一系集成的測試,系統(tǒng)開銷過大.考慮使用System.Transactions.TransactionScope作為避免在每一次測試時(shí)永久存儲(chǔ)對(duì)數(shù)據(jù)庫的修改.
Creating a Custom Database Initializer
創(chuàng)建一個(gè)定制的數(shù)據(jù)庫初始化器
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框架中包含的初始化器.有時(shí)不想按照已有的初始化器的邏輯進(jìn)行工作.Database.SetInitializer 接受IDatabaseInitializer 接口,你可以通過實(shí)現(xiàn)這個(gè)接口來定制邏輯.
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.
小貼士:除了自已寫定制的初始化器,也可以引用別人創(chuàng)建的.有一個(gè)例子EFCodeFirst.CreateTablesOnly NuGet 包.這個(gè)初始化器允許你在已經(jīng)存在的數(shù)據(jù)庫進(jìn)行刪除和創(chuàng)建操作,而不是刪除和創(chuàng)建數(shù)據(jù)庫實(shí)體本身.當(dāng)你將數(shù)據(jù)庫指向一個(gè)宿主數(shù)據(jù)庫而又沒有權(quán)限刪除和創(chuàng)建整個(gè)數(shù)據(jù)庫時(shí)特別有用.
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).
你想實(shí)現(xiàn)自己的初始化器的原因可能有很多。我們來看一個(gè)簡單的場景:在數(shù)據(jù)庫刪除并重新創(chuàng)建之前給開發(fā)者一個(gè)提示。Database屬性暴露了各種方法與數(shù)據(jù)庫進(jìn)行交互,可以實(shí)現(xiàn)檢查是否存在,是否創(chuàng)建,或是否刪除等功能。API中包含的初始化器包含的邏輯利用了這些方法。在你自己的類,你也可以將這些方法整合在邏輯里。這就是下面這個(gè)例子要做的。添加PromptForDropCreateDatabaseWhenModelChages類到您的DataAccess項(xiàng)目(例6-16)。
Example 6-16. Custom initializer
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類實(shí)現(xiàn)單一的InitializeDatabase方法。首先,它檢查數(shù)據(jù)庫是否存在以及是否與當(dāng)前的模型相匹配。如果是這樣,什么也不做,初始化器返回。如果該數(shù)據(jù)庫存在,但不匹配當(dāng)前的模型,會(huì)提示你是否想刪除和創(chuàng)建數(shù)據(jù)庫。如果您決定不重新創(chuàng)建數(shù)據(jù)庫,初始化器返順,EF框架將嘗試按現(xiàn)有的數(shù)據(jù)庫模式再次運(yùn)行。如果您決定重新創(chuàng)建數(shù)據(jù)庫,現(xiàn)有的數(shù)據(jù)庫將被刪除。最后一行代碼簡單地創(chuàng)建數(shù)據(jù)庫中,只會(huì)在數(shù)據(jù)庫不存在,或者我們選擇重新創(chuàng)建數(shù)據(jù)庫才會(huì)得到執(zhí)行。
自定義的初始器在需要在EF框架內(nèi)注冊(cè);修改Main方法(例6-17)。你會(huì)注意到Main方法調(diào)用了我們?cè)诘?章更新的InsertDestination方法:
Example 6-17. Custom initializer registered in Main
static void Main() {Database.SetInitializer(newPromptForDropCreateDatabaseWhenModelChages<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:
我們對(duì)模型作些修改使其不再與數(shù)據(jù)庫匹配.修改Destinaton類中的Name屬性,在其上附加一個(gè)Data Annotations標(biāo)記: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:
現(xiàn)在運(yùn)行的應(yīng)用程序,將提示您,詢問您是否要?jiǎng)h除并創(chuàng)建數(shù)據(jù)庫。答否(N),告訴我們的自定義初始化器不理會(huì)數(shù)據(jù)庫。你會(huì)注意到,應(yīng)用程序仍然成功完成。這是因?yàn)槟龅母牟粫?huì)阻止EF框架使用當(dāng)前模型訪問過時(shí)的數(shù)據(jù)庫架構(gòu)。EF框架預(yù)期Destination Names應(yīng)為200個(gè)字符或更少。由于數(shù)據(jù)庫沒有改變,它沒有強(qiáng)制執(zhí)行的最大長度,所以EF框架給它發(fā)送的INSERT語句可以執(zhí)行。
現(xiàn)在,讓我們做出改變,會(huì)影響INSERT語句。修改目標(biāo)類,包括一個(gè)新的TravelWarnings屬性:
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.
再次運(yùn)行應(yīng)用程序。像以前一樣,你會(huì)被提示,詢問您是否要?jiǎng)h除并創(chuàng)建數(shù)據(jù)庫。選擇不創(chuàng)建數(shù)據(jù)庫,這個(gè)時(shí)候你會(huì)得到一個(gè)DbUpdateException。你需要通過內(nèi)部異常鏈去找到錯(cuò)誤的真正原因(圖6-4)。
頂層異常的內(nèi)部異常是UpdateException,該內(nèi)部異常是一個(gè)SQLException。最后的SQLException的消息,說明發(fā)生了什么:“無效的列名稱”TravelWarnings,“問題發(fā)生的原因是EF框架試圖執(zhí)行示例6-18中所示的SQL語句,但TravelWarnings列在數(shù)據(jù)庫中不存在。
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.
再次運(yùn)行程序,這次選擇刪除并重建數(shù)據(jù)庫,程序成功執(zhí)行.
Setting Database Initializers from a Configuration File
在配置文件中設(shè)置數(shù)據(jù)庫初始化器
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.
在代碼中設(shè)置的初始化是一種簡單的方法,但部署用程序時(shí),您可能希望有一個(gè)更簡單的方式設(shè)置,而無需修改代碼。想要應(yīng)用程序部署設(shè)置DropCreateDatabaseIfModelChanges的初始化器,這是極不可能的!將appSettings節(jié)添加到BreakAwayConsole項(xiàng)目的配置文件中,其中包括了初始化器的設(shè)置,見示例6-19中所示。
Example 6-19. Initializer set in configuration file
小貼士:代碼示例有一行斷裂的代碼,在實(shí)際的App.config文件中應(yīng)該刪除.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:
還應(yīng)有很多配置行,我們打破配置結(jié)構(gòu)來分別研究。關(guān)鍵部分開始于DatabaseInitializerForType,后跟一個(gè)空格,然后配置正確的上下文名稱以便初始化器能夠?yàn)槠湓O(shè)置。在我們的例子就是DataAccess.BreakAwayContext,DataAccess,僅僅意味著DataAccess.BreakAwayContext類型的定義是在DataAccess程序集。Value部分是配置數(shù)據(jù)庫初始化器要使用名稱。它看起來復(fù)雜,因?yàn)槲覀兪褂梅盒皖愋?#xff0c;我們使用了EF框架程序集中定義的DropCreateDatabaseIfModelChanges<BreakAwayContext> 方法來進(jìn)行設(shè)置。
還需要修改Main方法,以便它不再設(shè)置在代碼中的初始化:
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:
現(xiàn)在對(duì)模型作些改變以便測試配置文件是否得到應(yīng)用.修改Destination類包含一個(gè)新的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.
運(yùn)行程序,你會(huì)看到數(shù)據(jù)庫被刪除重建,新增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).
現(xiàn)在,如果你要部署你的應(yīng)用程序,你可能變更初始化器為CreateDatabaseIfNotExists,以便永遠(yuǎn)不會(huì)導(dǎo)致數(shù)據(jù)丟失。您也可能工作在別人為您創(chuàng)建的DBA上,如果數(shù)據(jù)庫在Code First工作流之外創(chuàng)建,你會(huì)想禁用數(shù)據(jù)庫的初始化。你可以通過改變配置文件來指定初始化器的禁用(代碼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.
現(xiàn)在我探索了有關(guān)在配置文件設(shè)置初始化器的方法,請(qǐng)移除已經(jīng)添加的任何設(shè)置.
Using Database Initializers to Seed Data
數(shù)據(jù)庫數(shù)據(jù)庫初始化器添加種子數(shù)據(jù)
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.
在本章中,你已經(jīng)看到數(shù)據(jù)庫的初始化可以被用來控制Code First何時(shí)以及如何創(chuàng)建數(shù)據(jù)庫。到目前為止,Code First創(chuàng)建的數(shù)據(jù)庫一直是空的,但也有一些需要Code First創(chuàng)建一些種子數(shù)據(jù)的情況。您可能有一些預(yù)定義的數(shù)據(jù),如性別或國家的查找表。或者你可能只是想在本地工作時(shí),在數(shù)據(jù)庫中放一些示例數(shù)據(jù),從而可以看到應(yīng)用程序的行為。
種子數(shù)據(jù)可以用另一種情況是運(yùn)行集成測試。在上一節(jié)中,我們寫了一個(gè)測試,依靠的是一個(gè)空的數(shù)據(jù)庫,現(xiàn)在讓我們進(jìn)行一個(gè)依賴于包含一些已知數(shù)據(jù)的數(shù)據(jù)庫的測試。
讓我們開始寫要運(yùn)行的測試。修改Main方法來運(yùn)行測試,以驗(yàn)證“Great Barrier Reef”是否為數(shù)據(jù)庫中的Destination條目(例6-21).確保您已經(jīng)刪除在上一節(jié)添加到任何設(shè)置的配置文件。
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.Destinationswhere 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.
運(yùn)行應(yīng)用程序,你會(huì)看到測試失敗,說明“Great Barrier Reef”在數(shù)據(jù)庫中沒有任何條目與之匹配。這是有道理的,因?yàn)槟阍O(shè)置了DropCreateDatabaseAlways初始化,這將創(chuàng)建和清空數(shù)據(jù)庫。
測試真正需要的是,在創(chuàng)建了數(shù)據(jù)庫后,將插入一些種子數(shù)據(jù),能夠DropCreateDatabaseAlways的變化來實(shí)現(xiàn)。包括在EF框架中的三個(gè)初始化器不是sealed的,這意味著你可以通過派生其中之一來創(chuàng)建自己的初始化器。所有三個(gè)初始化器都包括一個(gè)名為Seed的abstract方法(在Visual Basic中為Overridable),這意味著它可以被覆寫。Seed方法有一個(gè)空的實(shí)現(xiàn),但是,初始化器可以在適當(dāng)?shù)臅r(shí)候插入您提供的種子數(shù)據(jù)。
要檢查此功能,您的DataAccess項(xiàng)目添加DropCreateBreakAwayWithSeedData類。提供種子數(shù)據(jù)的關(guān)鍵是覆寫初始化種子的方法,如例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.
小貼士:注意我們?cè)谶@里沒有調(diào)用SaveChanges方法.Seed的基方法會(huì)在定制方法之后調(diào)用.如果你讓VS的編輯器自動(dòng)實(shí)現(xiàn)覆寫方法,就會(huì)包括一個(gè)對(duì)base.Seed(context)的調(diào)用.你可以不去管他,但是記住要將這行代碼放在方法的最后一行.
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.
現(xiàn)在你已經(jīng)創(chuàng)建了一個(gè)能夠插入種子數(shù)據(jù)的初始化器,它需要在EF框架中注冊(cè)后才能被使用.這可以以我們?cè)谇懊姘跏蓟飨嗤姆绞竭M(jìn)行--通過Database.SetInitializer方法
Modify the Main method so that DropCreateBreakAwayWithSeedData is registered (Example 6-23).
修改Main方法以注冊(cè)DropCreateBreakAwayWithSeedData類:
Example 6-23. Initializer with seed data is registered
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.
再次運(yùn)行應(yīng)用程序,本次測試通過,因?yàn)镃ode First現(xiàn)在使用DropCreateBreakAwayWithSeedData初始化數(shù)據(jù)庫。由于此初始化派生自DropCreateDatabaseAlways,它會(huì)刪除數(shù)據(jù)庫并重新創(chuàng)建一個(gè)空數(shù)據(jù)庫。覆寫的Seed方法,隨后被調(diào)用,您指定的種子數(shù)據(jù)插入到了新創(chuàng)建的數(shù)據(jù)庫里。
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方法或許有些簡單化,但這讓我們很好地觀察了這個(gè)方法的創(chuàng)建過程。可以插入各類數(shù)據(jù)及相關(guān)數(shù)據(jù)。例如使用一個(gè)有效率的LINQ方法利用Code First檢查我的博客,將相關(guān)文章的全部圖片作為種子插入數(shù)據(jù)庫。
Using Database Initialization to Further Affect Database Schema
使用數(shù)據(jù)庫初始化進(jìn)一步影響數(shù)據(jù)庫構(gòu)架
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在數(shù)據(jù)庫中創(chuàng)建種子數(shù)據(jù)以外,你也可不使用配置或種子數(shù)據(jù)達(dá)到相同目的.你如,你可以想創(chuàng)建Lodgings表中Name字段的索引以加快使用name查詢的速度.
你可以通過調(diào)用DbContext.Database.ExecuteSqlCommand 方法來達(dá)到目的,這個(gè)方法會(huì)在Seed方法內(nèi)部構(gòu)造創(chuàng)建索引的SQL語句.代碼6-24顯示了對(duì)Seed方法的修改,強(qiáng)制數(shù)據(jù)插入時(shí)創(chuàng)建索引.
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
小結(jié)
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.
在這一章中,你看到了默認(rèn)情況Code First如何與數(shù)據(jù)庫進(jìn)行交互,也學(xué)習(xí)到如何覆寫此默認(rèn)行為。你已經(jīng)學(xué)會(huì)了如何控制數(shù)據(jù)庫,Code First連接到數(shù)據(jù)庫時(shí)是如何初始化的。您還學(xué)到如何將數(shù)據(jù)庫的初始化用于情景測試中,如何在初始化時(shí)插入種子數(shù)據(jù)。
在這本書中,你已經(jīng)看到Code First根據(jù)您的域類和配置創(chuàng)建了一個(gè)模型,然后,你也看到Code First是如何定義和初始化被模型用來訪問的數(shù)據(jù)庫。在下一章中,您將學(xué)習(xí)一些不太常用的先進(jìn)的理念,但這些理念有時(shí)會(huì)很有用。
轉(zhuǎn)載于:https://www.cnblogs.com/guolihao/p/3208943.html
總結(jié)
以上是生活随笔為你收集整理的Code First :使用Entity. Framework编程(6) ----转发 收藏的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 获取积分
- 下一篇: 时间操作(Java版)—获取给定日期N天