到目前为止,在设计器中你只是看到了模型的概念部分,它的EDMX还有更重要的部分:存储模型和映射。
注:事实上,EDMX文件有四部分,但是它的第四部分是包含了为发对象定位的设计器指令。我在这里的讨论将忽略那一部分。
这些元数据库的附加部分允许你使用EF API在概念模型和真实数据存储中转换。存储模型代表着你选择的数据库对象的构架,映射则描述了如何将概念模型的实体和属性放置到存储模型中描述的表和列中。(参见图2-7)。
当有了真实数据存储了为什么还需要使用代表着数据存储的存储层呢?有着很多使用这个模型的原因。最重要的原因就是它提供了对于数据库的松耦合;并不是数据库中的每个对象需要出现在模型中,这个你将在第6章中学习到。自定义存储层去适合模型都是可能的。
虽然实体模型在设计时被包含在单个文件中,当工程编译后它会为这些部分分别创建一个文件,共三个分离的文件。概念层保存到扩展名为.csdl的文件中,它表示概念模式定义语言。存储层保存到扩展名为.ssdl(表示存储模式定义语言)的文件,而映射层则保存到扩展名为.msl(映射规范语言)的文件中。这些文件在运行时使用,这也是为什么在模型中它们被包含在名为edmx:Runtime的模块中。
注:默认情况下,你将永远不会见到这些实现的文件,因为它们在工程编译时被嵌在工程的assembly中。这对于很多场景都有利,尽管修改模型的元数据Artiface Processing属性去读取"Copy to Output Directory"是可能的。
模式的其它昵称你将看到在文档,文章,培训,甚至本书中模型的三个部分有各种各样的描述。下面列出了它们各种各样的昵称: 概念模式定义语言(CSDL): 概念层 概念模式 概念模型 C-Side 存储模式定义语言(SSDL): 存储层 存储模式 存储模型 存储元数据 存储元数据模式 S-Side 映射规格语言(MSL): 映射层 映射规格C-S映射(参考概念到存储) |
每个部分由它自身深入生存在.NET框架文件中的XML构架定义文件控制。一个构架文件定义了CSDL的结构应该是怎么样,另一个定义MSL,剩下的定义SSDL。VS智能感知使用这些文件在你直接操作XML文件时帮助你,指出错误并呈现选项值。编译错误同样会显示,如果与他们的模式规则不一致。
构架的构架 如果你非常好奇,你可以看一眼在模型中为CSDL,SSDL和MSL定义了规则的构架文件。VS2010中的构架文件位于C:\Program Files\Microsoft Visual Studio 10.0\xml\Schemas。如果你运行在64位操作系统中,使用Program Files(86)代替。三个文件看起来像下面这样:
如果你在VS中打开它们,你将看到是格式化的便于操纵的XML文件。 |
实体数据模型设计器也在模型浏览器中提供了元数据的另一种视图。你可以在模型设计界面的上下文菜单中进入模型浏览器。
图2-8展示了模型浏览器,附带一些展开项。模型浏览器让你对CSDL和SSDL的项有个概览。在这里你可以进入不同实体和属性的属性窗口。你也可以通过右键单击,选择在设计器显示导航到特定的实体或属性。随着模型越来越复杂,模型浏览器是查看有组织的元数据视图的一个方便的办法。
现在,是时候看一下EDM了。在设计器中只有模型的一部分是可见的,这意味着通过查看它原始的格式你能了解到更多。在设计器模型拥有的副本的位置,你将看到这两个视图。
默认情况下,文件会在设计器中打开;因此,你需要使用另一个方法打开原始文件。在解决方案浏览器中,右键单击Model1.edmx文件。在打开的菜单中,选择使用工具打开,然后选择XML编辑器,单击OK。
VS不能同时在设计器和XML中显示XML,所以你将看到消息提示,问是否选择关闭模型的设计器视图。单击YES。
注:对于那些有着XML综合症的朋友,一开始这看起来可能有些令人胆怯。不用害怕,只要跟着往下走。我们不会很深入,但是如果你非常感兴趣,你将在附录C中找到更多关于原生元数据的细节。
在本书的后续章节中,你将直接同XML文件打交道,做一些设计器不支持模型自定义工作。另外,你将写一些直接与原生模型交互的代码。当执行这些任务时,你将感受到在接下来的几页和附录C中与XML文件打交道带来的好处。
EDMX文件由两个主要的模块:运行时信息和设计器信息。运行时模块由三个另外的模块组成:它们分别是存储模块,概念模块和映射。设计器模块指定不同的模型元素在设计器可视化图形中应该放置在什么位置。
折叠模型的全部主要模块。你可以通过在XML文件中右键,选择大纲,然后折叠所有到大纲快速完成。现在你将看到主要的代码,edmx:Edmx。你可以展开它,直接到看与2-9一样的视图。
现在你看到模型的主要模块了。设计元素是告诉设计器如何放置实体的元数据。在后面的时间里随意的浏览它。模型最主要的模块是运行时ConceptualModels,StorageModels和Mappings。
让我们开始细细品味CSDL,EDM的概念模式。在XML文件中,单击+号展开ConceptualModels模块直到你看到Schema和EntityContainer,参见图2-10。
注:有时XML的格式化受到影响,某一部分可能丢失它的回车,这样导致非常长的代码行难于格式化。为了修复这一点,选中行,然后在VS的菜单中,选择编辑-高级-格式化选中。这将使XML格式化更加平滑。
在这儿你看到了EntityContainer,EntitySets和各种EntityTypes,它们是我们在之前的设计器中看到定义在元数据中并包含不同的EntitySets。
现在我们来看看这个结构化的XML,更多地来了解实体数据模型中的不同元素。
模式里面是被称为SampleEntities(默认情况下)的EntityCOntainer。像命令空间一样,这是EntityContainer默认命名的方式,使用数据库名称并在后面加上单词Entities。当你在设计器中打开模型时,你可以在模型的属性窗口中查看并修改这个名称。
EntityContainer是EntitySets和AssociationsSets的容器。在图2-5中你可以从属性窗口中识别Contacts的EntitySet。AssociationSets指实体间的关联。我们会在讨论完Association元素之后回到AssociationSets上来。
如图2-11所示, EntityContainer是用于查询模型重要的入口。它暴露了EntitySets,它是你写查询的依靠的EntitySets。EntitySets反过来允许你访问它们的实体。
EntityContainer有一个属性:annotation:LazyLoadingEnabled="true"。注解只在EDMX文件中存在,是基于模型生成代码的指示,与模型本身没有任何关系。这个设置在模型的属性窗口也能看到。
EntitySet是一种实体的容器。它的两个属性是Name和EntityType。EntityType使用它的强类型名称定义了集合中包含实体的类型。实体的强类型名称包含在模型的命名空间中,如下代码片段:
<EntitySet Name=Addresses"
EntityType="SampleModel.Address" />
<EntitySet Name="Contacts"
EntityType="SampleModel.Contact" />
在对模型进行查询时是通过EntitySet访问单个实体。在下一章开始查询学习时,你将看到你使用什么代码翻译"在Addresses EntitySet中查找某些实体"。模型命令你的查询返回Address实体类型。
注:正如你将在本书后学习到的,实体数据模型允许你类型继承。因此,你的模型可能拥有Contact实体和Customer实体,customer是contact的一种。在这种情况下,Contacts EntitySet将作为Contact实体和Customer实体的包装器。
实体类型是模型中的数据类型。你已经看到了Contact实体类型和Address实体类型。
在XML模式中,展开Address实体类型,你将看到示例2-1中所示的,仔细看看它。它包括了主键元素和一些属性元素列表。
示例 2-1. The Address entity's XML
<EntityType Name="Address">
<Key>
<PropertyRef Name="addressID" />
</Key>
<Property Name="addressID" Type="Int32" Nullable="false"
annotation:StoreGeneratedPattern="Identity" />
<Property Name="Street1" Type="String" MaxLength="50"
Unicode="true" FixedLength="true" />
<Property Name="Street2" Type="String" MaxLength="50"
Unicode="true" FixedLength="true" />
<Property Name="City" Type="String" MaxLength="50"
Unicode="true" FixedLength="true" />
<Property Name="StateProvince" Type="String" MaxLength="50"
Unicode="true" FixedLength="true" />
<Property Name="CountryRegion" Type="String" MaxLength="50"
Unicode="true" FixedLength="true" />
<Property Name="PostalCode" Type="String" MaxLength="20"
Unicode="true" FixedLength="true" />
<Property Name="AddressType" Type="String" Nullable="false"
MaxLength="10" Unicode="true" FixedLength="true" />
<Property Name="ContactID" Type="Int32" Nullable="false" />
<Property Name="ModifiedDate" Type="DateTime" Nullable="false" />
<NavigationProperty Name="Contact"
Relationship="SampleModel.FK_Address_Contact"
FromRole="Address" ToRole="Contact" />
</EntityType>
主键元素定义了模型的主键由哪些属性组成。在设计器和运行时,你将看到它表示为EntityKey。实体的主键在实体的生命周期中扮演了重要的角色,它使得你的应用程序保持对实体的追踪,执行数据库的更新和刷新,等等。你将在第10章学习到这些。在设计器中,你能在实体的属性中指定主键。
Address实体的主键只用了单个属性,addressID。主键可以使用多个属性的组合。它们被称为联合实体主键,与数据库中的联合主键类似。你将在第10章学习更多关于联合主键的内容。
属性元素不光有名称,它们也有由他们的数据类型定义的其它方面,这些更深入地描述了属性元素。
定义这些属性的数据类型被称为简单类型。他们是EF对象模型中的基本类型,与.NET框架的数据元素类似。然而,EF的基本类型仅仅用于定义实体的属性。他们没有它们自己的属性。他们真的非常简单。
如图2-12所示,你可以在属性窗口查看及编辑这些信息的大部分。
你看到在属性窗口中显示的属性有些没有在XML中显示。
注:不能同时打开XML和设计器。想要回到设计器,先关闭模型的XML视图然后在EDMX文件上双击。
拥有默认值的属性不会显示在XML文件中。这是Address.Street1属性的一种情况,包括ConcurrencyModel, Default Value, Getter和Setter。EntityKey属性不是Street1的一个方面,但是它被用来创建EntityKey元素,这个我们前面描述过。如果你查看AddressID的属性,你将看到它的EntityKey属性值为True。
注:Getter和Setter属性定义了从模型实体生成的类的每一个属性的可访问性。默认情况下,所有的属性都是公开的,允许任何人读取和写入。修改Getter和Setter的值会影响属性的申明。第23章深入讨论了并发性,在那里你将学习到ConcurrencyModel属性的内容。
实体的导航属性与实体间的连线代表的关联紧紧相连,正如你在前面的图2-3看到的。我们会在后面讨论关联时深入讨论导航属性的话题。
关联定义了实体类型间的关系。然而,关联并没有完全定义关系。它定义了端点(例如,关系涉及到的实体)和它们的重数。
在示例的模型中,只有一个关联。它是Contact和Address实体类型之间的,告诉我们它们两个之间有一种关系。当向导第一次创建模型时,关联的名称采用数据库定义好的关系。像模型中的其它任何元素一样,如果你倾向于更可读的名称或者你有需要遵循的命令规则,你可以修改关联的名称。
让我们首先在设计视图中看一下关联的属性。如果你是一直跟着操作的,关闭XML,然后在设计器中打开模型,单击关联。图2-13展示了Contact和Address之间关联的属性窗口。
关联列出两端。端点1是Contact实体类型。它分配了一个角色叫做Contact,它扮演端点1的名称,这样模型中其它任何位置的元素能指向它。另外,端点1的属性告诉我们关联中这一端更多的信息。重数指出在Contact和Address的关系中仅出现一个Contact。导航属性在Contact类型中展示了Address属性,它带我们到关联的另一端点。OnDelete属性,可选值有Cascade和None,告诉我们当Contact实体被删除时,内在中其它任何相关联的实体也会被删除。
端点2是Address实体类型,在这个关系中会有多个地址。在读过关联之后,你能看出在Contact和Address之间是一对多的关系。Contact可能有家庭地址,工作地址,甚至其它的地址。然而,一个地址将只能与单个Contact关联。在真实世界里,一个地址可能会与多个人员关系,例如,家庭成员或室友或同一公司的员工。那将涉及到多对多联系,我们会在第8章讲到。
就实体来说,关联定义了包含这种关联的AssociationSet的名称。默认情况下,它与关联的名称相匹配。你也可以使这个命名使用复数,但是这样做没有拥有复数EntitySet名称一样关键因为在代码中不需要与AssociationSets交互。
注:在附录C中学习更多关于AssociationSet的内容。
最后,来为Referential Constraint属性加点注解。在实体内包含外键的模型中,例如图2-14中所示的Address的ContactID属性,引用约束是关键的。它定义了相关的实体间的依赖关系。
每个Address实体必须指向一个Contact。当你存储数据到数据库时引用约束会检查这些。
注:为了向后兼容版本1的模型,当你在两个主键间有关系时你还是可以使用关联映射定义约束的。
在创建实体数据模型时包含外键属性是默认的方式。你可以像版本那样创建实体不包含外键的模型。那样的话,引用约束将不会被用到,而Contact和Address间的依赖关系则由关联映射定义。在第19章你将学到更多关于这种交替使用的内容。在这种模型判刑中的关联都被称为独立关联,而模型中使用外键的则被称为外键关联。
我们将本章的后面讨论关联的更多细节,在第19章中甚至有更仔细的,它专注于关系和关联上。
最后,我们来看一下Address和Contact实体类型中的导航属性。到目前为止我解释过关联,导航属性应该更容易理解。
虽然实体拥有不止一个导航属性是容易的,在这个特别的模型中我们的每个实体类型中仅只有一个。图2-15展示了Contact导航属性Address实体的属性窗口。
当你在代码中同Address工作时,Contact的导航属性仅以另一个属性的姿态出现。虽然其它属性都被称为标量属性,意思是他们都是值,导航属性描述了如何导航到相关的实体上。
导航最重要的属性是关联。它述说着与模型相关联的导航属性包含着关于如何导航到相关联的实体(在这里是Contact.Addresses实体)上的信息。
正如我之前解释过的,关联定义了Address和Contact实体类型间的关系。FromRole和ToRole在EF关注时告诉它这些,它需要从名称为Address的端点导航到名称为Contact端点。这允许你在代码中从Address实体导航到它关联的Contact实体。
就实体而主,设计器展示了用于代码生成的一些属性:Getter, Setter和Documentation。另外,Multiplicity 与关联中相同端的重数连接。你可以在导航属性的属性或关联属性中修改它。Contact的重数是一,告诉你当你导航到Address.Contact时,你将获得一个Contact实例。返回单个对象的导航属性被称为导航引用。返回的类型是位于设计器属性窗口的只读的属性,为了使你更好的理解导航。
当着眼于XML文件中相同的导航属性时,你将不会看到最后两个属性,因为Getter,Setter和Documentation属性都使用了默认值,他们像下面的代码片段一样不会被列出来:
<NavigationProperty Name="Contact"
RelationShip="SmapleModel.FK_Address_Contact"
FromRole="Address"
ToROle=Contact" />
Address实体的Contact属性返回contact的单个实例。那Contact实体的Addresses属性又是如何呢?图2-16展示了Addresses属性。
当从Contact导航到Addresses时,在关系FK_Address_Contact中定义的Addresses端点拥有多的重数。因此,EF期待在Addresses属性中是一个集合。在设计器中,Addresses的返回属性是Address类型的集合。
这种类型的导航属性被称为导航集合。
在代码中,Contact.Addresses属性返回Address实体的集合,即使在这个集合中只有一个Address。如果某Person没有Addresses,集合将为空。
Addresses导航属性中暴露的集合不是来自System.Collections命名空间下的集合,而是EntityCollection。它是EF中彻底唯一的类。因此,虽然说"一个addresses的集合"更简单了,在你与EntityCollection和实现自System.Collections.Icollection的类型一起工作时还是要更加注意,它们不是可互换的。
记住在这个简单的模型中,概念层是没有被自定义的。它是数据库模式的写照,也是开始学习EDM最好的出发点。在本书的后面,你将学习到自定义模型,并开始领略EDM真正的力量。