Entity Framework技术系列之3:对象-关系映射

前言

Entity Framework技术是通过SSDL(Storage Schema Definition Language)、CSDL(Conceptual Schema Definition Language)和MSL(Mapping Concept and Storage Schema Language)三层映射语言来实现对象-关系映射的。下图展示了Entity Framework对象-关系映射技术架构:

 

1实体数据模型内容结构图

 

E-R模型

本文示例使用的E-R模型如下图所示:

 

2 E-R模型图

对应的DDL语句如下:

 1 --创建表
 2 CREATE TABLE [Role](
 3     [ID] [uniqueidentifier] NOT NULL,
 4     [Name] [nvarchar](40) NOT NULL,
 5     [ParentID] [uniqueidentifier] NULL)
 6 GO
 7 
 8 CREATE TABLE [User](
 9     [ID] [uniqueidentifier] NOT NULL,
10     [Account] [nvarchar](50) NOT NULL,
11     [Password] [varchar](50) NOT NULL)
12 GO
13 
14 CREATE TABLE [UserDetail](
15     [ID] [uniqueidentifier] NOT NULL,
16     [Name] [nvarchar](50) NOT NULL,
17     [Sex] [nvarchar](2) NULL,
18     [Birthday] [datetime] NULL)
19 GO
20 
21 CREATE TABLE [UserRole](
22     [UserID] [uniqueidentifier] NOT NULL,
23     [RoleID] [uniqueidentifier] NOT NULL)
24 GO
25 
26 --创建主键
27 ALTER TABLE [Role]
28 ADD CONSTRAINT [PK_Role]
29     PRIMARY KEY CLUSTERED ([ID] ASC);
30 GO
31 
32 ALTER TABLE [User]
33 ADD CONSTRAINT [PK_User]
34     PRIMARY KEY CLUSTERED ([ID] ASC);
35 GO
36 
37 ALTER TABLE [UserDetail]
38 ADD CONSTRAINT [PK_UserDetail]
39     PRIMARY KEY CLUSTERED ([ID] ASC);
40 GO
41 
42 ALTER TABLE [UserRole]
43 ADD CONSTRAINT [PK_UserRole]
44     PRIMARY KEY CLUSTERED ([UserID] ASC, RoleID ASC);
45 GO
46 
47 --创建外键
48 ALTER TABLE [Role] WITH CHECK ADD CONSTRAINT [FK_Role_Role] FOREIGN KEY([ParentID])
49 REFERENCES [Role] ([ID])
50 GO
51 
52 ALTER TABLE [UserDetail] WITH CHECK ADD CONSTRAINT [FK_UserDetail_User] FOREIGN KEY([ID])
53 REFERENCES [User] ([ID])
54 ON DELETE CASCADE
55 GO
56 
57 ALTER TABLE [UserRole] WITH CHECK ADD CONSTRAINT [FK_UserRole_User] FOREIGN KEY([UserID])
58 REFERENCES [User] ([ID])
59 ON DELETE CASCADE
60 GO
61 
62 ALTER TABLE [UserRole] WITH CHECK ADD CONSTRAINT [FK_UserRole_Role] FOREIGN KEY([RoleID])
63 REFERENCES [Role] ([ID])
64 ON DELETE CASCADE
65 GO
66 
67 --创建储存过程
68 --SQL Server自关联表无法通过外键设置级联删除,所以专门写一个触发器来完成该工作
69 CREATE TRIGGER TRG_Role_Delete
70    ON [Role]
71    INSTEAD OF DELETE
72 AS 
73 BEGIN
74     -- SET NOCOUNT ON added to prevent extra result sets from
75     -- interfering with SELECT statements.
76     SET NOCOUNT ON;
77 
78     DECLARE @ID uniqueidentifier;
79     SELECT @ID = ID FROM deleted;
80     
81     DELETE [Role] WHERE ParentID = @ID;
82     DELETE [Role] WHERE ID = @ID;
83 END
84 GO

使用SQL Server Management Studio工具创建名为“Membership”的数据库,并执行以上的DDL语句,建立本示例所需的数据库对象。

 

实体数据模型

在Visual Studio解决方案的Edm项目中,使用Database First开发模式生成实体数据模型,如下图所示:

 

3自动生成的实体数据模型

可以看到,自动生成的实体数据模型包括两个文件:Membership.edmx和Membership.Designer.cs。

Membership.edmx是使用XML语言进行描述的,在Visual Studio中,用XML(文本)编辑器打开Membership.edmx文件,可以看到该文件的内容结构如下图所示:

 

4 Membership.edmx文件内容结构图

上图清晰地展示了Membership.edmx文件包括四部分内容,分别是SSDL、CSDL、MSL和设计视图内容。设计视图内容部分主要提供实体数据模型可视化编辑工具所需的实体图像的尺寸、位置等信息,本文对此就不做进一步研究了。Membership.edmx是对象-关系映射文件,它是实体数据模型的关键内容。

Membership.Designer.cs是Visual Studio运用T4模板技术,根据CSDL内容生成的后台代码,其内容如下图所示:

 

5 Membership.Designer.cs文件内容结构图

Membership.Designer.cs文件包括上下文和实体两部分内容,对应图1中所示的ObjectContext和Entity。它是上层代码访问实体对象模型的入口,有了它才使实体对象模型变得可用和易用。

一、SSDL

SSDL(Store Schema Definition Language,存储架构定义语言)面向关系,定义数据库对象的架构信息,包括表结构、主外键信息,以及存储过程等。该部分内容根据不同的数据库,会有一定的差异。Membership.edmx文件中SSDL部分的内容如下:

 1 <edmx:StorageModels>
 2   <Schema Namespace="MembershipModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
 3     <EntityContainer Name="MembershipModelStoreContainer">
 4       <EntitySet Name="Role" EntityType="MembershipModel.Store.Role" store:Type="Tables" Schema="dbo" />
 5  6       <AssociationSet Name="FK_Role_Role" Association="MembershipModel.Store.FK_Role_Role">
 7         <End Role="Parent" EntitySet="Role" />
 8         <End Role="Children" EntitySet="Role" />
 9       </AssociationSet>
10 11     </EntityContainer>
12     <EntityType Name="Role">
13       <Key>
14         <PropertyRef Name="ID" />
15       </Key>
16       <Property Name="ID" Type="uniqueidentifier" Nullable="false" />
17       <Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="40" />
18       <Property Name="ParentID" Type="uniqueidentifier" />
19 </EntityType>
20 21     <Association Name="FK_Role_Role">
22       <End Role="Parent" Type="MembershipModel.Store.Role" Multiplicity="0..1" />
23       <End Role="Children" Type="MembershipModel.Store.Role" Multiplicity="*" />
24       <ReferentialConstraint>
25         <Principal Role="Parent">
26           <PropertyRef Name="ID" />
27         </Principal>
28         <Dependent Role="Children">
29           <PropertyRef Name="ParentID" />
30         </Dependent>
31       </ReferentialConstraint>
32 </Association>
33 34   </Schema>
35 </edmx:StorageModels>

由上面的代码示例可以看出,SSDL的内容主要包括两大部分:对象容器和对象详细定义。而这两部分均包括了对象(表)和关系(外键)两方面的内容。

二、CSDL

CSDL(Conceptual Schema Definition Language,概念架构定义语言)面向对象,定义概念层实体对象的架构信息,包括实体属性信息及实体间的引用关系。该部分不关心下层所使用的数据库类型,内容始终保持一致。Membership.edmx文件中CSDL部分的内容如下:

 1 <edmx:ConceptualModels>
 2   <Schema Namespace="MembershipModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
 3     <EntityContainer Name="Membership" annotation:LazyLoadingEnabled="true">
 4       <EntitySet Name="Roles" EntityType="MembershipModel.Role" />
 5  6       <AssociationSet Name="FK_Role_Role" Association="MembershipModel.FK_Role_Role">
 7         <End Role="Parent" EntitySet="Roles" />
 8         <End Role="Children" EntitySet="Roles" />
 9       </AssociationSet>
10 11     </EntityContainer>
12     <EntityType Name="Role">
13       <Key>
14         <PropertyRef Name="ID" />
15       </Key>
16       <Property Name="ID" Type="Guid" Nullable="false" />
17       <Property Name="Name" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />
18       <Property Name="ParentID" Type="Guid" />
19       <NavigationProperty Name="Parent" Relationship="MembershipModel.FK_Role_Role" FromRole="Children" ToRole="Parent" />
20       <NavigationProperty Name="Children" Relationship="MembershipModel.FK_Role_Role" FromRole="Parent" ToRole="Children" />
21 22     </EntityType>
23 24     <Association Name="FK_Role_Role">
25       <End Role="Parent" Type="MembershipModel.Role" Multiplicity="0..1" />
26       <End Role="Children" Type="MembershipModel.Role" Multiplicity="*" />
27       <ReferentialConstraint>
28         <Principal Role="Parent">
29           <PropertyRef Name="ID" />
30         </Principal>
31         <Dependent Role="Children">
32           <PropertyRef Name="ParentID" />
33         </Dependent>
34       </ReferentialConstraint>
35 </Association>
36 37   </Schema>
38 </edmx:ConceptualModels>

由上面的代码示例可以看出,CSDL的内容与SSDL其实很类似,主要包括实体容器和实体引用关系两部分。所不同的是,SSDL是针对具体某种数据库的E-R模型进行定义,而CSDL则是针对抽象的实体概念模型进行定义。

三、MSL

MSL(Mapping Specification Language,映射规范语言)实现SSDL和CSDL之间的映射,包括表与实体的映射、字段与属性的映射,以及外键与引用的映射等。Membership.edmx文件中MSL部分的内容如下:

 1 <edmx:Mappings>
 2   <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
 3     <EntityContainerMapping StorageEntityContainer="MembershipModelStoreContainer" CdmEntityContainer="Membership">
 4       <EntitySetMapping Name="Roles">
 5         <EntityTypeMapping TypeName="MembershipModel.Role">
 6           <MappingFragment StoreEntitySet="Role">
 7             <ScalarProperty Name="ID" ColumnName="ID" />
 8             <ScalarProperty Name="Name" ColumnName="Name" />
 9             <ScalarProperty Name="ParentID" ColumnName="ParentID" />
10           </MappingFragment>
11         </EntityTypeMapping>
12       </EntitySetMapping>
13 14       <AssociationSetMapping Name="UserRole" TypeName="MembershipModel.UserRole" StoreEntitySet="UserRole">
15         <EndProperty Name="Role">
16           <ScalarProperty Name="ID" ColumnName="RoleID" />
17         </EndProperty>
18         <EndProperty Name="User">
19           <ScalarProperty Name="ID" ColumnName="UserID" />
20         </EndProperty>
21       </AssociationSetMapping>
22 23     </EntityContainerMapping>
24   </Mapping>
25 </edmx:Mappings>

由上面的代码示例可以看出,MSL的内容主要是定义SSDL与CSDL的映射关系,其内容主要包括对象与实体、关系与引用的映射。

四、实体

实体是E-R关系在概念层的映射表现,Visual Studio自动生成的实体并不是POCO对象,而是继承自EntityObject对象的分部类,其内容大致如下:

  1 [EdmEntityTypeAttribute(NamespaceName="MembershipModel", Name="Role")]
  2 [Serializable()]
  3 [DataContractAttribute(IsReference=true)]
  4 public partial class Role : EntityObject
  5 {
  6     #region 工厂方法
  7     public static Role CreateRole(global::System.Guid id, global::System.String name)
  8     {
  9         Role role = new Role();
 10         role.ID = id;
 11         role.Name = name;
 12         return role;
 13     }
 14 #endregion
 15 
 16     #region 基元属性
 17     [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
 18     [DataMemberAttribute()]
 19     public global::System.Guid ID
 20     {
 21         get { return _ID; }
 22         set
 23         {
 24             if (_ID != value)
 25             {
 26                 OnIDChanging(value);
 27                 ReportPropertyChanging("ID");
 28                 _ID = StructuralObject.SetValidValue(value);
 29                 ReportPropertyChanged("ID");
 30                 OnIDChanged();
 31             }
 32         }
 33     }
 34     private global::System.Guid _ID;
 35     partial void OnIDChanging(global::System.Guid value);
 36 partial void OnIDChanged();
 37 
 38  39 
 40     [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
 41     [DataMemberAttribute()]
 42     public Nullable<global::System.Guid> ParentID
 43     {
 44         get { return _ParentID; }
 45         set
 46         {
 47             OnParentIDChanging(value);
 48             ReportPropertyChanging("ParentID");
 49             _ParentID = StructuralObject.SetValidValue(value);
 50             ReportPropertyChanged("ParentID");
 51             OnParentIDChanged();
 52         }
 53     }
 54     private Nullable<global::System.Guid> _ParentID;
 55     partial void OnParentIDChanging(Nullable<global::System.Guid> value);
 56     partial void OnParentIDChanged();
 57     #endregion
 58     
 59     #region 导航属性
 60     [XmlIgnoreAttribute()]
 61     [SoapIgnoreAttribute()]
 62     [DataMemberAttribute()]
 63     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_Role_Role", "Children")]
 64     public EntityCollection<Role> Children
 65     {
 66         get
 67         {
 68             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Role>("MembershipModel.FK_Role_Role", "Children");
 69         }
 70         set
 71         {
 72             if ((value != null))
 73             {
 74                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Role>("MembershipModel.FK_Role_Role", "Children", value);
 75             }
 76         }
 77     }
 78     
 79     [XmlIgnoreAttribute()]
 80     [SoapIgnoreAttribute()]
 81     [DataMemberAttribute()]
 82     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_Role_Role", "Parent")]
 83     public Role Parent
 84     {
 85         get
 86         {
 87             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent").Value;
 88         }
 89         set
 90         {
 91             ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent").Value = value;
 92         }
 93     }
 94     
 95     [BrowsableAttribute(false)]
 96     [DataMemberAttribute()]
 97     public EntityReference<Role> ParentReference
 98     {
 99         get
100         {
101             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent");
102         }
103         set
104         {
105             if ((value != null))
106             {
107                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent", value);
108             }
109         }
110     }
111 112     #endregion
113 }

由上面的代码可以看出,实体类主要包括工厂方法、基元属性和导航属性三部分内容。其中基元属性和导航属性与CSDL中定义的实体内容基本一致。此外,实体类还附加了很多特性、事件方法等,在本系列的第5篇延迟加载中,你将了解到所有这些附件的内容,几乎都是用于实现自动延迟加载功能的。

五、ObjectContext

ObjectContext是实体上下文环境,自定义上下文环境需要继承该类,并扩展自定义的实体集,以供上层代码通过它方便的使用实体。自定义ObjectContext类的内容如下:

 1 public partial class Membership : ObjectContext
 2 {
 3     #region 构造函数
 4     public Membership() : base("name=Membership", "Membership")
 5     {
 6         this.ContextOptions.LazyLoadingEnabled = true;
 7     }
 8     
 9     public Membership(string connectionString) : base(connectionString, "Membership")
10     {
11         this.ContextOptions.LazyLoadingEnabled = true;
12     }
13     
14     public Membership(EntityConnection connection) : base(connection, "Membership")
15     {
16         this.ContextOptions.LazyLoadingEnabled = true;
17     }
18     #endregion
19     
20     #region ObjectSet 属性
21     
22     public ObjectSet<Role> Roles
23     {
24         get
25         {
26             if ((_Roles == null))
27             {
28                 _Roles = base.CreateObjectSet<Role>("Roles");
29             }
30             return _Roles;
31         }
32     }
33     private ObjectSet<Role> _Roles;
34 35 #endregion
36 
37     #region AddTo 方法
38     /// </summary>
39     public void AddToRoles(Role role)
40     {
41         base.AddObject("Roles", role);
42     }
43 44     #endregion
45 }

由上面的代码可以看出,自定义实体上下文环境类的内容主要包括构造函数、实体集和AddTo方法。其中,构造函数提供使用默认连接配置名、指定连接配置名和指定连接三种方式构造实例;实体集定义该上下文环境可以访问的实体集合,这部分很重要,上层代码都是通过这部分提供的实体集来访问实体对象模型的;AddTo方法是向实体集中新增实体,这部分内容是已经废弃了的,我们可以直接使用实体集的添加方法替换。

另外,还有一部分内容是容易被忽略的,那就是关系源元数据,可以在Membership.Designer.cs文件的开始部分找到它,内容如下:

1 [assembly: EdmSchemaAttribute()]
2 #region EDM 关系源元数据
3 
4 [assembly: EdmRelationshipAttribute("MembershipModel", "FK_Role_Role", "Parent", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), "Children", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), true)]
5 [assembly: EdmRelationshipAttribute("MembershipModel", "FK_UserDetail_User", "User", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Apollo.Blog.EF.Chapter3.Edm.User), "UserDetail", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Apollo.Blog.EF.Chapter3.Edm.UserDetail), true)]
6 [assembly: EdmRelationshipAttribute("MembershipModel", "UserRole", "Role", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), "User", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.User))]
7 
8 #endregion

关系源元数据定义了实体对象间引用关系的元数据信息,它是实现自动延迟加载不可或缺的一部分,本系列的第5篇文章将会涉及到该部分内容。

实体数据模型的内容就这么多了。下面提供图1的形象化版本,以帮助你更直观的了解对象-关系映射的实现过程:

 

6对象-关系映射过程图

 

关系映射

众所周知,E-R关系主要包括一对一、一对多和多对多三种,在了解了对象-关系映射的实现过程后,下面就逐一对这三种关系与实体数据模型中的实体引用如何进行映射做进一步的研究。考虑篇幅过长会影响读者对重点内容的关注,后续代码中只摘出关键的、能说明问题的部分进行阐述。

一、一对一

本文示例所用的E-R模型(如图2)中,User和UserDetail对象是一对一的关系。接下来就来看看这两个对象及其关系是如何映射为对应的实体对象模型的。

首先来看SSDL定义,如下所示:

 1 <edmx:StorageModels>
 2   <Schema>
 3     <EntityContainer Name="MembershipModelStoreContainer">
 4  5       <AssociationSet Name="FK_UserDetail_User" Association="MembershipModel.Store.FK_UserDetail_User">
 6         <End Role="User" EntitySet="User" />
 7         <End Role="UserDetail" EntitySet="UserDetail" />
 8       </AssociationSet>
 9     </EntityContainer>
10     <EntityType Name="User">
11       <Key>
12         <PropertyRef Name="ID" />
13       </Key>
14       <Property Name="ID" Type="uniqueidentifier" Nullable="false" />
15 16     </EntityType>
17     <EntityType Name="UserDetail">
18       <Key>
19         <PropertyRef Name="ID" />
20       </Key>
21       <Property Name="ID" Type="uniqueidentifier" Nullable="false" />
22 23     </EntityType>
24     <Association Name="FK_UserDetail_User">
25       <End Role="User" Type="MembershipModel.Store.User" Multiplicity="1" />
26       <End Role="UserDetail" Type="MembershipModel.Store.UserDetail" Multiplicity="0..1" />
27       <ReferentialConstraint>
28         <Principal Role="User">
29           <PropertyRef Name="ID" />
30         </Principal>
31         <Dependent Role="UserDetail">
32           <PropertyRef Name="ID" />
33         </Dependent>
34       </ReferentialConstraint>
35     </Association>
36   </Schema>
37 </edmx:StorageModels>

SSDL的定义包含了User表、UserDetail表和FK_UserDetail_User外键的定义,与前文给定的数据库DDL的定义是一一对应的,不用做过多的解释了。

CSDL定义如下所示:

 1 <edmx:ConceptualModels>
 2   <Schema>
 3     <EntityContainer Name="Membership" annotation:LazyLoadingEnabled="true">
 4  5       <AssociationSet Name="FK_UserDetail_User" Association="MembershipModel.FK_UserDetail_User">
 6         <End Role="User" EntitySet="Users" />
 7         <End Role="UserDetail" EntitySet="UserDetails" />
 8       </AssociationSet>
 9     </EntityContainer>
10     <EntityType Name="User">
11 12       <NavigationProperty Name="UserDetail" Relationship="MembershipModel.FK_UserDetail_User" FromRole="User" ToRole="UserDetail" />
13     </EntityType>
14     <EntityType Name="UserDetail">
15 16       <NavigationProperty Name="User" Relationship="MembershipModel.FK_UserDetail_User" FromRole="UserDetail" ToRole="User" />
17     </EntityType>
18     <Association Name="FK_UserDetail_User">
19       <End Role="User" Type="MembershipModel.User" Multiplicity="1" />
20       <End Role="UserDetail" Type="MembershipModel.UserDetail" Multiplicity="0..1" />
21       <ReferentialConstraint>
22         <Principal Role="User">
23           <PropertyRef Name="ID" />
24         </Principal>
25         <Dependent Role="UserDetail">
26           <PropertyRef Name="ID" />
27         </Dependent>
28       </ReferentialConstraint>
29     </Association>
30   </Schema>
31 </edmx:ConceptualModels>

CSDL中也是对实体和引用进行了定义。另外,在实体定义中,加入了导航属性来实现实体引用。这种引用关系是双向的,即你可以在User实体中引用UserDetail实体,也可以在UserDetail实体中引用User实体。其实不只是一对一,一对多、多对多映射也是同样的情况,下文就不再赘述了。

MSL中不需要对一对一映射作任何定义,只是简单的定义表与实体、字段与属性映射关系即可。

关系源元数据如下所示:

1 [assembly: EdmRelationshipAttribute("MembershipModel", "FK_UserDetail_User", "User", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Apollo.Blog.EF.Chapter3.Edm.User), "UserDetail", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Apollo.Blog.EF.Chapter3.Edm.UserDetail), true)]

实体的内容如下所示:

 1 public partial class User : EntityObject
 2 {
 3     #region 导航属性
 4     [XmlIgnoreAttribute()]
 5     [SoapIgnoreAttribute()]
 6     [DataMemberAttribute()]
 7     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_UserDetail_User", "UserDetail")]
 8     public UserDetail UserDetail
 9     {
10         get
11         {
12             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<UserDetail>("MembershipModel.FK_UserDetail_User", "UserDetail").Value;
13         }
14         set
15         {
16             ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<UserDetail>("MembershipModel.FK_UserDetail_User", "UserDetail").Value = value;
17         }
18     }
19     [BrowsableAttribute(false)]
20     [DataMemberAttribute()]
21     public EntityReference<UserDetail> UserDetailReference
22     {
23         get
24         {
25             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<UserDetail>("MembershipModel.FK_UserDetail_User", "UserDetail");
26         }
27         set
28         {
29             if ((value != null))
30             {
31                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<UserDetail>("MembershipModel.FK_UserDetail_User", "UserDetail", value);
32             }
33         }
34     }
35     #endregion
36 }
37     
38  [EdmEntityTypeAttribute(NamespaceName="MembershipModel", Name="UserDetail")]
39 [Serializable()]
40 [DataContractAttribute(IsReference=true)]
41 public partial class UserDetail : EntityObject
42 {
43     #region 导航属性
44     [XmlIgnoreAttribute()]
45     [SoapIgnoreAttribute()]
46     [DataMemberAttribute()]
47     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_UserDetail_User", "User")]
48     public User User
49     {
50         get
51         {
52             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<User>("MembershipModel.FK_UserDetail_User", "User").Value;
53         }
54         set
55         {
56             ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<User>("MembershipModel.FK_UserDetail_User", "User").Value = value;
57         }
58     }
59     /// <summary>
60     /// 没有元数据文档可用。
61     /// </summary>
62     [BrowsableAttribute(false)]
63     [DataMemberAttribute()]
64     public EntityReference<User> UserReference
65     {
66         get
67         {
68             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<User>("MembershipModel.FK_UserDetail_User", "User");
69         }
70         set
71         {
72             if ((value != null))
73             {
74                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<User>("MembershipModel.FK_UserDetail_User", "User", value);
75             }
76         }
77     }
78 
79     #endregion
80 }

可以看到,实体类中除了包括CSDL中定义的导航属性外,还额外定义了一个*Reference属性,该属性是用于实现自动延迟加载的。

二、一对多

Role的Parent和Children是一对多的自关联关系(并不是所有一对多关系都是自关联的,只是本文所使用的E-R模型正好是如此的),下面直接给出实体数据模型各部分关键内容。

SSDL:

 1 <edmx:StorageModels>
 2   <Schema>
 3 <EntityContainer Name="MembershipModelStoreContainer">
 4  5       <AssociationSet Name="FK_Role_Role" Association="MembershipModel.Store.FK_Role_Role">
 6         <End Role="Parent" EntitySet="Role" />
 7         <End Role="Children" EntitySet="Role" />
 8       </AssociationSet>
 9     </EntityContainer>
10     <EntityType Name="Role">
11 12       <Property Name="ParentID" Type="uniqueidentifier" />
13     </EntityType>
14     <Association Name="FK_Role_Role">
15       <End Role="Parent" Type="MembershipModel.Store.Role" Multiplicity="0..1" />
16       <End Role="Children" Type="MembershipModel.Store.Role" Multiplicity="*" />
17       <ReferentialConstraint>
18         <Principal Role="Parent">
19           <PropertyRef Name="ID" />
20         </Principal>
21         <Dependent Role="Children">
22           <PropertyRef Name="ParentID" />
23         </Dependent>
24       </ReferentialConstraint>
25     </Association>
26   </Schema>
27 </edmx:StorageModels>

CSDL:

 1 <edmx:ConceptualModels>
 2   <Schema>
 3     <EntityContainer Name="Membership" annotation:LazyLoadingEnabled="true">
 4  5       <AssociationSet Name="FK_Role_Role" Association="MembershipModel.FK_Role_Role">
 6         <End Role="Parent" EntitySet="Roles" />
 7         <End Role="Children" EntitySet="Roles" />
 8       </AssociationSet>
 9     <EntityType Name="Role">
10 11       <Property Name="ParentID" Type="Guid" />
12       <NavigationProperty Name="Parent" Relationship="MembershipModel.FK_Role_Role" FromRole="Parent" ToRole="Children" />
13       <NavigationProperty Name="Children" Relationship="MembershipModel.FK_Role_Role" FromRole="Children" ToRole="Parent" />
14 15     </EntityType>
16     <Association Name="FK_Role_Role">
17       <End Role="Parent" Type="MembershipModel.Role" Multiplicity="0..1" />
18       <End Role="Children" Type="MembershipModel.Role" Multiplicity="*" />
19       <ReferentialConstraint>
20         <Principal Role="Parent">
21           <PropertyRef Name="ID" />
22         </Principal>
23         <Dependent Role="Children">
24           <PropertyRef Name="ParentID" />
25         </Dependent>
26       </ReferentialConstraint>
27     </Association>
28   </Schema>
29 </edmx:ConceptualModels>

MSL:MSL中不需要对一对一映射作任何定义,只是简单的定义表与实体、字段与属性映射关系即可。

关系源元数据:

1 [assembly: EdmRelationshipAttribute("MembershipModel", "FK_Role_Role", "Parent", System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), "Children", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), true)]

实体:

 1 public partial class Role : EntityObject
 2 {
 3     #region 基元属性
 4  5     [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
 6     [DataMemberAttribute()]
 7     public Nullable<global::System.Guid> ParentID
 8     {
 9         get
10         {
11             return _ParentID;
12         }
13         set
14         {
15             OnParentIDChanging(value);
16             ReportPropertyChanging("ParentID");
17             _ParentID = StructuralObject.SetValidValue(value);
18             ReportPropertyChanged("ParentID");
19             OnParentIDChanged();
20         }
21     }
22     private Nullable<global::System.Guid> _ParentID;
23     partial void OnParentIDChanging(Nullable<global::System.Guid> value);
24     partial void OnParentIDChanged();
25     #endregion
26     
27     #region 导航属性    
28     [XmlIgnoreAttribute()]
29     [SoapIgnoreAttribute()]
30     [DataMemberAttribute()]
31     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_Role_Role", "Children")]
32     public EntityCollection<Role> Parent
33     {
34         get
35         {
36             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Role>("MembershipModel.FK_Role_Role", "Children");
37         }
38         set
39         {
40             if ((value != null))
41             {
42                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Role>("MembershipModel.FK_Role_Role", "Children", value);
43             }
44         }
45     }
46     
47     /// <summary>
48     /// 没有元数据文档可用。
49     /// </summary>
50     [XmlIgnoreAttribute()]
51     [SoapIgnoreAttribute()]
52     [DataMemberAttribute()]
53     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "FK_Role_Role", "Parent")]
54     public Role Children
55     {
56         get
57         {
58             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent").Value;
59         }
60         set
61         {
62             ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent").Value = value;
63         }
64     }
65 
66     [BrowsableAttribute(false)]
67     [DataMemberAttribute()]
68     public EntityReference<Role> ChildrenReference
69     {
70         get
71         {
72             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent");
73         }
74         set
75         {
76             if ((value != null))
77             {
78                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<Role>("MembershipModel.FK_Role_Role", "Parent", value);
79             }
80         }
81     }
82     #endregion
83 }

三、多对多

User和Role是多对多的关系,下面直接给出实体数据模型各部分关键内容。

SSDL:

 1 <edmx:StorageModels>
 2   <Schema>
 3     <EntityContainer Name="MembershipModelStoreContainer">
 4  5       <EntitySet Name="UserRole" EntityType="MembershipModel.Store.UserRole" store:Type="Tables" Schema="dbo" />
 6  7       <AssociationSet Name="FK_UserRole_Role" Association="MembershipModel.Store.FK_UserRole_Role">
 8         <End Role="Role" EntitySet="Role" />
 9         <End Role="UserRole" EntitySet="UserRole" />
10       </AssociationSet>
11       <AssociationSet Name="FK_UserRole_User" Association="MembershipModel.Store.FK_UserRole_User">
12         <End Role="User" EntitySet="User" />
13         <End Role="UserRole" EntitySet="UserRole" />
14       </AssociationSet>
15     </EntityContainer>
16 17     <EntityType Name="UserRole">
18       <Key>
19         <PropertyRef Name="UserID" />
20         <PropertyRef Name="RoleID" />
21       </Key>
22       <Property Name="UserID" Type="uniqueidentifier" Nullable="false" />
23       <Property Name="RoleID" Type="uniqueidentifier" Nullable="false" />
24     </EntityType>
25 26     <Association Name="FK_UserRole_Role">
27       <End Role="Role" Type="MembershipModel.Store.Role" Multiplicity="1" />
28       <End Role="UserRole" Type="MembershipModel.Store.UserRole" Multiplicity="*" />
29       <ReferentialConstraint>
30         <Principal Role="Role">
31           <PropertyRef Name="ID" />
32         </Principal>
33         <Dependent Role="UserRole">
34           <PropertyRef Name="RoleID" />
35         </Dependent>
36       </ReferentialConstraint>
37     </Association>
38     <Association Name="FK_UserRole_User">
39       <End Role="User" Type="MembershipModel.Store.User" Multiplicity="1" />
40       <End Role="UserRole" Type="MembershipModel.Store.UserRole" Multiplicity="*" />
41       <ReferentialConstraint>
42         <Principal Role="User">
43           <PropertyRef Name="ID" />
44         </Principal>
45         <Dependent Role="UserRole">
46           <PropertyRef Name="UserID" />
47         </Dependent>
48       </ReferentialConstraint>
49     </Association>
50   </Schema>
51 </edmx:StorageModels>

CSDL:

 1 <edmx:ConceptualModels>
 2   <Schema>
 3     <EntityContainer Name="Membership" annotation:LazyLoadingEnabled="true">
 4       <EntitySet Name="Roles" EntityType="MembershipModel.Role" />
 5       <EntitySet Name="Users" EntityType="MembershipModel.User" />
 6  7       <AssociationSet Name="UserRole" Association="MembershipModel.UserRole">
 8         <End Role="Role" EntitySet="Roles" />
 9         <End Role="User" EntitySet="Users" />
10       </AssociationSet>
11     </EntityContainer>
12     <EntityType Name="Role">
13 14       <NavigationProperty Name="Users" Relationship="MembershipModel.UserRole" FromRole="Role" ToRole="User" />
15     </EntityType>
16     <EntityType Name="User">
17 18       <NavigationProperty Name="Roles" Relationship="MembershipModel.UserRole" FromRole="User" ToRole="Role" />
19     </EntityType>
20 21     <Association Name="UserRole">
22       <End Role="Role" Type="MembershipModel.Role" Multiplicity="*" />
23       <End Role="User" Type="MembershipModel.User" Multiplicity="*" />
24     </Association>
25   </Schema>
26 </edmx:ConceptualModels>

MSL部分需要专门为多对多关系进行映射定义,如下所示:

 1 <edmx:Mappings>
 2   <Mapping>
 3     <EntityContainerMapping StorageEntityContainer="MembershipModelStoreContainer" CdmEntityContainer="Membership">
 4  5       <AssociationSetMapping Name="UserRole" TypeName="MembershipModel.UserRole" StoreEntitySet="UserRole">
 6         <EndProperty Name="Role">
 7           <ScalarProperty Name="ID" ColumnName="RoleID" />
 8         </EndProperty>
 9         <EndProperty Name="User">
10           <ScalarProperty Name="ID" ColumnName="UserID" />
11         </EndProperty>
12       </AssociationSetMapping>
13     </EntityContainerMapping>
14   </Mapping>
15 </edmx:Mappings>

关系源元数据如下所示:

1 [assembly: EdmRelationshipAttribute("MembershipModel", "UserRole", "Role", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.Role), "User", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Apollo.Blog.EF.Chapter3.Edm.User))]

实体:

 1 public partial class Role : EntityObject
 2 {
 3  4 #region 导航属性
 5  6     [XmlIgnoreAttribute()]
 7     [SoapIgnoreAttribute()]
 8     [DataMemberAttribute()]
 9     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "UserRole", "User")]
10     public EntityCollection<User> Users
11     {
12         get
13         {
14             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<User>("MembershipModel.UserRole", "User");
15         }
16         set
17         {
18             if ((value != null))
19             {
20                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<User>("MembershipModel.UserRole", "User", value);
21             }
22         }
23     }
24 
25     #endregion
26 }
27 
28 public partial class User : EntityObject
29 {
30 31     #region 导航属性
32 33     [XmlIgnoreAttribute()]
34     [SoapIgnoreAttribute()]
35     [DataMemberAttribute()]
36     [EdmRelationshipNavigationPropertyAttribute("MembershipModel", "UserRole", "Role")]
37     public EntityCollection<Role> Roles
38     {
39         get
40         {
41             return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<Role>("MembershipModel.UserRole", "Role");
42         }
43         set
44         {
45             if ((value != null))
46             {
47                 ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Role>("MembershipModel.UserRole", "Role", value);
48             }
49         }
50     }
51 
52     #endregion
53 }

最后,给出该实体数据模型的简单测试代码,从中可以看出该模型使用起来是很方便和谐的:

 1 using (var db = new Membership())
 2 {
 3     var role = new Role();
 4     role.ID = Guid.NewGuid();
 5     role.Name = "管理员";
 6 
 7     var role1 = new Role();
 8     role1.ID = Guid.NewGuid();
 9     role1.Name = "业务管理员";
10     role1.Parent = role;
11 
12     var user = new User();
13     user.ID = Guid.NewGuid();
14     user.Account = "Apollo";
15     user.Password = "123456";
16     user.UserDetail = new UserDetail() { ID = user.ID, Name = "Yilin", Sex = "", Birthday = DateTime.Now };
17     user.Roles.Add(role1);
18     db.Users.AddObject(user);
19 
20     db.SaveChanges();
21 }

 

总结

本文首先给出了实体数据模型内容结构图,宏观分析了实体数据模型主要由SSDL、CSDL、MSL、实体和ObjectContext几部分组成;紧接着通过一个实例逐一分析了这几部分的详细内容;最后详细讲解了如何使用Entity Framework技术将一对一、一对多和多对多三种E-R关系映射为实体数据模型。

下一篇文章将在本文的基础上,探讨如何驾驭实体数据模型的各组成内容,将Entity Framework技术灵活应用到项目开发中。

 

附件下载:示例源码+本文PDF版本

posted @ 2013-02-26 22:29 Apollo.NET 阅读(...) 评论(...) 编辑 收藏