博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::


Advanced Concepts



The Code First modeling functionality that you have seen so far should be enough to get you up and running with most applications. However, Code First also includes some more advanced functionality that you may require as your needs advance. Throughout this book you’ve seen Code First’s conventions in action, but if there are one or more conventions you don’t like, Code First allows you to remove them. You may also want to get rid of that EdmMetadata table Code First is adding to your database. Code First caches its model by default, and it’s possible to override that behavior to solve problems like targeting multiple database providers in the same application in stance. This chapter will cover these topics and more.

到目前为止你已经看到Code First的建模功能应足以让你和运行大多数应用。然而,Code First还包括一些更高级的功能,您可能有需要。在这本书中,您已经看到Code First的默认行为,但如果有一个或多个你不喜欢的约定,Code First允许你删除它们。您可能还希望得到摆脱EdmMetadata表添加到您的数据库。默认情况下Code First需要缓存模型,您可以覆写这种行为来解决类似在一个应用程序中指向多个数据库的这种情况。本章将涵盖更多的这些主题。

Mapping to Nontable Database Objects

So far you have used Code First to map to tables, whether you are generating a database or mapping to tables in an existing database. But databases support many other types of objects, including stored procedures and views.
As of Entity Framework 4.2, Code First only has built-in support for tables, meaning that it is only capable of generating schemas that contain tables. Therefore, if you are using Code First to generate your database, you are restricted to tables.
However, if you are mapping to an existing database, you may have views, stored procedures, and other objects in the database you are mapping to. Let’s take a look at how we can interact with those.

您已经学习了使用Code First映射到表,无论是生成一个数据库还是映射到一个现有的数据库中的表,都是针对有表的数据库进行。但数据库支持许多其他类型的对象,包括存储过程和视图。
对EF框架4.2而言,Code First只内置表支持,这意味着它只能生产生包含表的构架。因此,如果您
使用Code First生成数据库,就被限制在使用表工作上面。

You have the option of manually editing the database schema after Code First has created it. If you do manually edit the database to include nontable objects, you can apply the same techniques discussed in this section.
The Entity Framework team has indicated that they plan to add support for mapping to other database objects in future releases.

小贴士:你可以选择在Code First生成数据库构架后手工编辑数据库来包含非表对象。在本节中你就可以使用这样的技术。


Mapping to Updatable Views

In some cases you may want to simply map an entity to a view rather than a table. For example, you may be mapping to a database that has a very large and confusing schema.To simplify things, the database might contain a view that exposes the data for your entity with more comprehensible column names. If the view is updatable, you can use the Entity Framework to insert, update, and delete data as well as selecting it. Fortunately, most databases, including SQL Server, use the same SQL syntax for interacting with views as they do for tables. This means you can simply “lie” to Code First and tell it that the view is a table. You do this by using the same configuration you use for naming tables.

在某些情况下,你可能想简单地将实体映射到视图,而不是表。例如,您可能需要映射到一个数据库,它有一个非常大的和混乱的构架。为了简单,数据库可能包含一个视图,使用相比实体更容易理解的列名暴露数据。如果视图是可更新的,您可以使用EF框架来插入,更新和删除数据以及选择数据。幸运的是,大多数数据库,包括SQL Server,使用相同的SQL语法与视图交互,就好像与表格进行交互一样。这意味着你可以简单地“欺骗”Code First,并告诉它那个视图就是一个表。您可以通过使用命名表中相同的配置的方法来控制它。

小贴士:不懂可更新视图?请到MSDN中的 SQL Server’s “CREATE VIEW (Transact-SQL)”去看看,会有所帮助。

For example, perhaps you want the Destination data to come from an updateable view called my_destination_view rather than a table. You can use the Table annotation to specify the view name:


public class Destination

Alternatively, you can use the ToTable method from the Fluent API to map to the view:

你也可能使用Fluent API的ToTable方法来映射到视图:

Using Views to Populate Objects

Not all scenarios call for mapping an entity directly to an updateable view. You may find yourself wanting to leave a class mapped to a table but to have the ability to use a view to retrieve a set of those classes in a particular scenario. For example, let’s assume that you want to leave Destination mapped to the Destinations table, but in one area of your application you want to load all the destinations from the TopTenDestinations view. You can use the SqlQuery method on DbSet to load entities based on some SQL that you write:


var destinations = context.Destinations
    .SqlQuery("SELECT * FROM dbo.TopTenDestinations");

In the above code we are using a SQL statement that bypasses Entity Framework to get back the desired Destination objects. The good thing is that once those objects are retrieved from the database, they are treated exactly the same as objects that were loaded any other way. This means you still get change tracking, lazy loading, and other DbContext features for the Destination objects that were loaded.


The SqlQuery method relies on an exact match between the column names in the result set of the query you wrote and the names of the properties in your object. Because the Destination class contains DestinationId, Name, and other properties, the view must return columns with these same names. If the view does not have the same column names as the properties on your class, you will need to alias the columns in your select statement.
For example, let’s say that your TopTenDestinations view uses Id instead of DestinationId for the primary key name. In SQL Server, you can use the AS word to  alias the Id column from the view as the DestinationId column that Entity Framework
is expecting, as you can see in Example 7-1.

例如,TopTenDestinations视图使用Id而不是DestinationId作为主键的名称。在SQL Server中,可以使用AS关键字为Id列指定DestinationId列的别名,满足@EF的要求,代码7-1对此进行了解释:

Example 7-1. Querying a database view from a DbSet

var destinations = context.Destinations
                Id AS DestinationId,
              FROM dbo.TopTenDestinations");

Note that the column-to-property name matching does not take any mapping into   
account. For example, if you had mapped the DestinationId property to a column      
called Id in the Destinations table, the SqlQuery method would not use this mapping.  The SqlQuery method always attempts the column-to-property matching based on property name. Therefore, the column in the result set would still need to be called DestinationId.

请注意,列与属性的名称匹配并未考虑任何到帐户的映射。例如,如果你映射DestinationId属性到Desintaions表中的Id列,SqlQuery方法不会使用这种映射。 sqlquery函数的方法总是尝试其于属性名来匹配列-属性关系。因此,仍然需要在结果集中将此列称为DestinationId。

Using Views to Populate Nonmodel Objects

The two techniques we have looked at so far allow you to use a view to populate a set of objects that are part of your model. Once these objects are created, they are tracked by the context and any changes will be written back to the database. You may find yourself wanting to get the results of a view back into a read-only set of objects. The results of the view may combine data from multiple tables and therefore can’t be mapped directly to an entity that is part of your model.
For example, you may have a view called DestinationSummaryView that combines data from the Destinations and Lodgings tables. This view may have DestinationId, Name, LodgingCount, and ResortCount columns. These columns don’t match any of the entities in the BAGA model, but it would be great to be able to get the results back into a purpose-built object that you can then use in your application.


The DestinationSummary class might look something like Example 7-2.


Example 7-2. DestinationSummary implementation

public class DestinationSummary
  public int DestinationId { get; set; }
  public string Name { get; set; }
  public int LodgingCount { get; set; }
  public int ResortCount { get; set; }

Because the class isn’t part of the BAGA model, you can’t use a DbSet to query for
results. Instead, you use the SqlQuery method on DbContext.Database as follows:


var summary = context.Database.SqlQuery<DestinationSummary>(
  "SELECT * FROM dbo.DestinationSummaryView");

In response, Entity Framework will run the SQL that you supplied to access the DestinationSummaryView view. It will then take these results and try to match the column names up with the property names of the DestinationSummary class that you specified in the generic argument of SqlQuery. Because the column and property names match, we will get the results of the query in a collection of DestinationSummary objects.
Because we didn’t go through a DbSet as we did in Example 7-1, the DestinationSummary objects that are created are not tracked by the context. Therefore, if you change any of the properties, Entity Framework will not pay any attention to those changes any time SaveChanges is called.


Working with Stored Procedures

Code First does not have any support for mapping Insert, Update, and Delete statements for your classes directly to stored procedures, as you are able to do in the designer.

Code First并不支持直接通过存储过程来映射插入,更新和删除命令,但是你可以在设计器中使用这些命令。


Using the same techniques you just saw for working with views, you can also use stored procedures to fetch results from the database. For example, let’s say you have a Get TopTenDestinations stored procedure that takes a single parameter to specify in which country to look for destinations. You can use the SqlQuery method on DbSet to execute this procedure:

使用你刚刚在视图中同样的技术,也可使用存储过程从数据库中生成结果。例如,有一个名为TopTenDestinations 的存储过程通过一个单一的参数来指定在哪个国家内查找目的地。你可以使用DbSet的SqlQuery方法来生成这个过程:

var country = "Australia";
var destinations = context.Destinations
  .SqlQuery("dbo.GetTopTenDestinations @p0", country);

Notice that SqlQuery accepts parameters. See the sidebar “SqlQuery Parameters to
Prevent SQL Injection” on page 157 for more information.


As you saw above with views, you can also use the DbContext.Database.SqlQuery method to get back results from stored procedures that don’t match an entity in your model. Let’s assume you have a GetDestinationSummary stored procedure and you want to get the results in a collection of the DestinationSummary class you saw back in Example 7-2. Let’s also say this stored procedure takes two parameters—one for the country and the other for some keywords:


var country = "Australia";
var keyWords = "Beach, Sun";
var destinations = context.Database.SqlQuery<DestinationSummary>(
  "dbo.GetDestinationSummary @p0, @p1", country, keyWords);

In the above code, you can see that we’re using index-based naming for parameters. As noted in the sidebar, Entity Framework will wrap these parameters up as DbParameter objects for you to avoid any SQL injection issues. The column names in the result returned by the stored procedure will be matched with the property names on DestinationSummary. Because DestinationSummary isn’t part of the BAGA model, the results are not tracked and any changes will not be pushed back to the database.


SqlQuery Parameters to Prevent SQL Injection
The SqlQuery method allows you to specify parameters. Entity Framework will take    
care of wrapping these into DbParameter objects to help prevent against SQL injection attacks. You use a @p prefix for parameters followed by an integer index. Entity Framework will then match these indexes up with the list of parameters you provide after the query string. As with the view-based example you saw earlier, the results of the query are tracked by the context and behave the same as results of any other query.



Removing Conventions

In previous chapters you have seen that Code First includes a set of conventions that help build your model. You’ve seen how you can supplement or override what the conventions do using Data Annotations or the Fluent API. One other option you have is to switch off one or more of the default conventions.
Each Code First convention is implemented as a class in the System.Data.Entity.ModelConfiguration.Conventions namespace. Code First currently only allows you to remove one or more of the included conventions.
从前面的章节中可以看到,Code First包含了一系列的约定帮助你创建模型。你也看到如何设置或覆写默认规则,可以使用Data Annoations,也可使用@FA。还有一个选项可以关闭一个或多个默认约定。

每个Code First的默认约定都被配置为一个类,这个类位于System.Data.Entity.ModelConfiguration.Conventions 名称空间。Code First目前只允许移除一个多个包含的默认规则。

The ability to write your own conventions was included in a preview of Code First. However, the Entity Framework team removed this functionality because they felt that they didn’t have time to polish the design and get to the appropriate quality level without holding up the muchawaited release of Code First. It’s likely this feature will become available again in a future release.

小贴士:Code First的预览版本支持编写自己的“约定”代码。但是,EF框架团队在正式版取消了此功能,因为他们认为他们没有时间去完善并不是太过令信期待的功能。很可能在未来的版本会再次提供此功能。

A full list of the Code First conventions that can be removed and a description of what each convention does is available at http://msdn.microsoft.com/en-us/library/
gg696316(v=VS.103).aspx. The complete list of conventions is also shown in
Figure 7-1


While you can remove any of the conventions listed in Figure 7-1, we’ll use just one
OneToManyCascadeDelete—to demonstrate how to go about this process. This convention adds a cascade delete rule to all required relationships.


While you could just override the cascade behavior for every required relationship, if  you have a lot of relationships, it may make more sense just to disable the convention altogether.
Switching off conventions is done in the OnModelCreating method on your context via the DbModelBuilder.Conventions.Remove method. Add the following line of code to OnModelCreating in your BreakAwayContext class:




The model contains a required relationship between Lodging and Destination. Up until now, Code First has been automatically adding a cascade delete rule to this relationship.With the new code in place, run the application so that the database gets recreated and this cascade delete will be removed from this relationship in the model and in the database (Figure 7-2). It will also disappear from any other required relationships that may exist.

在模型中有一个必须的关系:Lodging与Destination.当前,Code First自动为这个关系添加了一个级联删除。使用新代码后,运行程序,级联删除就从模型和数据库中被移除了(见图7-2)。任何其他必须关系的级联删除都被去除了。


After switching off the conventions, you may decide that you want to reintroduce cas
cade delete behavior on some of the relationships. You can do this using the Fluent API as described back in Chapter 4.

In the BAGA model, it makes sense for us to have cascade delete enabled on required relationships, so go ahead and re-enable the OneToManyCascadeDeleteConvention convention by removing the modelBuilder.Conventions.Remove call we just added.

关闭默认约定后,你可能还想在许多关系中重新引入级联删除行为。你可以使用Fluent API来进行设置。(见第4章)


Taking Control of Model Caching

Throughout this book you have seen how Code First takes care of a lot of things for you, but that you can take control of them and change the behavior when needed. Model caching is no exception; in fact, you likely had no idea that Code First was caching a model for you up to this point. After scanning your classes and applying conventions and configuration, Code First keeps an in-memory version of your model around so that it can be reused in the application instance. This is the reason that the OnModelCreating method is only hit once for each DbContext in an application instance. In this section, you will learn more about what model caching is, when you might need to override the conventions, and how you go about doing that.

在这本书中,你已经看到Code First管理了很多东西,但是,你可以在需要控制它,改变它的行为。模型缓存也不例外,事实上,你可能没想到,Code First为你进行了模型的缓存。在扫描类并使用默认约定与配置后,Code First就会使模型的一个版本保持在内存中,以便可以被应用程序的实例重用。这是因为应用程序实例的每一个Dbcontext只调用一次OnModelCreating方法。在本节中,您将学习模型缓存是什么,如果需要重写约定,应如何去做。

Understanding Model Caching

In earlier chapters, you have seen that Code First will automatically discover and build a model based on the DbSet properties that you expose on your context. The model  creation process involves taking that set of entity types, running the Code First conventions on them, and then applying any additional configuration that you specified via Data Annotations or the Fluent API. This process isn’t cheap on resources and can take some time, especially if your model is large and/or complex. To avoid incurring this cost every time you create an instance of your context, Code First runs this process once and then caches the final model for your context type. Model caching occurs at the AppDomain level.
You can see model caching in action by monitoring when the OnModelCreating method is called on your context. Add a line to the OnModelCreating method that will write to the console whenever it is called:

在前面的章节中,你已经看到,Code First会自动发现和建立一个模型,这个模型基于你在context中暴露的DbSet属性而创建。模型创建过程中涉及到提取实体类型设置,配置Code First约定以及启用任何由@DA或@FA实施的附加配置。这个过程很耗费资源,可能会需要一些时间,尤其是在模型很大和/或复杂的时候。为了避免每次创建上下文的一个实例所需的开销过大,Code First每运行一次这个过程,就会缓存上下文类型的最后一个模型。模型缓存发生在AppDomain级别。
Console.WriteLine("OnModelCreating called!");
Modify the Main method to call the InsertDestination method a number of times 
(Example 7-3). You added the InsertDestination method itself back in Chapter 2.  


Example 7-3. Main updated to use the context multiple times

static void Main()
    new DropCreateDatabaseIfModelChanges<BreakAwayContext>());

After running the application again, you will see that although the code constructs and uses three separate instances of BreakAwayContext, the OnModelCreating method is only called once. This is because Code First only calls OnModelCreating while creating the model for the first context instance; after that, the final model is cached and is reused for the following uses of BreakAwayContext.

运行程序,你会发现尽管代码构造和使用了三个BreakAwayContext实例,OnModelCrating方法只被调用了一次。这是因为Code First只有当第一次context实例创建模型时和进行调用;在那以后,最后一个模型就被加入缓存,在后面的BreakAwayContext需要时被重用。

Overriding Default Model Caching

There aren’t many situations where you need to take control of model caching. Provided that the model for a given context type stays the same for every instance of that context with an AppDomain, the default behavior is going to work as expected. Using the default behavior is also going to give you the best performance, because model creation will only occur one time.
There are some situations where the model for a given context type may vary between instances in the same AppDomain. One example would be using a multitenant database. A multitenant database involves having the same model replicated multiple times in the same physical database. For example, you may have a model that is used to store blog posts and a website that displays them. Your website might contain a personal and a work blog that both use the same model. In the database you could have the tables used to store the data for this model replicated in two separate schemas. The tables for your work blog may live in the work schema (work.Posts, work.Comments, etc.) and the tables for your personal blog might live in the personal schema (personal.Posts, personal.Comments, etc.). Each of these sets of tables is known as a tenant. Database schemas are just one way to distinguish between tenants; there are many other patterns, such as table prefixes.
If your application needs to access multiple tenants from the same AppDomain, the mapping between classes and tables is going to be different depending on what tenant you are targeting. Different mapping means a different model, which in turn means the default model caching won’t work for you.


Another example would be using the same context to target the same model on different database providers in the same AppDomain. Different database providers means different data types for the columns in the database, which in turn means a different model. Let’s take a look at this scenario and how to handle model caching.
Add the TargetMultipleProviders method shown in Example 7-4. This method uses
the same context to access a SQL Server and SQL Server Compact Edition database.

同样的上下文访问SQL Server和SQL Server Compact Edition数据库。

You will need the SQL Server Compact Edition runtime installed to complete this section. If you have completed Chapter 6, you have already installed the runtime. If not, see “Installing SQL Server Compact Edition” on page 135. You may also remember that in Chapter 6 we had to change our model to target SQL Compact. If you want to test out this code, you will need to make the same change again here. 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.


完成本节需要安装SQL ServerCompact Edition运行时。如果您在第6章,已经安装了运行时,请继续。如果没有,请参阅前述的“安装SQL Server Compact Edition”。您可能还记得,在第6章中,我们不得不改变我们的模型,指向SQL Compact Edition。如果你想测试一下这个代码,你需要在这里再次作出同样的变化。早在第3章,我们配置数据库生成的键Trip.Identifier标识符是一个GUID属性,使用在SQL Server上没有任何问题。 SQL Compact不能产生的GUID列的值。的如果你想运行应用程序,删除数据库生成Trip.Identifier的Data Annoations或Fluent API配置代码。

Example 7-4. Reusing a context to target multiple providers

static void Main(string[] args)
private static void TargetMultipleProviders()
  var sqlString = @"Server=.\SQLEXPRESS;
  using (var connection = new SqlConnection(sqlString))
    using (var context = new BreakAwayContext(connection))
      context.Destinations.Add(new Destination { Name = "Hawaii" });
  var sqlCeString =
   @"Data Source=|AppData|\DataAccess.BreakAwayContext.sdf";
  using (var connection = new SqlCeConnection(sqlCeString))
    using (var context = new BreakAwayContext(connection))
      context.Destinations.Add(new Destination { Name = "Hawaii" });

Run the application. You will get an exception when trying to use the context instance that targets SQL Server Compact Edition. This will be a NotSupportedException stating that, “Using the same DbCompiledModel to create contexts against different types of database servers is not supported. Instead, create a separate DbCompiledModel for each type of server being used.”
To use the same context type with different models in the same AppDomain, you need to externally build a DbCompiledModel for each model and then use these to construct the different context instances. DbContext exposes a set of constructors that allow you to supply the model to be used, along with connection information. Add a constructor to the BreakAwayContext class that allows a DbCompiledModel and a DbConnection to be supplied:

运行程序,在试图使用上下文实例指向SQL Server Compact Edition时会得到一个异常。这是一个NotSupportedException,言明:“不支持在不同类型的数据服务中使用同一DbCompiledModel创建contexts.可以为每种服务器创建一个单独的context类型”。

为了在同一AppDomain中使用同一context指向不同的模型,你需要为每个模型构建一个DbCompiledModel,然后使用这个模型构建不同的上下文实例。DbContext暴露了一系构造器允许你支持模型这样使用。添加一个构造器到BreakAwayContext类中,允许支持DbCompiledModel 和DbConnection:

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

The code in Example 7-5 shows an updated TargetMultipleProviders method that demonstrates how this constructor can now be used to target different database providers, using a different model for each.


Example 7-5. Code updated to work with multiple providers

private static void TargetMultipleProviders()
  var sql_model = GetBuilder().Build(
    new DbProviderInfo("System.Data.SqlClient", "2008"))
  var ce_model = GetBuilder().Build(
    new DbProviderInfo("System.Data.SqlServerCe.4.0", "4.0"))
  var sql_cstr = @"Server=.\SQLEXPRESS;
  using (var connection = new SqlConnection(sql_cstr))
    using (var context =
      new BreakAwayContext(connection, sql_model))
      context.Destinations.Add(new Destination { Name = "Hawaii" });
  var ce_cstr = 
    @"Data Source=|DataDirectory|\DataAccess.BreakAwayContext.sdf";
  using (var connection = new SqlCeConnection(ce_cstr))
    using (var context = new BreakAwayContext(connection, ce_model))
      context.Database.Initialize(force: true);
      context.Destinations.Add(new Destination { Name = "Hawaii" });
private static DbModelBuilder GetBuilder()
  var builder = new DbModelBuilder();
  return builder;

Let’s walk through what the code in the TargetMultipleProviders method is doing. The GetBuilder method is responsible for creating a DbModelBuilder and registering all your classes with the builder. The code in the example registers each class using the DbModelBuilder.Entity and DbModelBuilder.ComplexType methods. This approach will work if you have been using Data Annotations to configure your classes. If you have been using the Fluent API, you should copy the code from your OnModelCreating method to replace this code. Note that you also need to include the EdmMetadata class and map it to the EdmMetadata table; this allows Code First to detect when the model and database go out of sync. When DbContext is responsible for building the model, it will take care of adding this class for you.
我们来看看TargetMultipleProviders方法中的代码做了什么。 GetBuilder方法负责创建一个DbModelBuilder,使用builder注册所有的类。示例代码中的每个类都使用DbModelBuilder.Entity和DbModelBuilder.ComplexType方法进行了注册。这种方法将正常工作在已经使用Data Annoations配置过的类上。如果使用Fluent API,应该从OnModelCreating方法复制的代码来替换这部分代码。请注意,还需要包括EdmMetadata类,并将其映射到EdmMetadata表中,这使Code First可以到检测到模型和数据库是否同步。当由DbContext负责创建模型时,会对此类自动维护。

The next step is to build and compile the model for the two providers that are going to be targeted. In the example, the invariant name and manifest token for the database provider are supplied to the Build method. As an alternative, there is another overload of Build that accepts a DbConnection to get the provider information from.


With the compiled models created, they can now be used to access the two different databases. Remember that database initialization only occurs once per AppDomain, so only the first database to be used will be initialized automatically. The call to Database.Initialize on the context targeting the second database ensures that the second database is also initialized.
In the end, the new Destination is added to two different databases using the same set of classes and configurations to define duplicate models. Now that we’re done using SQL Compact, go ahead and re-enable the configuration to make Trip.Identifier database-generated.

最后,新的Destination被添加到两个不同的数据库中,使用的是相同的类和相同的配置设置定义了重复的模型。现在,我们使用过SQL Compact了,重新启用以往配置,以使Trip.Identifier键由数据库生成。

Remember that building and compiling the model are expensive operations. The resulting compiled model should be cached and reused for all context instances that target the same model.


Working with the EdmMetadata Table


Back in Chapter 2, you learned that, by default, Code First adds an EdmMetadata table to your database. There are some advantages in allowing Code First to have this table in the database, but you also have the option of removing it. In this section, you will see how to remove the EdmMetadata table from your database. You’ll also learn about the implications of removing it.
The EdmMetadata table serves a single purpose, and that is to store a snapshot of the model that was used to create the database. Having the snapshot allows Code First to check whether the current model matches the current database or not. The snapshot is stored by taking a SHA256 hash of the database portion of the model. You can see in Figure 7-3 that the EdmMetadata table always contains a single row with the hash stored in it.

早在第2章中,您就学习到,默认情况下,Code First会将EdmMetadata表添加到您的数据库。允许Code First在数据库中创建这个表有一定的好处,但您也可以选择删除它。在本节中,你会看到如何从数据库中删除EdmMetadata表。您还可以了解删除它的影响。
EdmMetadata表的存在只有一个目的,那就是存储被用来创建数据库的模型快照。快照允许Code First检查当前模型是否匹配当前数据库。快照以model数据库部分的SHA256哈希形式来存储。你可以看到在图7-3 EdmMetadata表总是包含一个存储有哈希值的单列。


Coding Against EdmMetadata

Code First uses the EdmMetadata table in the included database initializers, but you can also interact with it programmatically using the EdmMetadata class in the EntityFramework API. Modify the Main method to call a new UseEdmMetadataTable method, shown in Example 7-6, to experiment with this class:

Code First在包含的数据库初始化器中使用EdmMetadata表,但是你也可以使用编程方式与其交互,通过在@EFAPI中EdmMetadata类来实现。修改Main方法,调用一个新的UseEdmMetadataTable方法,如代码7-6所示:
Example 7-6. The UseEdmMetadata method

Example 7-6. The UseEdmMetadata method
static void Main()
    new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
private static void UseEdmMetadataTable()
  using (var context = new BreakAwayContext())
    var modelHash = EdmMetadata.TryGetModelHash(context);
    Console.WriteLine("Current Model Hash: {0}", modelHash);
    var databaseHash =
    Console.WriteLine("Current Database Hash: {0}", databaseHash);
    var compatible =
     context.Database.CompatibleWithModel(throwIfNoMetadata: true);
Console.WriteLine("Model Compatible With Database?: {0}",

This code starts by using the static EdmMetadata.TryGetModelHash method to find the hash for the current model. This method will always work for Code First models, but if you attempt to use it with a model created using the designer, it will return null. The EdmMetadata class is included as part of your model, so you can use your DbContext to interact with it.
The second section of code creates a DbSet for the EdmMetadata class and then asks for the single row of data so that it can read the hash value from it. Finally, there is a    DbContext.Database.bvgt65 method that makes it simple to check if the model and database match. This is the method that the database initializers included in the Entity Framework make use of. Specifying true for the throwIfNoMetadata parameter will cause an exception to be thrown if the EdmMetadata table has been excluded from the database. Specifying false will cause the method to return false if the table is excluded. You can run the code and see that everything currently matches.

此代码开始使用静态的EdmMetadata.TryGetModelHash方法,找到当前模型的哈希值。此方法会始终工作在Code First模型中,但如果您尝试与设计器创建的模型进行工作,将返回null。 EdmMetadata类作为你的模型的一部分,所以你可以用你的DbContext与它交互。

Using Code First with ObjectContext
在Code First中使用ObjectContext

Up until now, you have seen Code First being used with the DbContext API, which is    the recommended API surface for working with Code First. DbContext was  introduced in Entity Framework 4.1 as a lighter-weight and more productive wrapper over the existing Entity Framework components. The alternative to DbContext is the ObjectContext API, and while it is recommended to use DbContext with Code First, it is still possible to use ObjectContext. In this chapter, you will see how to build a Code First model and use it to construct an ObjectContext.

至此,Code First都在使用DbContext API进行工作,这是Code First的推荐API。DbContext是一个轻型工具,在EF框架4.1中引入,封装了现有的其他EF框架组件,更具生产力。可以使用ObjectContext代替DbContext。本章,你会看到如何使用ObjcetContext来创建Code First模型。

DbContext or ObjectContext?
DbContext is simply a wrapper over ObjectContext and associated classes. If you need some of the more advanced features that are only available from ObjectContext, you can cast DbContext to the IObjectContextAdapter interface to access the underlying ObjectContext. This approach allows you to access the functionality from ObjectCon text while still being able to write most of your code against the newer DbContext. You might consider using Code First with ObjectContext if you have existing applications that are based on ObjectContext and you are swapping from Model First or Database First to Code First.


DbContext封装自ObjectContext,并对相关类进行了简化。如果你需要一些只有ObjectContext才能提供的高级功能 ,可以强制DbContext实现 IObjectContextAdapter接口访问底层的ObjectContext。这种方法允许您访问ObjectCon文本的功能,同时仍然能够对新的DbContext写你的代码。您可能会考虑使用ObjectContext的代码,首先,如果您有现有的,是基于ObjectContext和你交换首先从型号的第一或数据库代码的应用。

Similar to using a DbContext-based context, you start by creating a derived context,   except this time it derives from ObjectContext and exposes ObjectSet properties instead of DbSet properties. Notice in Example 7-8 that when using an ObjectContext, you need to write a bit more code than with the DbContext. You must expose a constructor that accepts an EntityConnection. The ObjectSet properties also need to be initialized using the CreateObjectSet method; this is something DbContext takes care of for you.


Add this new BreakAwayObjectContext class to your DataAccess project.


Example 7-8. Implementing ObjectContext

using System.Data.EntityClient;
using System.Data.Objects;
using Model;
namespace DataAccess
  public class BreakAwayObjectContext : ObjectContext
    public BreakAwayObjectContext(EntityConnection connection)
      : base(connection)
      this.Destinations = this.CreateObjectSet<Destination>();
      this.Lodgings = this.CreateObjectSet<Lodging>();
      this.Trips = this.CreateObjectSet<Trip>();
      this.People = this.CreateObjectSet<Person>();
      this.PersonPhotos = this.CreateObjectSet<PersonPhoto>();
    public ObjectSet<Destination> Destinations { get; private set; }
    public ObjectSet<Lodging> Lodgings { get; private set; }
    public ObjectSet<Trip> Trips { get; private set; }
    public ObjectSet<Person> People { get; private set; }
    public ObjectSet<PersonPhoto> PersonPhotos { get; private set; }

At this point, DbContext would take care of scanning the DbSet properties and building a model based on them. But ObjectContext has no built-in support for Code First. Code First provides a method to bridge this gap—DbModelBuilder.UseObjectContext. In the following walkthrough, you’ll learn how to leverage this to create an ObjectContext from a Code First model.

DbContext能够自动扫描DbSet属性并创建一个基于这些属性的模型。但是ObjectContext没有为Code First内建支持。Code First提供了一个方法来填补这个鸿沟,这个方法就是DbModelBuilder.UseObjectContext. 下面,我们来看如何使用这个方法在Code First模型中创建ObjectContext.

Modify the Main method to make use of a new UseObjectContext method, as shown in Example 7-9.

Example 7-9. Code updated to use BreakAwayObjectContext

static void Main()
private static void UseObjectContext()
  var builder = GetBuilder();
  var cstr = @"Server=.\SQLEXPRESS;
using (var connection = new SqlConnection(cstr))
    var model = builder.Build(connection).Compile();
    using (var context =
      if (!context.DatabaseExists())
      new Destination { Name = "Ayers Rock" });

You start by creating a DbModelBuilder, using the GetBuilder method we added earlier in this chapter. You then use the model builder to create a model and compile it. Note that you must supply the connection or provider information when building the model, as the provider affects the data types, etc. in the resulting model. With the model compiled, you can then use the CreateObjectContext method to construct the ObjectContext. This method relies on the constructor you exposed that accepts a single Entity Connection parameter. ObjectContext doesn’t support database initializers, so you also need to write code to check if the database exists and create it if it does not. Note that ObjectContext does not support EdmMetadata either, so there is no way to detect if the model and database are compatible.




In this chapter, you saw a variety of advanced features that Code First provides. These features are provided to make sure that you can override what Code First does by default in situations where the default behavior just doesn’t work for your scenario. Most applications won’t require you to use these features, but it’s good to have an understanding of what is available in case you ever need them.
We’ve now covered Code First from top to bottom. We started with a high-level overview of what Code First is. You then learned how Code First builds the model and how you can customize the model by using configuration. You saw how to influence which database Code First connects to and how that database is initialized. And finally in this chapter, we wrapped things up with some advanced topics.
The final chapter of this book will help prepare you for what’s coming in future releases of Code First.

本章提供了Code First提供的一些高级特征,这些特性确保在默认行为不能满足您工作场景需要时能够覆写Code First的默认设置。大多数应用程序者不需要你使用这些特征,但是理解这些特征可以作为不时之需。

到目前为止我们已经全面介绍了Code First。我们开始于一个高级别的概览。然后学习了如何使用Code First构建模型,如何使用配置定制模型。还学习了如何影响Code First连接哪一个数据库,如何让数据库初始化。最后在本章中,我们讨论了一些高级话题。本书的最后一章将帮您为未来的新版本发布作些准备。

posted on 2012-01-06 20:26  qouoww  阅读(6087)  评论(5编辑  收藏