2012-01-19 15 views
2

首先:我們是而不是正在執行TPH(每個層次的表),這將使這更簡單很多。在SQL Server上過濾實體框架結果

我有大約20個POCO在某些情況下都具有相似的屬性。我關心的類似房產是___.CreatedDate___.UpdatedDate。對於一些POCO,UpdatedDate沒有意義,所以他們沒有這個屬性。 CreatedDate將始終存在,UpdatedDate可能爲空。有時候還有第三個領域。

無論我正在查詢的20個POCO中的哪一個,從數據庫中檢索對象的查詢總是看起來相同。但是,查詢重複了20次,每種類型的對象都有一次。我能夠將檢索部分分解爲擴展方法(關閉IDbSet<T>)以執行初始查找和連接,將日期範圍篩選作爲鍛鍊留給消費者。我現在想要將20個幾乎完全相同的日期範圍過濾器合併爲一個,但遇到查詢理解問題。

每個POCO的日期過濾邏輯是應該檢查UpdatedDate,並將其值與閾值進行比較。如果UpdatedDate爲null(或者該屬性不存在),則應該使用CreatedDate。

< /背景>

我開始通過創建每個POCO,我命名爲 「DateFields」 靜態屬性獲取。它的類型爲,看起來像這樣:

get 
{ 
    yield return x => x.UpdatedDate; 
    yield return x => x.CreatedDate; 
    yield return x => x.Date; 
} 

這些字段中,我希望他們檢查,並各自獨有的POCO順序返回。

然後,我創建了一個謂詞來對抗高和低量程檢查每個值:

public static bool DatesBetween<T> (T value, IEnumerable<Expression<Func<T, DateTime?>>> dates, DateTime? dateFrom, DateTime? dateTo) 
{ 
    var firstDate = dates 
     .Select(expression => expression.Compile()) 
     .FirstOrDefault(func => func(value) != null); 

    if (firstDate == null) 
     return false; 

    var minDate = dateFrom.GetValueOrDefault(SqlDateTime.MinValue.Value); 
    var maxDate = dateTo.GetValueOrDefault(SqlDateTime.MaxValue.Value); 

    var actualDate = firstDate(value); 

    return (minDate <= actualDate && actualDate <= maxDate); 
} 

然而,正如所料,當我嘗試在我的IQueryable利用這一點,我得到一個運行時異常,因爲有沒有翻譯到SQL中的DatesBetween。我希望通過保留表達樹,我可以......我不知道......讓EF快樂。整個事情是這樣使用:

return Entities 
    .GetEntitiesBySomeField(field := "magic value") 
    .Where(entity => RepositoryExtensions.DatesBetween(entity, MyPOCO.MyDates, dateFrom, dateTo)); 

是我要問清楚,有沒有辦法來完成這種通用的過濾,而不做一個AsEnumerable()(它的工作,但不完成我在數據庫上過濾的目標)。

回答

1

我最近在一個項目中做了類似的事情。我最終什麼事做是實現存儲庫模式,並創建了名爲「ApplyFilter」的寶庫,它看起來是這樣的方法:

void ApplyFilter(Expression<Func<TEntity, bool>> predicate) 
{ 
    if(this.resultSet == null) 
    { 
     this.resultSet = this.context.Set<TEntity>().AsQueryable(); 
    } 

    this.resultSet = this.resultSet.Where(predicate); 
} 

而且對我的倉庫被稱爲「結果集」剛剛返回的IQueryable的<屬性TEntity > resultSet字段並強制枚舉結果(觸發EF的數據庫調用)。通過這種方式,我可以在枚舉結果之前將所有動態創建的表達式應用於乾淨的原因。

我的代碼和你所做的事情之間的真正區別是我的代碼將布爾謂詞追加到最終的表達式樹中,因爲你正試圖在最終表達式樹中使用你的。我的方式基本上創建/附加一個WHERE子句的SQL,你試圖從「DatesBetween」方法生成一個SQL函數調用。

下面是一些嘗試:

public static Expression<Func<TEntity, bool>> MakeDateRange<TEntity>(DateTime? dateFrom, DateTime? dateTo) 
    { 
     var et = typeof(TEntity); 
     var param = Expression.Parameter(et, "a"); 
     var prop = et.GetProperty("UpdatedDate"); 
     Expression body = null, left = null, right = null; 
     if (prop == null) 
     { 
      prop = et.GetProperty("CreatedDate"); 
      if (prop == null) 
      { 
       prop = et.GetProperty("Date"); 
      } 
     } 

     if (dateFrom.HasValue) 
     { 
      left = Expression.GreaterThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateFrom.GetValueOrDefault())); 
     } 

     if (dateTo.HasValue) 
     { 
      right = Expression.LessThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateTo.GetValueOrDefault())); 
     } 

     if (left != null && right != null) 
     { 
      body = Expression.AndAlso(left, right); 
     } 
     else if (left != null) 
     { 
      body = left; 
     } 
     else 
     { 
      body = right; 
     } 

     return Expression.Lambda<Func<TEntity, bool>>(body, param); 
    } 
+0

我可以改變實施。我試圖用我的'DatesBetween'方法來構建SQL。問題是我的謂詞比正常情況更復雜,因爲它的條件是基於哪些屬性存在或不存在。我一直沒有想出一種方法來通用化這個決定,而沒有EF對此做出反應。 –

+0

我在回答中添加了一個代碼塊,它可以解決您的問題。它使用反射檢查類型上是否存在屬性。 – doogle

0

所以,如果我明白,你想能夠在多個具有相似結構的表上運行相同的查詢?

如果是這種情況,請考慮創建一個定義這些屬性的接口,並讓您的POCO實現該接口。然後讓你的通用參數對該接口有一個限制。從那裏,你應該能夠編寫一個使用接口屬性的通用查詢。

+0

,在情況下工作,其中同名波蘇斯共享屬性。問題在於,字段名稱經常會有細微的差別(或者在某些情況下缺失) - 實體會拋出異常。 –