第七章:高级概念

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

映射到非表数据库对象

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

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

EF框架开发团队声称他们已经计划在未来的版本技持非表数据库对象的映射。

映射到可更新的视图

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

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

例如,假设有一个Destination数据是来自于一个可更新的视图,名为my_destination_view。你可以使用Table标记为其指定视图名称:

[Table("my_detination_view")]
public class Destination

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

modelBuilder.Entity < Destination > ().ToTable("my_detination_view");

使用视图填充对象

并非所有的情况下都可调用实体直接映射到一个可更新的视图。在某些特殊场景里,如果一个类已经映射到一个表,而需要视图能够来调用一组这样的类。举个例子,假设你已经让Destination表映射到了Destinations表,而在您的应用程序的一个区域里需要从TopTenDestination视图中加载所有的destinations。您就可以使用DbSet的SqlQuery方法来加载你写的一些以SQL为基础的实体:

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

在上面的代码中,我们使用一个SQL语句,绕过实体框架取回所需的目标对象。好消息是,一旦这些对象从数据库中检索,它们被视为与其他方式加载的对象相同。这意味着你仍然能为加载的目标对象获得变化跟踪,延迟加载,和其他DbContext功能。

SqlQuery函数的方法依赖于在查询结果集的列名和对象属性名的精确匹配。由于目标类包含DestinationId,Name和其他属性,视图必须返回与其相同名称的列。如果视图没有与类属性相同的列名,需要在SELECT语句中的为列设置别名。 例如,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.SqlQuery(@"SELECT
                Id AS DestinationId,
                Name,
                Country,
                Description,
                Photo
              FROM dbo.TopTenDestinations");

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

使用视图来填充非模型对象

我们已经了解了两个技术,都可以使用视图来填充一个对象,成为模型的一部分。一旦这些对象被创建,将会被上下文跟踪,任何更改都会写回数据库。您可能会发现视图的结果返回的是只读的对象集。视图的结果可能会将多个表中的数据结合起来,因此不能直接映射到一个作为模型一部分的实体中。 例如,你可能有一个视图,命名为DestinationSummaryView,组合了Destinations和Lodgings表的数据。这个视图可能有DestinationId,Name,LodgingCount,和ResortCount列。这些列不匹配任何在BAGA模型内的实体,但它能够将结果返回一个专用的对象,你可以在程序中自由使用。

DestinationSummary类类似代码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;
    }
}

由于此类不是BAGA模型的一部分,不能使用DbSet来查询结果。相反,需要使用SqlQuery方法查询DbContext.Database:

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

作为回应,EF框架将运行由您提供的SQL访问DestinationSummaryView视图。然后将这些结果尝试使用DestinationSummary类的属性名匹配您在SqlQuery方法的范型参数指定的列名。因为列名和属性名相匹配,我们可以从DestinationSummary对象的集合中得到的查询结果。 在例7-1中,我们并没有通过DbSet创建DestinationSummary对象,因此不会被上下文跟踪到。因此,如果你改变任何属性,EF框架将不会关注任何在调用SaveChanges时发生的任何变化。

使用存储过程

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

小贴士:EF框架开发团队已经接到了大量的用户要求支持此功能,声明在后续版本中可能会加入支持。

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

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

注意到SqlQuery接受一个参数,见后面“使用SqlQuery参数避免SQL注入”获取更多信息。

与视图类似,你可以使用DbContext.Database.SqlQuery方法获得存储过程返回的数据,这些存储过程并不与模型中的实体相匹配。假设有一个GetDestinationSummary存储过程,想要获取的结果是DestinationSummary类的集合(代码7-2)。现在给存储过程提供两个参数,一个为country,另一个为某个关键词:

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

在上述代码中,我们为参数使用了索引名。@EF将会封装这些参数为DbParameter对象以避免任何SQL注入问题。由存储过程返回的列名将会匹配DestinationSummary的属性名。由于DestinationSummary并不是BAGA模型的一部分,结果不会被跟踪,任何更改也不会返回到数据库中。

小贴士:使用SqlQuery参数防止SQL注入

SqlQuery方法允许指定参数。EF框架将封装参数为DbParameter对象以防止SQL注入式攻击。使用对参数@p前缀后附一个整数索引号来表达。EF框架将以这些索引号匹配在查询字符串中提供的参数值。正如你在前面看到的基于视图的案例,查询结果可以被上下文所跟踪,像其他查询结果一样使用。

删除默认规则

从前面的章节中可以看到,Code First包含了一系列的约定帮助你创建模型。你也看到如何设置或覆写默认规则,可以使用Data Annoations,也可使用@FA。还有一个选项可以关闭一个或多个默认约定。

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

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

有关类列表取自微软网站, http://msdn.microsoft.com/zh-cn/library/gg696316(v=VS.103).aspx ,目前尚无中文版本。

我们可以移除上述列表中的任何一个默认规则。这里我们只尝试对OneToManyCascadeDelete进行移除作为展示。这个默认规则对所有必须的关系创建级联删除。

当然可以覆写每个必须关系的级联行为。但是如有很多关系,可能需要同时禁用默认规则。

关闭默认规则在context的OnModelCreating方法内进行,通过调用DbModelBuilder.Conventions.Remove方法就可以实现。在BreakAwayContext类中的OnmodelCrating方法加入如下代码:

modelBuilder.Conventions.Remove < OneToManyCascadeDeleteConvention > ();

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

image

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

在BAGA模型中,需要在必须的关系中具有级联删除功能,请删除上述代码,恢复 OneToManyCascadeDeleteConvention默认规则。

控制模型缓存

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

理解模型缓存

在前面的章节中,你已经看到,Code First会自动发现和建立一个模型,这个模型基于你在context中暴露的DbSet属性而创建。模型创建过程中涉及到提取实体类型设置,配置Code First约定以及启用任何由@DA或@FA实施的附加配置。这个过程很耗费资源,可能会需要一些时间,尤其是在模型很大和/或复杂的时候。为了避免每次创建上下文的一个实例所需的开销过大,Code First每运行一次这个过程,就会缓存上下文类型的最后一个模型。模型缓存发生在AppDomain级别。 通过在context中调用OnModelCreating方法可以看到模型缓存行为,在OnModelCreating方法中添加一行如下的代码,只要被调用就会写入控制台: 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.

修改Main方法调用InsertDestination方法一定次数(代码7-3)。InsertDestination方法见第2章。

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

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

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

Overriding Default Model Caching

覆写默认的模型缓存

有许多情况需要控制模式缓存。只要AppDomain中的每一个实例中上下文类型的模型保持相同,默认行为就会按预期方式工作。使用默认行为,可以获得最好的性能,因为模型的创建将只发生一次。 有某些情况下,给定的上下文类型的模型,在同一AppDomain中实例之间可能会有所不同。一个例子是使用多租赁数据库。多租赁数据库会涉及到在同一物理数据库具有相同的模式的实例会复制多闪。例如,可能有一个模型用于存存储blog文章,同时也用于在网站上予以显示。您的网站可能包含个人和工作博客,都使用相同的模型。在数据库中你可能将这一模型复制存付两个单独构架中的表中。工作博客使用的表可能位于work构架中(如work.Posts,work.Comments等),而个人博客可能位于personal构架中(如personal.Posts,personal.Comments等)。这些表被称为租客。数据库模式只是一种区分租户的方法;还有许多其他的模式,如表前缀,。 如果您的应用程序需要访问相同的AppDomain多个租户,类和表之间的映射将是不同的,取决于你的目标租户。不同的映射意味着不同的模型,这反过来又意味着默认模型缓存不会为你工作。

另一个例子是同一AppDomain内,使用相同的上下文指向不同数据库引擎的相同模型。不同的数据库引擎意味着数据库列中有不同的数据类型,而这又意味着不同的模型。让我们看看在这种情况下,如何处理模型缓存。 添加例7-4中所示的TargetMultipleProviders方法。此方法使用 同样的上下文访问SQL Server和SQL Server Compact Edition数据库。

小贴士:

完成本节需要安装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) {
    Database.SetInitializer(new
    DropCreateDatabaseIfModelChanges < BreakAwayContext > ());
    TargetMultipleProviders();
}
private static void TargetMultipleProviders() {
    var sqlString = @"Server=.\SQLEXPRESS;
        Database=DataAccess.BreakAwayContext;
        Trusted_Connection=true";
    using(var connection = new SqlConnection(sqlString)) {
        using(var context = new BreakAwayContext(connection)) {
            context.Destinations.Add(new Destination {
                Name = "Hawaii"
            });
            context.SaveChanges();
        }
    }
    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"
            });
            context.SaveChanges();
        }
    }
}

运行程序,在试图使用上下文实例指向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) {}

代码7-5显示了一个更新的TargetMultipleProviders方法展示如何利用这个构造器来指向不同的数据库引擎,为各自使用不同的模型:

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")).Compile();
    var ce_model = GetBuilder().Build(
    new DbProviderInfo("System.Data.SqlServerCe.4.0", "4.0")).Compile();

    var sql_cstr = @"Server=.\SQLEXPRESS;
    Database=DataAccess.BreakAwayContext;
    Trusted_Connection=true";

    using(var connection = new SqlConnection(sql_cstr)) {
        using(var context = new BreakAwayContext(connection, sql_model)) {
            context.Destinations.Add(new Destination {
                Name = "Hawaii"
            });
            context.SaveChanges();
        }
    }

    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"
            });
            context.SaveChanges();
        }
    }
}
private static DbModelBuilder GetBuilder() {
    var builder = new DbModelBuilder();
    builder.Entity < EdmMetadata > ().ToTable("EdmMetadata");
    builder.Entity < Activity > ();
    builder.Entity < Destination > ();
    builder.Entity < Hostel > ();
    builder.Entity < InternetSpecial > ();
    builder.Entity < Lodging > ();
    builder.Entity < Person > ();
    builder.Entity < PersonPhoto > ();
    builder.Entity < Reservation > ();
    builder.Entity < Resort > ();
    builder.Entity < Trip > ();
    builder.ComplexType < Address > ();
    builder.ComplexType < Measurement > ();
    builder.ComplexType < PersonalInfo > ();
    return builder;
}

我们来看看TargetMultipleProviders方法中的代码做了什么。 GetBuilder方法负责创建一个DbModelBuilder,使用builder注册所有的类。示例代码中的每个类都使用DbModelBuilder.Entity和DbModelBuilder.ComplexType方法进行了注册。这种方法将正常工作在已经使用Data Annoations配置过的类上。如果使用Fluent API,应该从OnModelCreating方法复制的代码来替换这部分代码。请注意,还需要包括EdmMetadata类,并将其映射到EdmMetadata表中,这使Code First可以到检测到模型和数据库是否同步。当由DbContext负责创建模型时,会对此类自动维护。

下一步要为两个需要指向的数据库引擎建立和编译模型。在这个例子中,不变的名称和数据库引擎令牌都提供给Build方法。作为一种替代方法,Builder的另一个重载,接受一个DbConnection获得数据库引擎信息。

在编译过的模型创建后,他们现在可以用来访问两个不同的数据库。请记住,数据库初始化只在每个AppDomain发生一次,所以只有第一个要使用的数据库将自动初始化。调用Database.Initialize是针对第二个数据库的,确保第二个数的也得到初始化。 最后,新的Destination被添加到两个不同的数据库中,使用的是相同的类和相同的配置设置定义了重复的模型。现在,我们使用过SQL Compact了,重新启用以往配置,以使Trip.Identifier键由数据库生成。

小贴士:记住构建和编译模型是高开销的操作。结果编译模型应被缓存并且在同一方面的所有context实例中被重用。

使用EdmMetadata表

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

image

使用EdmMetadata编码

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

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

此代码开始使用静态的EdmMetadata.TryGetModelHash方法,找到当前模型的哈希值。此方法会始终工作在Code First模型中,但如果您尝试与设计器创建的模型进行工作,将返回null。 EdmMetadata类作为你的模型的一部分,所以你可以用你的DbContext与它交互。 代码的第二部分创建了为EdmMetadata类创建了DbSet,然后索取一行数据以便可以读取哈希值。最后,还有一个DbContext.Database.bvgt65方法,它简单地检查模型和数据库是否匹配。这是包含在EF框架内部的数据库初始化器的方法。如果EdmMetadata表从数据库中已排除,为throwIfNoMetadata参数指定true将导致抛出一个异常。如果排除表,指定false将导致该方法返回false。您可以运行代码并看到一切当前的匹配。

在Code First中使用ObjectContext

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

小贴士:DbContext还是ObjectContext?

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

类似于基于DbContext的context,你可以开始于创建一个派生的context,只不过这一次我们是派生自ObjectContext,暴露ObjectSet的属性而不是DbSet的属性。在代码7-8就使用了ObjectContext,相比DbContext需要多写一点代码。必须暴露一个构造器接受一个EntityConnection.ObjcetSet也CreaeObjectSet方法进行初始化;这些DbContext都替您完成了。

添加一个新的BreakAwayObjectContext类到DataAccess项目。

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;
        }
    }
}

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.

修改Main方法使用一个新的UseObjectContext方法,如代码7-9所示: Example 7-9. Code updated to use BreakAwayObjectContext

static void Main() {
    UseObjectContext();
}
private static void UseObjectContext() {
    var builder = GetBuilder();
    var cstr = @"Server=.\SQLEXPRESS;
    Database=BreakAwayObjectContext;
    Trusted_Connection=true";
    using(var connection = new SqlConnection(cstr)) {
        var model = builder.Build(connection).Compile();
        using(var context = model.CreateObjectContext < BreakAwayObjectContext > (connection)) {
            if (!context.DatabaseExists()) {
                context.CreateDatabase();
            }
            context.Destinations.AddObject(
            new Destination {
                Name = "Ayers Rock"
            });
            context.SaveChanges();
        }
    }

以往我们都是先创建DbModelBuilder,使用GetBuilder方法添加上下文。然后使用模型构建器创建模型并进行编译。注意在创建模型时必须提供数据库连接或引擎信息,因为数据库引擎类型影响最终结果数据的类型。使用编译过的模型,也可使用CreateObjectContext方法来构建ObjectContext。这个方法依赖于你暴露的构造器,这个构造器接受一个单一的实体连接参数。ObjectContext不支持数据库初始化器,因此必须编写代码检查数据库是否存在并在不存在的时候去创建它。注意ObjectContext不支持EdmMetadata,因此无法检测模型和数据库是否兼容。

小结

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

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