加载相关对象
实体类型可以定义在数据模型中表示关联的导航属性。可以使用这些属性加载与所定义的关联返回的实体相关的实体。如果实体是基于数据模型生成的,则会在关联两端为实体生成导航属性。这些导航属性在一对一或多对一关系的“一”端返回一个引用,或在一对多或多对多关系的“多”端返回一个集合。
加载相关实体的方法:
· 在查询中指定
· 显式加载
· 延迟加载
· 预先加载或使用 Include 定义查询路径
在查询中指定
导航属性:实体框架中的导航属性提供了一种在两个实体类型之间导航关联的方法。导航属性在概念模型中由 NavigationProperty 元素 (CSDL) 定义。针对对象参与到其中的每个关系,各对象均可以具有导航属性。使用导航属性,可以在两个方向上导航和管理关系,如果重数为一或者零或一,则返回 EntityReference<(Of <(<'TEntity>)>)>,或者如果重数为多个,则返回 EntityCollection<(Of <(<'TEntity>)>)>。也可以选择单向导航,这种情况下可以删除导航属性。
编辑和删除导航属性(实体数据模型工具):http://msdn.microsoft.com/zh-cn/library/bb738475.aspx
通过使用导航属性,可以编写 Entity SQL 或 LINQ to Entities 查询对关系进行显式导航。当执行此类查询时,将返回在最外部的查询投影中作为导航属性包含的相关实体。
示例类图:每个客户拥有一种客户类型和多个邮箱地址。
LINQ to Entities 示例
代码片断:
using (var context = new EFRecipesEntities())
{
//此处如果不在select中显示调用相关对象,则在遍历时会出错。
var customers = from customer in context.Customers
select new { Name = customer.Name, CustomerType = customer.CustomerType, CustomerEmails = customer.CustomerEmails };
Console.WriteLine("Customers");
Console.WriteLine("=========");
foreach (var customer in customers)
{
Console.WriteLine("{0} is a {1}, email address(es)", customer.Name, customer.CustomerType.Description);
foreach (var email in customer.CustomerEmails)
{
Console.WriteLine("\t{0}", email.Email);
}
}
}
Entity SQL 示例
代码片断:
using (var context = new EFRecipesEntities())
{
string esql = @"SELECT c.Name,c.CustomerType,c.CustomerEmails FROM Customers AS c";
foreach (DbDataRecord rec in new ObjectQuery<DbDataRecord>(esql, context))
{
CustomerType customerTypes = rec[1] as CustomerType;
List<CustomerEmail> customerEmails = rec[2] as List<CustomerEmail>;
Console.WriteLine("{0} is a {1}, email address(es)", rec[0], customerTypes.Description);
foreach (CustomerEmail customerEmail in customerEmails)
{
Console.WriteLine("\t{0}", customerEmail.Email);
}
}
}
运行结果:
显示加载
将实体显式加载到 ObjectContext 需要多次往返数据库,并且可能需要多个活动结果集,但是返回的数据量仅限于所加载的实体。可以对 EntityCollection<(Of <(<'TEntity>)>)> 或 EntityReference<(Of <(<'TEntity>)>)> 使用 Load 方法或对 ObjectContext 使用 LoadProperty 方法,以便从数据源显式检索相关实体。对于 Load 方法的每个调用都会打开与数据库的连接,以检索相关信息。这可确保在没有对相关实体的显式请求时,始终不会执行查询。显式加载是实体框架的默认行为。
说明说明:在调用 Load 之前,有关相关实体的少量信息已加载到 ObjectContext 中。
若要显式加载相关实体,必须对导航属性所返回的相关端调用 Load 方法。对于一对多关系,请对 EntityCollection<(Of <(<'TEntity>)>)> 调用 Load 方法,而对于一对一关系,请对 EntityReference<(Of <(<'TEntity>)>)> 调用 Load 方法。如果使用的是 POCO 实体,请对 ObjectContext 使用 LoadProperty 方法。LoadProperty 方法也可以用于从 EntityObject 派生的实体。这些方法将相关对象数据加载到对象上下文中。当查询返回结果时,可以使用 foreach 循环枚举整个对象集合,并对结果中每个实体的 EntityReference<(Of <(<'TEntity>)>)> 和 EntityCollection<(Of <(<'TEntity>)>)> 属性按条件调用 Load 方法。
说明:
当在 foreach (C#) 枚举过程中调用 Load 方法时,实体框架会尝试打开一个新的数据读取器。除非您已经通过在连接字符串中指定 multipleactiveresultsets=true 来启用多个活动结果集,否则此操作将失败。 还可以将查询结果加载到 List<(Of <(<'T>)>)> 集合中,这会关闭数据读取器并使您能够对集合进行枚举以加载引用的实体。
示例:使用 EntityCollection<(Of <(<'TEntity>)>)> 的 Load 方法来显式加载单个客户的所有邮箱项。
代码片断:
using (var context = new EFRecipesEntities())
{
Customer customer = context.Customers.Where("it.CustomerID=@CustomerID", new ObjectParameter("CustomerID", 13)).First();
if (!customer.CustomerEmails.IsLoaded)
{
//显示加载
customer.CustomerEmails.Load();
}
Console.WriteLine("Name:{0}", customer.Name);
foreach (CustomerEmail customerEmail in customer.CustomerEmails)
{
Console.WriteLine("Email:{0}", customerEmail.Email);
}
}
运行结果:
示例:使用 EntityCollection<(Of <(<'TEntity>)>)> 的 CreateSourceQuery 方法,以仅加载1个具有相关 CustomerEmail对象(这些对象属于单个 Contact)的 CustomerEmail 对象。 随后,此查询结果会附加到原始 CustomerEmailEntityCollection<(Of <(<'TEntity>)>)>。
代码片断:
Customer customer = context.Customers.Where("it.CustomerID=@CustomerID", new ObjectParameter("CustomerID", 13)).First();
customer.CustomerEmails.Attach(customer.CustomerEmails.CreateSourceQuery().Take(1));
//if (!customer.CustomerEmails.IsLoaded)
//{
// customer.CustomerEmails.Load();
//}
...
延迟加载
实体框架支持相关实体的延迟加载。在实体框架运行时中,ObjectContext 实例中 LazyLoadingEnabled 属性的默认值为 false。但是,如果使用实体框架工具创建新模型和对应的生成类,则在对象上下文的构造函数中,LazyLoadingEnabled 设置为 true。启用延迟加载后,在某个导航属性的 get 访问器以编程方式访问相关实体之前,将不会从数据源加载它们。若要禁用延迟加载,请对于由ObjectContext.ContextOptions 属性返回的 ObjectContextOptions 实例将 LazyLoadingEnabled 属性设置为 false。
延迟加载可以与预先加载一起使用。 通过这种方式,可以使用查询路径定义基本数据图,并可以根据需要加载原始查询路径中未包括的其他相关实体。
延迟加载时注意:
· 对于同时返回单个实体(如 EntityReference<(Of <(<'TEntity>)>)>)和实体集合(如 EntityCollection<(Of <(<'TEntity>)>)>)的导航属性,支持延迟加载。
· 如果启用了延迟加载并且已加载了相关实体,则不会再次加载它。
· 对于处于 Detached 状态的实体,支持延迟加载。 在此情况下,相关对象还将以 Detached 状态返回。
· 延迟加载行为是由用于从数据源检索对象(即使实体是使用 NoTrackingMergeOption 加载的)或对象所添加或附加到的 ObjectContext 实例确定的。 因此,一旦释放了此上下文,将无法更改延迟加载行为,并且任何进一步的延迟加载操作都将失败。
· 当对实体进行序列化时,请考虑禁用延迟加载。 否则,将触发延迟加载,并且序列化实体包含的数据可能超过预期。
代码片断:(预先加载有介绍)
context.ContextOptions.LazyLoadingEnabled = true;
预先加载
如果了解应用程序需要的相关实体的图形的确切形状时,可以使用 ObjectQuery<(Of <(<'T>)>)> 的 Include 方法来定义查询路径,此查询路径控制将哪些相关实体作为初始查询的一部分返回。当定义查询路径时,仅需对数据库请求一次,即可在单个结果集中返回查询路径所定义的所有实体,并且属于在路径中定义的类型的所有相关实体将随查询返回的每个对象一起加载。
例如:“context.CustomerTypes.Include("Customers.CustomerEmails")” 返回 CustomerTypes 相关的 Customers 和 CustomerEmails 对象的查询路径。可以对一个 ObjectQuery<(Of <(<'T>)>)> 多次调用此方法,从而包括来自多个关系的实体,如“context.Customers.Include("CustomerType").Include("CustomerEmails")”。
代码片断:
using (var context = new EFRecipesEntities())
{
var web = new CustomerType { Description = "Web Customer", CustomerTypeId = 1 };
var retail = new CustomerType { Description = "Retail Customer", CustomerTypeId = 2 };
//添加客户“Joan Smith”,所属类型"web",拥有两个“Email”。
var customer = new Customer { Name = "Joan Smith", CustomerType = web };
customer.CustomerEmails.Add(new CustomerEmail { Email = "jsmith@gmail.com" });
customer.CustomerEmails.Add(new CustomerEmail { Email = "joan@smith.com" });
context.Customers.AddObject(customer);
customer = new Customer { Name = "Bill Meyers", CustomerType = retail };
customer.CustomerEmails.Add(new CustomerEmail { Email = "bmeyers@gmail.com" });
context.Customers.AddObject(customer);
//提交添加
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
//EF4默认不开启延迟加载功能,如果略掉Include,则在访问子项时会出错。
var customers = context.Customers.Include("CustomerType").Include("CustomerEmails");
Console.WriteLine("Customers");
Console.WriteLine("=========");
foreach (var customer in customers)
{
Console.WriteLine("{0} is a {1}, email address(es)", customer.Name, customer.CustomerType.Description);
foreach (var email in customer.CustomerEmails)
{
Console.WriteLine("\t{0}", email.Email);
}
}
}
using (var context = new EFRecipesEntities())
{
//必须使用Include("Customers.CustomerEmails")来加载。
//var customTypes = context.CustomerTypes.Include("Customers").Include("CustomerEmails");
var customTypes = context.CustomerTypes.Include("Customers.CustomerEmails");
Console.WriteLine("\nCustomers by Type");
Console.WriteLine("=================");
foreach (var customerType in customTypes)
{
Console.WriteLine("Customer type: {0}", customerType.Description);
foreach (var customer in customerType.Customers)
{
Console.WriteLine("{0}", customer.Name);
foreach (var email in customer.CustomerEmails)
{
Console.WriteLine("\t{0}", email.Email);
}
}
}
}
Console.WriteLine("Press <enter> to continue...");
Console.ReadLine();
延迟加载:LazyLoadingEnabled() 属性用来设置相关对象的惰性加载行为。LazyLoadingEnabled() 的默认值为 false。使用这种加载时,请注意如果对象已经不在 ObjectContext 中,您访问的每个导航属性将导致对数据源执行单独的查询。开启延迟加载功能,context.ContextOptions.LazyLoadingEnabled = true;则不用显示使用Include方法来加载相关实体。
定义查询路径时注意事项:
· 查询路径可以用于查询生成器方法和 LINQ 查询。
· 在调用 Include 时,查询路径仅在 ObjectQuery<(Of <(<'T>)>)> 的返回实例上有效。 不影响 ObjectQuery<(Of <(<'T>)>)> 的其他实例和对象上下文本身。
· 因为 Include 返回查询对象,所以可以对一个 ObjectQuery<(Of <(<'T>)>)> 多次调用此方法,从而包括来自多个关系的实体,如:“context.Customers.Include("CustomerType").Include("CustomerEmails")” 。
· 如果使用查询路径,看似简单的对象查询也可能需要对数据源执行复杂的命令。 之所以发生这种情况,原因是在单个查询中返回相关对象要求具有一个或多个联接,这可能导致从数据源返回的每个相关实体出现冗余数据。 对复杂模型(如具有继承关系的实体或包含多对多关系的路径)进行的查询的复杂性将进一步加大。 使用 ToTraceString 方法可以查看将由 ObjectQuery<(Of <(<'T>)>)> 生成的命令。 如果查询路径包含过多相关对象,或对象包含过多行数据,数据源可能无法完成查询。 如果查询所需的中间临时存储区超过数据源的容量,则会出现这种情况。 出现这种情况时,通过显式加载相关对象或启用延迟加载可以降低数据源查询的复杂性。 如果在优化复杂查询后仍然频繁超时,请考虑通过设置 CommandTimeout 属性来增大超时值。