第二章(5)自引用关系建模
问题:
数据库中有一张自引用的表,你想建立一个自引用的实体关系模型。
解决过程:
假设自引用的表结构如图2-5-1所示。
图2-5-1
建立自引用模型步骤如下:
- 在你的项目上右键,添加一个新的模型。选择添加新项,然后选择ADO.NET Entity Data Model。
- 选择从数据库中生成,点击下一步。
- 使用创建向导,选择一个已有数据库连接或者新建一个。
- 从“选择你的数据库对象”对话框中,选择PictureCategory表。保持最下面的两个复选框为选中状态。点击完成。
向导会帮你创建如图2-5-2所示的模型:
图2-5-2
上面的模型中包含了两个导航属性,分别是PictureCategory1和PictureCategory2.
原因:
数据库关系的特征有度、多重性和方向性。
度是参与到关系中的实体类型个数。一元和两元的关系是最常见的,三元和N元则是理论意义多语实际意义。
多重性是指关系中每个实体类型终端的个数。有1对多,1对1 这些。
方向性是指单向还是双向。
EF的实体数据模型支持一种特殊的数据库关系叫做关联(Assioation)。在关联类型的关系中,度是一元或者两元,多重性是0..1(0或1), 或者是*,方向性是双向的。
在这个例子中,度是一元的(只有PictureCategory这个实体型),多重性是0..1(0或1)和*,方向性也是双向的。
自引用类型通常是有父子关系,每个父亲都可以有很多孩子,但一个孩子只有一个父亲。因为父端是0..1(0或1),所以孩子可以没有父亲。这正代表了你的根节点,也就是只有孩子没有父亲的节点。
下面的代码递归枚举了从根节点往下的所有picture categories
static void RunExample() { using (var context = new EFRecipesEntities()) { var louvre = new PictureCategory { Name = "Louvre" }; var child = new PictureCategory { Name = "Egyptian Antiquites" }; louvre.Subcategories.Add(child); child = new PictureCategory { Name = "Sculptures" }; louvre.Subcategories.Add(child); child = new PictureCategory { Name = "Paintings" }; louvre.Subcategories.Add(child); var paris = new PictureCategory { Name = "Paris" }; paris.Subcategories.Add(louvre); var vacation = new PictureCategory { Name = "Summer Vacation" }; vacation.Subcategories.Add(paris); context.PictureCategories.AddObject(paris); context.SaveChanges(); } using (var context = new EFRecipesEntities()) { PictureCategory root = (from c in context.PictureCategories where c.ParentCategory == null select c).FirstOrDefault(); Print(root, 0); } } static void Print(PictureCategory cat, int level) { StringBuilder sb = new StringBuilder(); Console.WriteLine("{0}{1}", sb.Append(' ', level).ToString(), cat.Name); foreach (PictureCategory child in cat.Subcategories) { Print(child, level + 1); } }
-------------------------------------------
Summer Vacation
Paris
Louvre
Egyptian Antiquites
Sculptures
Paintings
-------------------------------------------
现在来看下代码。首先,我们创建并实例化我们的实体类型。通过把创建的子PictureCategory都加到louvre类下,我们就把这个类图个连接起来了。接着把louvre类填到paris类中。最后再把paris类添加到summer vacation类中。这样就把层次结构从底到上建起来了。
一旦我们调用SaveChanges()放大,就会把这些都插入到数据库。接着我们就可以进行查询操作来看看真正有哪些记录是插入成功的。
在读取部分,我们从根实体开始。这是一个没有父节点的实例,也就是我们summer vacation。有个这个根节点,我们可以调用自己写的Print()方法。Print()方法有两个参数,一个是PictureCategory的实例,另一个是level,或者叫做层次结构中的深度。因为summer vacation 是根节点,所以它的深度是0,我们Print方法里的应该写成Print(root, 0)。
在Print方法中,我们把category的名字写出来,并且在前面加了空格表示它的深度。Append()方法产生空格的个数来表示深度。在递归部分,我们通过递归调用Print()方法来遍历所有的子节点,同时确保每深入1层,level要加1.当所有的子节点都遍历完了,推出即可。结果如上面显示的一样。
在6—5中,我们会介绍另外一种方法来解决这个问题,在存储过程中使用Common Table Expression来遍历整个类图,然后返回一个二维的结果集。