Entity Framework 小技巧三 —— 如何在导入集合类型的Navigation Property时增加过滤条件?

在使用EF的集合类型的Navigation Property时,我时常需要只导入符合特定条件的实体对象,例如:Blog类和Post类存在一对多关系,即一个博客可以拥有多篇博文。现在我们想获得博客以及其相关博文中在今年1月1日之后发表的所有博文,可是之前EF版本所提供的Lazy LoadingExplicitly LoadingEagerly Loading都不支持在Navigation Property上增加一个过滤条件。有关三种Loading的详细信息,不在本文过多介绍,请见MSDN文档:http://msdn.microsoft.com/en-us/library/bb896272.aspx,或者参看我以前写过的一篇英文MSDN博客:http://blogs.msdn.com/b/msdnforum/archive/2010/07/12/community-goodies-lazy-loading-in-entity-framework-4.aspx。之后也会为大家带来有关Loading的文章。

 

过去我们可以用以下两种方法来实现这一目的:

1)  利用匿名类,并且将过滤条件嵌入到返回的匿名类中:

using (var context = new MyObjectContext))
{
    var query 
= (from b in context.Blogs
                 
where b.Tags.Contains("EF")
                 select 
new 
                 {
                     Blog 
= b,
                     Posts 
= b.Posts.Where(p => p.CreationTime >= new DateTime(201111))
                 }).ToList();

} 

不过,此方法的缺点显而易见, 我们必须使用匿名类来包装实体对象,在数据绑定时会有挺多问题。

 

2)   使用多个query,让EF的跟踪机制帮助我们建立对象之间的联系。(EF4和EF4.1中,我们需要关闭Lazy Loading)

using (var context = new MyObjectContext))
{
    
// For EF4
    
// context.ContextOptions.LazyLoadingEnabled = false;
    // For EF4.1
    // context.Configuration.LazyLoadingEnabled = false;
    var blogs = (from b in context.Blogs
                 
where b.Tags.Contains("EF")
                 select b).ToList();
    
    var posts 
= (from p in context.Posts
                 
where p.Blog.Tags.Contains("EF")
                 
&& p.CreationTime >= new DateTime(201111)
                 select p).ToList();
}

在两个query执行之后,EF的ObjectStateManager会自动建立Blog和Post之间的关系,如果我们访问blogs.First().Posts属性,将得到的都是今年1月1日之后创建的博文。

 

当然我想这两个方法在EF4.1中同样能成功。幸运的是,EF4.1提供了新的功能使我们可以在使用Explicitly Loading时添加过滤条件。

using (var context = new MyDbContext())
{
   var blog 
= context.Blogs.Where(b => b.Tags.Contains("EF")).First();

   context.Entry(blog).
Collection(b => b.Posts)
          .Query()
          .Where(p 
=> p.CreationTime >= new DateTime(201111))
          .Load();

   
// 或者使用String类型来标记Blog与Post的关系
   context.Entry(blog).Collection("Posts")
          .Query().Cast
<Post>()
          .Where(p 
=> p.CreationTime >= new DateTime(201111))
          .Load();
}

Collection()方法在这里返回DbCollectionEntry,Query()方法返回对应的IQueryable<T>对象。熟悉LINQ的同志们一定知道,得到IQueryable<T>了的话,一切好说,哈哈。EF4.1中,System.Data.Entity命名空间下的DbExtensions类中定义了不少LINQ的扩展方法,其中一个就是Load方法。用Reflector看了下Load方法的实现,其实就是调用IEnumerator.MoveNext遍历一遍集合:

using (IEnumerator enumerator = source.GetEnumerator())
{
    
while (enumerator.MoveNext())
    {
    }
}
值得注意的是:在EF4.1中使用这一功能最好也关闭Lazy Loading,否则一旦对应的集合类型的Navigation Property被访问的话,所有对应的实体都将被导入。

 

当然这里如果我们不想导入这些相关的实体对象,也可以使用Count()方法得到符合条件对象的数量。这样我们得到的是实体的数量,而没有一个对应实体会被导入。

using (var context = new MyDbContext())
{
   var blog 
= context.Blogs.Where(b => b.Tags.Contains("EF")).First();

   context.Entry(blog).Collection(b 
=> b.Posts)
          .Query()
          .Count();
}


一口气搞定这篇,希望对大家学习EF有所帮助吧! 

 

 

PS1:这里为大家带来一个好消息:微软一站式实例代码库(Microsoft All-In-One Code Framework)即日起正式迁移至MSDN代码库了,新的平台会帮您更轻松地解决开发难题、节省更多时间、获得更友好的用户体验。本人作为这个项目的元老,见到我们已拥有600多个经典的代码实例,甚感欣慰啊!  更详细信息,请看http://msdn.microsoft.com/zh-cn/hh124104.aspx?ocid=ban-f-cn-loc-OC201104-MSDN

最新的代码浏览器也发布啦!为大家带来了更多很cool的功能,比如1)代码按需下载 2)实例集中化管理  3)自动更新

http://blog.csdn.net/MSCodeSample/archive/2011/04/18/6331382.aspx 

之后我将尽力为大家带来更多有关EF的代码实例以及相关的介绍!



PS2:同事开发了一个很cool的MSDN论坛桌面小工具,绝对给力!欢迎使用!(我也出了不少力啊


也欢迎到MSDN中文论坛ADO.NET与LINQ论坛来提问EF的问题啊,可以试试直接报我的名字Michael Sun,哈哈!


如需转发请注明原文出处,谢谢:http://www.cnblogs.com/LingzhiSun/archive/2011/04/19/EF_Trick3.html

posted @ 2011-04-19 09:11  LingzhiSun  阅读(5535)  评论(22编辑  收藏  举报