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賦值。
暫時沒有想到更好的辦法,遂先滾回去。:(


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM