2011-03-16 66 views
3

我想知道最可維護的方法(如果存在)將實體條件存儲在參數中,以便在條件中的每個linq中重用它。如何存儲Linq條件屬性

假設有一個產品實體:

public class Product 
{ 
    public bool IsDiscontinued { get; set; } 

    public int UnitsInStock { get; set; } 
} 

我想添加到產品類,它包含的邏輯,以確定該產品是否有售或不是屬性IsOnSale。 在這個簡單的例子中,邏輯可以是:

IsOnSale = IsDiscontinued == false && UnitsInStock > 0 

然後我應該能夠寫這種類型的LINQ查詢:

from p in context.Products 
where p.IsOnSale == true 
select p 

溶液的目的應該是,如果在未來的邏輯來確定產品是否出售或不變(例如添加屬性IsBackOrderAllowed),我不必編輯任何地方的linq查詢,而只需更改IsOnSale屬性。

類似的問題已發佈here但似乎解決了一個更具體的問題。

回答

1

你可以把它返回的條件篩選一個IQueryable的方法:

public IQueryable<Product> WhereOnSale(this IQueryable<Product> source) 
{ 
    return source.Where(p => p.IsDiscontinued == false && p.UnitsInStock > 0); 
} 

,你會使用這樣的:如果你想做到這一點的

from p in context.Products.WhereOnSale() 
select p 

在Yakimych的回答中有這樣的表達,那麼這將起作用:

Expression<Func<Product, bool>> isOnSale = 
(p => p.IsDiscontinued == false && p.UnitsInStock > 0); 

,你可以使用它像這樣:

context.Products.Where(isOnSale) 

,它被聲明爲一個表達式,否則實體框架將不能夠把它翻譯成SQL,因爲拉姆達會被編譯成IL相反,它是重要的表達式樹。

+0

+1 IMO,擴展方法是一個更優雅的解決方案。 – Yakimych 2011-03-16 11:04:21

+0

我同意,我更喜歡使用與表達式相反的方法。你可以通過表達式來獲得更多,但它們往往會掩蓋代碼的含義。 – 2011-03-16 11:33:38

+0

表達式解決方案似乎更通用(在撰寫更復雜的查詢時)。但我會用擴展方法解決一些更多的測試,也許我會理解爲什麼它更好... – EdoT 2011-03-16 11:45:48

2

你可以做這樣的事情:

Func<Product, bool> isOnSaleFunc = 
          (p => p.IsDiscontinued == false && p.UnitsInStock > 0); 
在查詢

然後你做:

context.Products.Where(isOnSaleFunc) 

更新

與@DoctaJonez評論,討論的結果 - 這種方法的過濾將在客戶端進行(這是或當然效率低下),因此Expression<Func<Product, bool>> s應該用來代替Func<Product,bool>

+2

這會在查詢時失敗,因爲如果將聲明從Func 更改爲Expression >,它將無法將Func轉換爲SQL表達式。 – 2011-03-16 10:41:23

+0

@DoctaJonez - 不,它的工作方式非常完美,只是測試它。顯然EF足夠聰明,不會嘗試翻譯'isOnSaleFunc',而是事先分析,然後開始構建SQL表達式。 – Yakimych 2011-03-16 10:49:36

+0

雖然它不會在查詢時完成它,但它不可能。運行它LINQPad並檢查生成的SQL。如果您將它聲明爲Func,它將不包含您的where子句,因爲EF無法解析Func,它必須是表達式才能工作。 – 2011-03-16 10:51:35

0

我認爲你正在尋找規範模式。

有關將EF與EF一起使用的文章,請參閱http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/

如何實現,這將是一個例子,讓您的查詢

from p in context.Products 
where ProductSpecifications.IsOnSale.Predicate 
select p 

,並使用下面的幫助和規範的定義。

public static class ProductSpecifications{ 
    public static readonly Specification<Product> IsOnSale = new Specification<Product>(x => !x.IsDiscontinued && x.UnitsInStock > 0); 
} 

public class Specification<TEntity> : ISpecification<TEntity> 
{ 
    public Specification(Expression<Func<TEntity, bool>> predicate) 
    { 
     _predicate = predicate; 
    } 

    public bool IsSatisfiedBy(TEntity entity) 
    { 
     return _predicate.Compile().Invoke(entity); 
    } 

    private Expression<Func<TEntity, bool>> _predicate; 

    public Expression<Func<TEntity,bool>> Predicate{ 
     get{ return _predicate; } 
    } 
} 

您也可以使用這種模式做更多的事情,所以我建議您考慮一下!

+0

看起來很有希望,我一定會深入瞭解一下。對於簡單的「規格」來說,這可能很複雜。 – EdoT 2011-03-16 12:00:32

+0

這不是那麼複雜,但你可以做'Expression > IsOnSale = product =>!product.IsDiscontinued && product.UnitsInStock> 0' – smartcaveman 2011-03-16 12:06:48

1

這裏的第一個問題是,linq實體不能用於不屬於模型一部分的屬性(=無法使用自定義屬性)。

您必須定義表達式。

var query = context.Products.Where(Product.IsOnSale); 

另一種方法是使用model defined function:如果只定義Func它將作爲LINQ的被執行對象:

public class Product 
{ 
    ... 

    public static Expression<Func<Product, bool>> IsOnSale 
    { 
     get 
     { 
      return p => (p.IsDiscontinued == false && p.UnitsInStock > 0); 
     } 
    } 
} 

現在你必須調用查詢這個樣子。