2016-10-11 65 views
3

我有一個來自MVC提交的viewmodel類,並且我想根據用戶填寫的值從EF6獲取結果集,但忽略這些項目在那些空模型:使用非空對象屬性的EF6查詢條件

public class SearchFilterVM 
{ 
    public int? ID { get; set; } 
    public bool? Active { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string MiddleInitial { get; set; } 
    public DateTime? DateOfBirth { get; set; } 
    public string Phone { get; set; } 
    public string Email { get; set; } 
} 

基本上就是我尋找的是這樣的僞代碼:

var results = context.Members 
    .Where(x => x.Active == vm.Active.Value) // but only if vm prop is not null 
    .Where(x => x.FirstName == vm.FirstName) // but only if vm prop is not null 
    .Where(x => x.LastName == vm.LastName)  // but only if vm prop is not null 
    .ToList(); 

(例如,如果過濾器模型屬性爲null,完全忽略他們)

當然,我不想使用類似上面的內容,因爲它會根據第一個標準進行查詢,然後根據第二個適用的標準進行查詢等,直到完成(我正在使用500k +行)。

我想不出一種使用LINQ查詢表達式的方法。

我可以做的是根據值的存在構建一個參數化的SQL語句,並附加條件,並最終通過EF6的RawSQLQuery傳遞它,這將工作在性能方面(並且可以很好地控制索引的順序字段爲更好的調整),但我想知道是否有一種「自然」的方式來通過LINQ完成相同的事情。

+0

只有當它不是null時,纔會引用'vm'的值嗎?如果它是'null',那麼你想要做什麼?沒有那種條件? –

+0

@GiladGreen - 正確,更新的問題,以適應 – jleach

回答

5

我建議的方法

記住,EF使用延遲執行和你,直到你與ToList()兌現,或迭代它例如不實際執行任何查詢。這意味着你可以這樣做:

var results = context.Members; 

if(vm.Active.HasValue) 
{ 
    results = results.Where(x => x.Active == vm.Active.Value); 
} 

if(!string.IsNullOrEmpty vm.FirstName)) 
{ 
    results = results.Where(x => x.FirstName == vm.FirstName); 
} 

//and so on until... 

return results.ToList(); 

其他方法

我想我會添加這種額外的贈品理解爲什麼你可能不希望使用其他的答案中提到的技術。假設你這樣做了:

string name = "bob"; 

var users = context.Users.Where(u => name == null || u.Name == name).ToList(); 

這看起來和我的版本非常相似,並且會給出相同的結果,但是SQL查詢是完全不同的。你最終會像這樣的東西:

DECLARE @p__linq__0 NVarChar(1000) = 'bob' 
DECLARE @p__linq__1 NVarChar(1000) = 'bob' 

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    /* snip */ 
    FROM [dbo].[Users] AS [Extent1] 
    WHERE @p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1 

注意,空檢查數據庫現在做的,你也派參數兩次。好吧,它可能不會明顯變慢,但是這可能是您未來可能想要記住的。

+0

那怎麼樣!這正是我認爲不會工作的原因(我不知道延期執行)。謝謝你一堆 – jleach

+0

這是'IQueryable'接口(在EF的上下文中)非常有用的功能之一,你正在建立一個查詢(就像你將在以前建立一個SQL字符串一樣) – DavidG

+0

只是一個快速的解決方案,'var results = context.MyEntities'需要被具體聲明爲'IQueryable results = context.MyEntities'來表示適當的downcast,否則結果的類型是DbSet和後續的應用程序的應用程序Where結果失敗。 – jleach

3

你可以只檢查VM變量NULL裏,語句:

var results = context.Members 
    .Where(x => vm.Active == null || x.Active == vm.Active.Value) // but only if it's not null 
    .Where(x => vm.FirstName == null || x.FirstName == vm.FirstName) // but only if it's not null 
    .Where(x => vm.LastName == null || x.LastName == vm.LastName)  // but only if it's not null 
    .ToList(); 

在這種情況下,例如,如果vm.FirstNamenull,第一條語句vm.FirstName == null將是真實的(如果不爲空,那麼它實際上會檢查名字是否相等),以此類推其他條件。

+0

好吧,酷,不知道我可以在Where表達式中使用該語法。我會喜歡,但會給DavidG一個答案。謝謝! – jleach

+1

此方法的缺點是您要將空檢查發送到數據庫。 – DavidG

+0

PS我已經擴展我的答案,以顯示這樣做的副作用。 – DavidG

0

以下內容將過濾器合併到1 where子句中,但沒有太多需要,因爲過濾器返回的IQueryable將在開始枚舉結果時建立(並執行)(例如,通過調用ToList ())。

var results = context.Members 
        .Where(x => 
           (!vm.Active.HasValue() || x.Active == vm.Active.Value) 
          && (vm.FirstName == null || x.FirstName == vm.FirstName) 
          && (vm.LastName == null  || x.LastName == vm.LastName)) 
        .ToList(); 
+1

根據其他答案:這種方法的缺點是您要將空檢查發送到數據庫。 – DavidG

+0

PS我已經擴展我的答案,以顯示這樣做的副作用。 – DavidG