EF联合查询,如何设置条件过滤从表数据

最近在使用EF进行联合查询过程中,遇到了一件不开心的事情。

已禁用懒加载

var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Include(p => p.PostToCategories)
                        .SingleOrDefaultAsync();
如上代码所示的查询中,使用Include()关联了PostToCategories,这是常用的联合查询方式。可是PostToCategories是软删除(IsAcitve)的,使用Include()方法会把所有的相关的PostToCategories都查询出来,这不是我们想要的结果。
首先分析下原因,Include()方法,是根据配置的关系查询关联的对象,所以我们只要在它生成sql之前加上过滤条件就可以了,可是纵观EF的api,无一能实现目的。既然这样,我们只能想想其他办法了。
var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Select( p => new {
                                                BlogPost = p,
                                                PostToCategories = p.PostToCategories.Where(c => c.IsActive)
                                                }).SingleOrDefaultAsync();
var blogPost = post.BlogPost;
blogPost.PostToCategories = post.PostToCategories.ToList();

用匿名类型接受主表和从表的查询结果,这样就可以为从表设置过滤条件了。最后,把匿名结果显示赋值给blogPost。
通过观察生成的sql语句,我们发现确实是在 ` join PostToCategories ` 后面增加了where条件。
但是这种方法在面对多对多关系时,就**不够优雅**了。
 var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Include(p => p.TagMap)
                        .Include(p => p.TagMap.Select(t => t.Tag))
                        .SingleOrDefaultAsync();

改写成如下:

var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Select( p => new {
                                                BlogPost = p,
                                                TagMap = p.TagMap,
                                                Tag = p.TagMap.Select(t => t.Tag),
                                                }).SingleOrDefaultAsync();
var blogPost = post.BlogPost;
blogPost.TagMap = post.TagMap;
blogPost.TagMap.ForEach(m => m.Tag = post.Tag.FirstOrDefault(g => g.Id == m.TagId));

是不是感觉不美好了?

那有没有更好的办法呢?stackoverflow上有人推荐了一个针对EF的扩展包 EntityFramework.DynamicFilters ,它的实现方式是在OnModelCreating的时候给Entity设置好过滤条件,当前DbContext对象涉及到该Entity类型的查询时,都会自动加上过滤条件。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Filter("PostToCategory_IsActive", (PostToCategory ptc) => ptc.IsActive, true);//为PostToCategory类型设置过滤条件:IsActive==true
}

直接使用Include()就可以得到我们想要的结果,如下所示:

var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Include(p => p.PostToCategories)
                        .SingleOrDefaultAsync();

可是这样以来所有的涉及到PostToCategories的查询都会被过滤,怎么办呢?EntityFramework.DynamicFilters提供 禁用/启动 过滤条件的API

context.DisableFilter/EnableFilter("PostToCategory_IsActive");// 在当前DbContext实例对象中禁用/启用名为PostToCategory_IsActive的Filter
modelBuilder.DisableFilterGlobally("PostToCategory_IsActive");// 全局禁用名为PostToCategory_IsActive的Filter
context.DisableAllFilters();//禁用所有的Filters
context.EnableAllFilters(); //启用所有的Filters

于是代码就变成了这样:

一、设置Filter

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Filter("PostToCategory_IsActive", (PostToCategory ptc) => ptc.IsActive, true);//为PostToCategory类型设置过滤条件:IsActive==true
}

二、全局禁用Filters

context.DisableAllFilters();

三、在需要过滤的地方启用Filter

context.EnableFilter("PostToCategory_IsActive");
var post = await _repository.GetMyPostById(blogId, postId).AsNoTracking()
                        .Include(p => p.PostToCategories)
                        .SingleOrDefaultAsync();

而且还没有考虑DbContext缓存查询结果带来的问题。
如果说第一种解决方案不优雅,那么这种方案就是恶心

--- 2016.10.10 ---

昨天借鉴了ABP的封装思想,把EntityFramework.DynamicFilters的API封装了下,确实美观多了。
参见:

本来顺风顺水,如tkb至简的博文中描述的那样,优雅的过滤数据,可惜最后在一次保存的时候出现了意外,最后发现原因出在和EntityFramework.Extended有冲突,因为后者是重新生成了SqlExpression,没有对EntityFramework.DynamicFilters在设置where条件中的@DynamicFilterParam_000001赋值。
暂时没有想到更好的办法,遂先滚回去。:(

posted @ 2016-10-01 12:00  蝌蝌  阅读(4001)  评论(5编辑  收藏  举报