zoukankan      html  css  js  c++  java
  • 《Entity Framework 6 Recipes》中文翻译系列 (23) -----第五章 加载实体和导航属性之预先加载与Find()方法

    翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇

    5-2  预先加载关联实体

    问题

      你想在一次数据交互中加载一个实体和与它相关联实体。

    解决方案

      假设你有如图5-2所示的模型。

    图5-2 包含Customer和与它相关联信息的实体

      和5-1节一样,在模型中,有一个Customer实体,一个与它关联的CustomerType和多个与它关联的CustomerEamil。它与CustomerType的关系是一对多关系,这是一个实体引用(译注:Customer中的导航属性CustomerType)。

      Customer与CustomerEmail也是一对多关系,只是这时CustomerEmail在多的这一边。这是一个实体集合(译注:Customer中的导航属性CustomerEmails)。

      为了在一次查询中,获取父对象customer和与它关联的实体CustomerEamil和CustomrType的所有数据,我们使用Include()方法。如代清单5-2所示。

    代码清单5-2. 预先加载与Customer相关联的CustomerType和CustomerEmail实例

     1 using (var context = new EFRecipesEntities())
     2             {
     3                 var web = new CustomerType {Description = "Web Customer", CustomerTypeId = 1};
     4                 var retail = new CustomerType {Description = "Retail Customer", CustomerTypeId = 2};
     5                 var customer = new Customer {Name = "Joan Smith", CustomerType = web};
     6                 customer.CustomerEmails.Add(new CustomerEmail {Email = "jsmith@gmail.com"});
     7                 customer.CustomerEmails.Add(new CustomerEmail {Email = "joan@smith.com"});
     8                 context.Customers.Add(customer);
     9                 customer = new Customer {Name = "Bill Meyers", CustomerType = retail};
    10                 customer.CustomerEmails.Add(new CustomerEmail {Email = "bmeyers@gmail.com"});
    11                 context.Customers.Add(customer);
    12                 context.SaveChanges();
    13             }
    14 
    15             using (var context = new EFRecipesEntities())
    16             {
    17 
    18                 //Include()方法,使用基于字符串类型的,与导航属性相对应的查询路径
    19                 var customers = context.Customers
    20                                        .Include("CustomerType")
    21                                        .Include("CustomerEmails");
    22                 Console.WriteLine("Customers");
    23                 Console.WriteLine("=========");
    24                 foreach (var customer in customers)
    25                 {
    26                     Console.WriteLine("{0} is a {1}, email address(es)", customer.Name,
    27                                       customer.CustomerType.Description);
    28                     foreach (var email in customer.CustomerEmails)
    29                     {
    30                         Console.WriteLine("	{0}", email.Email);
    31                     }
    32                 }
    33             }
    34 
    35             using (var context = new EFRecipesEntities())
    36             {
    37                 //Include()方法,使用基于强类型的,与导航属性相对应的查询路径
    38                 var customerTypes = context.CustomerTypes
    39                                            .Include(x => x.Customers
    40                                                           .Select(y => y.CustomerEmails));
    41 
    42                 Console.WriteLine("
    Customers by Type");
    43                 Console.WriteLine("=================");
    44                 foreach (var customerType in customerTypes)
    45                 {
    46                     Console.WriteLine("Customer type: {0}", customerType.Description);
    47                     foreach (var customer in customerType.Customers)
    48                     {
    49                         Console.WriteLine("{0}", customer.Name);
    50                         foreach (var email in customer.CustomerEmails)
    51                         {
    52                             Console.WriteLine("	{0}", email.Email);
    53                         }
    54                     }
    55                 }
    56             }

     代码清单5-2的输出如下:

    Customers
    =========
    Joan Smith is a Web Customer, email address(es)
    jsmith@gmail.com
    joan@smith.com
    Bill Meyers is a Retail Customer, email address(es)
    bmeyers@gmail.com
    Customers by Type
    =================
    Customer type: Web Customer
    Joan Smith
    jsmith@gmail.com
    joan@smith.com
    Customer type: Retail Customer
    Bill Meyers
    bmeyers@gmail.com

      

    原理

      默认情况下,实体框架只加载你指定的实体,这就是所谓的延迟加载。用户在你的应用中会根据他的需要浏览不同的视图,在这种情况下延迟加载很有效。

      与之相反的是,立即加载父实体和与之关联的子实体(记住,对象图是基于关联的父实体和子实体,就像数据库中基于外键的父表和子表)。它叫做Eager Loading(预先加载)。它在需要大量关联数据时很有效,因为它在一个单独的查询中获取所有的数据(父实体和与之关联的子实体)。

      在代码清单5-2中,我们两次使用Include()方法(译注:第一段代码块中),立即获取对象图。第一次,我们加载一个包含Customer实体和实体引用CustmerType的对象图。CustomerType在一对多关联中的一这边。第二次,我们使用Include()方法(用相同的代码串连在一起)获取一对多有关联中多一边的CustomerEmails。两次通过fluent API方式将Include()方法链接在一起,我们从Customer的导航属性获取与其关联的实体。注意,我们在示例中使用字符串类型来表示导航属性,使用"."字符来分隔(译注:示例中没有用到,比如这样的的形式Include(“CustomerType.Customers”))。这种字符串形式的表示方式叫做关联实体的查询路径(query path)

      在接下来的代码块中,我们执行一样的操作,但使用了强类型的查询路径。请注意我们是如何使用lambda表达式来标识每一个关联实体的。强类型的用法给我们带来了智能提示、编译时检查和重构支持。

      请注意,代码清单5-3中使用Include()方法产生的SQL查询语句 。在结果集被实例化和返回之前,实体框架自动移除查询中重复的数据。如图5-3所示。

    代码清单5-3. 使用Include()方法产生的SQL查询语句

     1 SELECT
     2 [Project1].[CustomerId] AS [CustomerId],
     3 [Project1].[Name] AS [Name],
     4 [Project1].[CustomerTypeId] AS [CustomerTypeId],
     5 [Project1].[CustomerTypeId1] AS [CustomerTypeId1],
     6 [Project1].[Description] AS [Description],
     7 [Project1].[C1] AS [C1],
     8 [Project1].[CustomerEmailId] AS [CustomerEmailId],
     9 [Project1].[CustomerId1] AS [CustomerId1],
    10 [Project1].[Email] AS [Email]
    11 FROM ( SELECT
    12 [Extent1].[CustomerId] AS [CustomerId],
    13 [Extent1].[Name] AS [Name],
    14 [Extent1].[CustomerTypeId] AS [CustomerTypeId],
    15 [Extent2].[CustomerTypeId] AS [CustomerTypeId1],
    16 [Extent2].[Description] AS [Description],
    17 [Extent3].[CustomerEmailId] AS [CustomerEmailId],
    18 [Extent3].[CustomerId] AS [CustomerId1],
    19 [Extent3].[Email] AS [Email],
    20 CASE WHEN ([Extent3].[CustomerEmailId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    21 FROM [Chapter5].[Customer] AS [Extent1]
    22 INNER JOIN [Chapter5].[CustomerType] AS [Extent2] ON 
    23 [Extent1].[CustomerTypeId] = [Extent2].[CustomerTypeId]
    24 LEFT OUTER JOIN [Chapter5].[CustomerEmail] AS [Extent3] ON 
    25 [Extent1].[CustomerId] = [Extent3].[CustomerId]
    26 ) AS [Project1]
    27 ORDER BY [Project1].[CustomerId] ASC, [Project1].[CustomerTypeId1] ASC, [Project1].[C1] ASC

    图5-3 通过使用Include()方法产生的冗余数据

    5-3  快速查询一个单独的实体

    问题

      你想加载一个单独的实体,但是,如果该实体已经加载到上下文中时,你不想再进行一次数据库交互。同时,你想使用code-first 来管理数据访问。

    解决方案

      假设你有如图5-4所示的模型。

    图5-4 包含一个Club实体类型的模型

      在这个模型中,我们有一个实体类型Club,你可以通过查询获取各种各样的俱乐部(Clubs).

       在Visual Studio中添加一个名为Recipe3的控制台应用,并确保引用了实体框架6的库,NuGet可以很好的完成这个任务。在Reference目录上右键,并选择 Manage NeGet Packages(管理NeGet包),在Online页,定位并安装实体框架6的包。这样操作后,NeGet将下载,安装和配置实体框架6的库到你的项目中。

      创建一个名为Club类,复制代码清单5-4中的属性到这个类中,创建club实体。  (译注:本书是多位作者写的,描述的风格肯定有所不同)

    代码清单5-4. Club 实体类

    1   public class Club
    2     {
    3         public int ClubId { get; set; }
    4         public string Name { get; set; }
    5         public string City { get; set; }
    6     }

      

       接下来,创建一个名为Recipe3Context的类,并将代码清单5-5中的代码添加到其中,并确保其派生到DbContext类。

     1  public class Recipe3Context : DbContext
     2     {
     3         public Recipe3Context()
     4             : base("Recipe3ConnectionString")
     5         {
     6             // 禁用实体框架的模型兼容性
     7             Database.SetInitializer<Recipe3Context>(null);
     8         }
     9 
    10         protected override void OnModelCreating(DbModelBuilder modelBuilder)
    11         {
    12              modelBuilder.Entity<Club>().ToTable("Chapter5.Club");
    13         }
    14 
    15         public DbSet<Club> Clubs { get; set; }
    16     }

      接下来添加App.Config文件到项目中,并使用代码清单5-6中的代码添加到文件的ConnectionStrings小节下。

    <connectionStrings>
    <add name="Recipe3ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
    </connectionStrings>

      

      如果我们正使用一个关键词来搜索实体,一般是这样操作过程,凭借Find()方法,在从数据库中获取之前,先在内存中查找。记住,实体框架的默认行为,当你给出一个获取数据的操作时,它会去查询数据库,即使数据已经被加载到上下文中

      方法Find()是DbSet类中的成员函数,它是我们用来注册实体到上下文对象中的类。代码清单5-7将对此进行演示。

    代码清单5-7. 凭借实体框架中的Find()方法,避免获取已经加载到上下文对象中的数据。

     1  int starCityId;
     2             int desertSunId;
     3             int palmTreeId;
     4 
     5             using (var context = new Recipe3Context())
     6             {
     7                 var starCity = new Club {Name = "Star City Chess Club", City = "New York"};
     8                 var desertSun = new Club {Name = "Desert Sun Chess Club", City = "Phoenix"};
     9                 var palmTree = new Club {Name = "Palm Tree Chess Club", City = "San Diego"};
    10 
    11                 context.Clubs.Add(starCity);
    12                 context.Clubs.Add(desertSun);
    13                 context.Clubs.Add(palmTree);
    14                 context.SaveChanges();
    15 
    16                 // SaveChanges()返回每个最新创建的Club Id
    17                 starCityId = starCity.ClubId;
    18                 desertSunId = desertSun.ClubId;
    19                 palmTreeId = palmTree.ClubId;
    20             }
    21 
    22             using (var context = new Recipe3Context())
    23             {
    24                 var starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);
                starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);
    25 starCity = context.Clubs.Find(starCityId); 26 var desertSun = context.Clubs.Find(desertSunId); 27 var palmTree = context.Clubs.AsNoTracking().SingleOrDefault(x => x.ClubId == palmTreeId); 28 palmTree = context.Clubs.Find(palmTreeId); 29 var lonesomePintId = -999; 30 context.Clubs.Add(new Club {City = "Portland", Name = "Lonesome Pine", ClubId = lonesomePintId,}); 31 var lonesomePine = context.Clubs.Find(lonesomePintId); 32 var nonexistentClub = context.Clubs.Find(10001); 33 } 34 35 Console.WriteLine("Please run this application using SQL Server Profiler..."); 36 Console.ReadLine();

    原理

      当使用上下文对象查询时,即使数据已经加载到上下文中,仍会产生一次获取数据的数据库交互。当一次查询完成时,不存在上下文中的实体对象将被添加到上下文中,并被跟踪。在默认情况下,如果实体对象已经在上下文中,实体框架不会使用数据库中较新的值重写它

      然后, DbSet对象,它包装着我们的实体对象,公布了一个Find()方法。特别地,Find()方法期望得到一个被查询对象的主键(ID)参数。Find()方法非常有效率,因为它会先为目标对象查询上下文。如果对象不存在,它会自动去查询底层的数据存储。如果仍然没有找到,Find()方法将返回NULL给调用者。另外,Find()方法将返回已添加到上下文中(状态为"Added"),但还没有保存到数据库中的对象。Find()方法对三种建模方式均有效:Database First,Model First,Code First。

      在示例中,我们添加三个clubs实体到Club实体集合。请注意,在调用SaveChanges()后,我们是如何引用新创建的Club实体的ID的。当SaveChages()操作完成后,上下文会立即返回新创建对象的ID.

      接下来,我们从DbContext中查询实体,并返回StarCity Club 实体。注意,我们是如何凭借LINQ扩展方法SingleOrDefault(),返回一个对象的,如果在底层数据库中不存在要查找的对象,它返回NULL。当发现多个符合给定条件的对象时,SingleOrDefault()方法将抛出一个异常。SingleOrDefault()在通过主键查找对象时,是一个非常好的方法。如果存在多个对象且你希望返回第一个时,可以考虑使用FirstOrDefault()方法

      如果你运行SQL Profiler Tool(在SQL Server Developer Edition版本或更高版本中,SQL Express版本不包含),检查底层数据库的活动,你会看见如图5-5所示的SQL查询语句产生。

     图5-5 返回 Star City Club的SQL的查询语句

       请注意图5-5,为何在上下文对象中查询Clubs,总是会产生一个针对底层数据库的SQL查询语句。这里我们获取ID为80的Club,将数据实例化到Club实体对象,并存放在上下文对象中。有趣的是,为什么LINQ扩展方法SingleOrDefault()总是产生一个Select Top 2 的SQL查询。 Select Top 2 这条SQL查询确保只有一行数据被返回。 如果多于一条数据返回, 实体框架将抛出一个异常,因为 SingleOrDefault()方法保证只返回一个单独的结果。

      下一行代码(译注:指的是 starCity = context.Clubs.SingleOrDefault(x => x.ClubId == starCityId);),重新查询数据库获取相同的对象,Star City Club。请注意,虽然对象已经存在上下文中,但实体框架DbContext的默认行为,仍会重新查询数据库获取记录。在Profiler中,我们看相同的SQL语句被产生。不仅如此,因为Star City实体已经加载到上下文中,DbContext不会使用数据库中的新值来替换当前的值,如图5-6所示。

    图5-6 返回Star City Club的SQL语句

      下一行代码,我们再一次查找Star City Club。然后,这次我们使用的是Find()方法,它是在DbSet类中公布的。因为Clubs是一个DbSet类,因此,我们只是在它身上简单地调用Find()方法,并把要查找对象的主键作为参数传递线它。在我们示例中,主键的值为80。

      Find()方法首先在上下文对象中查找Star City Club,找到对象后,它返回该对象的引用。关键点是,Find()方法只有在上下文中没有找需要的对象时,才去数据库中查询。请注意,图5-7中为什么没有产生SQL语句。

    图5-7 Find()在上下文中找到了对象,没有产生任何针对数据库查询语句

     

      接下来,我们再次使用Find()方法去获取实体对象Desert Sun Club。方法Find()没有在上下文中找到该对象,它将查询数据库并返回信息。图5-8是它查询该对象产生的SQL语句。

    图5-8 返回Desert Sun Club对象产生的SQL语句

     

      在下一个查询中,我们获取实体对象Palm Tree Club的信息,但是我们这次使用LINQ查询。 注意AsNotracking()从句,它被添加到Clubs后面。NoTracking 选项将禁用指定对象的对象跟踪。没有了对象跟踪,实体框架将不在跟踪Palm Tree Club对象的改变。也不会将对象加载到上下文中

      随后,当我们查询并获取Palm Tree Club实体对象时,Find()方法将产生一个SQL查询语句并从数据库从获取实体。如图5-9所示。因为我们使用AsNoTracking()从句指示实体框架不要在上下文中跟踪对象,所以,数据库交互就成了必须的了。记住,Find()方法需要对象跟踪,以避免数据库调用 。

    图5-9 返回Desert Sun Club实体产生的SQL查询语句

      

       接下来,我们添加一个新的Club实体到上下文中。我们实例化一个Club实体类,并填充必要的数据。为Id分配一个临时的值-999。记住,我们不需要调用SaveChage()来提交新的Club对象,Lonesome Pine Club,到数据库。有趣的是,我们使用Find()方法并给它传递参数-999,实体框架从上下文中返回最新创建的 Lonesome Pine Club实体对象。你可以从图5-10中看到,这次调用Find()方法没有产生数据库活动。注意,Find()方法会返回一个最近添加到上下文中的实例,即使它还没有被保存到数据库中

    图5-10 Find()方法在上下文中定位一个刚创建,但没有保存的对象并返回,这个过程不生成sql查询语句

     

       最后,我们给Find()方法传递一个数据库中不存在的Id作为参数。这个Id的值为10001.如图5-11所示,Find()方法生成SQL查询并试图在数据库中返回Id为10001的记录。跟LINQ扩展方法SingleOrDefault()一样,如果没有找到指定的记录,会向调用方返回NULL。

    图5-11 Find()方法生成一个SQL查询,如果数据库中不存在要查找的记录便返回null

     

     

    实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

    谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

  • 相关阅读:
    现代软件工程 第一章 概论 第3题——韩婧
    现代软件工程 第一章 概论 第2题——韩婧
    小组成员邓琨、白文俊、张星星、韩婧
    UVa 10892 LCM的个数 (GCD和LCM 质因数分解)
    UVa 10780 幂和阶乘 求n!中某个因子的个数
    UVa 11859 除法游戏(Nim游戏,质因子)
    Codeforces 703C Chris and Road 二分、思考
    Codeforces 703D Mishka and Interesting sum 树状数组
    hdu 5795 A Simple Nim SG函数(多校)
    hdu 5793 A Boring Question 推公式(多校)
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4519642.html
Copyright © 2011-2022 走看看