不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多。不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题。
2-2 从已存在的数据库创建模型
问题
有一个存在的数据库,它拥有表、也许还有视图、外键。你想通过它来创建一个模型。
解决方案
让我们设想,你拥有一个描述诗人(Poet)以及他们的诗(Poem),还有他们之间关系的数据库。如图2-7所示。
图2-7 一个关于诗人及他们的诗的简单数据库
从上图可以看出,一个诗人可能是一首或多首诗的作者,每首诗可以按其韵律来分类,韵律是诗句的基本模式。上图未显示数据库中将表连接在一起的视图,它让我们更容易的枚举诗人,诗和韵律。
按下列步骤,将表、视图以及关系导入到模型:
1、右键你的项目,选择Add(增加) ➤New Item(新建项)。
2、选择Visual C#条目下的Data模板下的ADO.NET Entity Data Model(ADO.NET实体数据模型)。
3、选择Generate from database 从一个已存在的数据库创建模型,点击Next(下一步)。
4、可以选择一个已存在的数据库连接,也可以选择新建一个数据库连接,如果你选择新建,你将要选择数据库服务器、认证方式(Windwos or SQL Server)以及一个数据库。你一旦选择好,便可以点击Test Connection(测试连接)测试连接是否可用。测试好后,点击Next(下一步)。
弹出的对话框中显示了数据库所有的表、视图以及存储过程。选择你希望包含在模型中的项。我们选择所有的表(Meter,Poem,Poet),视图(vwLibrary),然后勾选上确定所生成对象名称的单复数形式、在模型中包含外键列复选框。我们将会进一步对此时行讨论。图2-8展示了我们所做的选择。
图2-8 选择表、视图包含进模型,勾选上确定所生成对象名称的单复数形式、在模型中包含外键列复选框
当点击Finish(完成),向导会生成一个包含三张表和一个视图的模型。向导从数据库读取外键约束,并推导出Poet和Poem(s)之间的一对多关系,还有Meter和Poem(s)之间的一对多关系。
图2-9 概念模型
图2-9展示了一个包含表Poet,Poem以及Meter、视图vwLibrary的模型。
现在,你拥有了一个可以在代码中使用的模型。请注意, vwLibrary实体是基于数据中的视图vwLibrary的。在绝大多数据库中,视图是只读的,插入、删除、更新不被支持。在实体框架中也同样如此,实体框架把视图视为只读。你可以通过映射存储过程来解决基于视图的实体的创建、更新和删除动作。我们将在第六章对此进行演示。
原理
让我们一起来看导入向导为我们创建的模型。请注意,实体中已经包含了标量属性和导航属性。标量属性被映射到数据库表中列,导航属性却来至数据库中的表间关系。
在数据库关系图中,一个poem拥有一个meter和一个poet(作者)。这符合Meter和Poet中的导航属性。如果我有一个Poem实体的实例,导航属性Poet将引用一个Poet实体的实例,导航属性Meter将引用一个Meter实体的实例。
一个Poet可能是多首诗的作者,它其中的导航属性Poems包含一个Poem实体的实例集合。这个集合可能是空的,这代表该诗人还未创建任何一首诗。对于Meter实体,其中的导航属性Poems同样也是一个集合。该导航属性包含属于指定Meter的Poem实体实例的集合。SQL Server不支持在视图上创建关系,这在模型中反映为一个没有导向属性的vwLibrary实体。
导入向导在包含集合的导航属性的名称单复数形式上表示得相当的聪明。当你右键实体去查看他的Properties(属性),你会看到每个实体的实体集的名称同样为复数形式。例如Poem实体的实体集名称为Poems. 这种自动添加复数利益于,勾选上确定所生成对象名称的单复数形式复选框。
勾选在模型中包含外键列复选框选项,使外键也包含在了模型中。虽然看上去,在拥有导航属性的同时好像没有必要再拥有外键属性。对此,我们会在接下来的技巧中演示拥有一个可直接访问外键属性的好处。
代码清单2-2,演示了如何在模型中创建Poet,Poem以及Meter实体的实例并将他们保存到数据为。同时也演示了,如何通过查询模型从数据库中获取到poets和poems。
代码清单2-2.在模型中插入和查询
1 using (var context = new EF6RecipesContext()) { 2 var poet = new Poet { FirstName = "John", LastName = "Milton" }; 3 var poem = new Poem { Title = "Paradise Lost" }; 4 var meter = new Meter { MeterName = "Iambic Pentameter" }; 5 poem.Meter = meter; 6 poem.Poet = poet; 7 context.Poems.Add(poem); 8 poem = new Poem { Title = "Paradise Regained" }; 9 poem.Meter = meter; 10 poem.Poet = poet; 11 context.Poems.Add(poem); 12 poet = new Poet { FirstName = "Lewis", LastName = "Carroll" }; 13 poem = new Poem { Title = "The Hunting of the Shark" }; 14 meter = new Meter { MeterName = "Anapestic Tetrameter" }; 15 poem.Meter = meter; 16 poem.Poet = poet; 17 context.Poems.Add(poem); 18 poet = new Poet { FirstName = "Lord", LastName = "Byron" }; 19 poem = new Poem { Title = "Don Juan" }; 20 poem.Meter = meter; 21 poem.Poet = poet; 22 context.Poems.Add(poem); 23 context.SaveChanges(); 24 } 25 using (var context = new EF6RecipesContext()) { 26 var poets = context.Poets; 27 foreach (var poet in poets) { 28 Console.WriteLine("{0} {1}", poet.FirstName, poet.LastName); 29 foreach (var poem in poet.Poems) { 30 Console.WriteLine(" {0} ({1})", poem.Title, poem.Meter.MeterName); 31 } 32 } 33 } 34 35 // 使用视图 vwLibrary 36 using (var context = new EF6RecipesContext()) { 37 var items = context.vwLibraries; 38 foreach (var item in items) { 39 Console.WriteLine("{0} {1}", item.FirstName, item.LastName); 40 Console.WriteLine(" {0} ({1})", item.Title, item.MeterName); 41 } 42 }
代码清单2-2中第一个代码块,我们创建 了Poet,Poem以及Meter实体类型的实例,诗人John Milton,他的诗“Paradise Lost"以及该诗的韵律,该诗属于五步抑扬格-Iambic Pentameter(诗的一种韵律)。我们一旦创建Poem实体类型的实例peom,并设置poem的Meter属性指向meter实例,Poet属性指向poet实例。使用相同的方法,我们创建其它的实体及关系。一旦完成创建,我们就可以调用SaveChanges()方法生成并执行适当的SQL语句,在底层数据库中插入数据。
代码清单2-2的输出发下:
Lord Byron
Don Juan (Anapestic Tetrameter)
Lewis Carroll
The Hunting of the Shark (Anapestic Tetrameter)
John Milton
Paradise Regained (Iambic Pentameter)
Paradise Lost (Iambic Pentameter)
Lewis Carroll
The Hunting of the Shark (Anapestic Tetrameter)
Lord Byron
Don Juan (Anapestic Tetrameter)
John Milton
Paradise Regained (Iambic Pentameter)
John Milton
Paradise Lost (Iambic Pentameter)
在代码中,我们最开始创建实例poet,poem以及 John Milton第一首诗的韵律meter。一旦我们创建好,就可以设置poem的导航属性Meter为meter实例,Poet导航属性为poet实例.实例poem的所有设置已完成,调用Add()方法将其添加到上下文中。接下来的工作将由实体框架来完成,包括添加poem到poet实例的导航属性Poems集合中,添加poem到meter实例的导航属性Poems集合中。使用相同的步骤完成剩下的任务。为了缩减代码,我们复用了变量和实例。
一旦创建完所有实例,并完成导航属性的初始化,我们就完成了一个对象图的创建。实体框架保持创建对象图的所有修改,这此跟踪是在数据库上下文中实现的。变量 context包含一个数据库上下文的实例(它的类型是DbContext),它是我们用来跟踪创建对象图所做修改的对象,调用其SaveChanges()方法,可以将所有修改发送到数据库中。
接来可以查询模型,以验证我们保存在数据库的数据。我们使用了一个新的上下文对象以及使用LINQ to Entities来查询。我们本来可以重用之前的上下文对象,但是我们知道它已经在内存中有了对象图,接下来的查询都不会通过数据库就直接从内存中就获取了(译注:这样就达不到验证的目的了)。
使用LINQ to Entities,我们查询出所有的诗人(poets),然后打印出每一位诗人的姓名,以及该诗人的每一首诗的详细信息。代码非常简单,但使用了两个嵌套的循环来实现。
最后一段代码块使用VwLibrary实体,该实体基于vwLibrary视图。vwLibrary视图把表连接在一起使其扁平化以提供一个更清晰的视角。我们通过vwLibraties实体集查询每位诗人的相关信息时,仅需要一次循环。输出有略有不同,因为我们在每首诗中重复了诗人的的姓名。
此示例中最后需要注意的是,我们没有通过视图vwLibrary插入poests,poems以及meters。因为在绝大多数的数据库中,视图是只读的。我在实体框架中,我们同样不能能过视图实体插入(或者更新、删除)实体。当然,我们将会在本书后面部分给你演示如何克服这个困难!