2017-10-12 107 views
4

我想使用LinqKit的PredicateBuilder並將謂詞傳遞給.Any方法以獲得相關模型。使用LinqKit PredicateBuilder進行相關模型(EF Core)

所以我想建立一個斷言:

var castCondition = PredicateBuilder.New<CastInfo>(true); 

if (movies != null && movies.Length > 0) 
{ 
    castCondition = castCondition.And(c => movies.Contains(c.MovieId)); 
} 
if (roleType > 0) 
{ 
    castCondition = castCondition.And(c => c.RoleId == roleType); 
} 

,然後用它來篩選具有關係模型在謂詞型號:

IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition)); 
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync(); 

但是這會導致System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

我看到similar question,並且回答那裏建議使用.Compile。或構建額外謂詞的one more question

所以我試圖用額外的謂詞

var tp = PredicateBuilder.New<Name>(true); 
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile())); 
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp); 

或者使用編譯直接

IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile())); 

但我對編譯錯誤:System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'

所以是有可能的結果轉換從PredicateBuilder進入Any

注意:我能夠構建所需的行爲組合表達式,但我不喜歡我需要額外的變量。

System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true); 
if (movies != null && movies.Length > 0) 
{ 
    castExpression = (c => movies.Contains(c.MovieId)); 
} 
if (roleType > 0) 
{ 
    var existingExpression = castExpression; 
    castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType; 
} 
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile())); 
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync(); 

所以我想我只是想念一些關於建設者。

更新有關版本:我用的dotnet核心2.0和LinqKit.Microsoft.EntityFrameworkCore 1.1.10

回答

4

看代碼,一個將假定castCondition變量的類型是Expression<Func<CastInfo, bool>>(因爲它是在前面版本爲PredicateBuilder)。

但是,如果是這樣的話,那麼n.CastInfo.Any(castCondition)甚至不應該編譯(假設CastInfo是一家集導航屬性,所以編譯器會打Enumerable.Any其預計Func<CastInfo, bool>,不Expression<Func<CastInfo, bool>>)。那麼這裏發生了什麼?

在我看來,這是C#隱式操作符濫用的一個很好的例子。該PredicateBuilder.New<T>方法實際上返回一個名爲ExpressionStarter<T>類,它有很多方法模擬Expression,但更重要的是,有轉換爲Expression<Func<T, bool>>Func<CastInfo, bool>。後者允許該類用於頂層Enumerable/Queryable方法作爲替換各自的lambda func/expression。但是,它也可以防止在表達式樹中使用編譯時錯誤 - 編譯器會發出類似n.CastInfo.Any((Func<CastInfo, bool>)castCondition)的東西,這在運行時會導致異常。

LinqKit AsExpandable方法的整個想法是允許通過自定義Invoke擴展方法「調用」表達式,然後在表達式樹中「擴展」方法。因此,回到開頭,如果變量類型爲Expression<Func<CastInfo, bool>>,使用目的是:

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c))); 

但現在,這並不編譯,因爲的理由如前所述。所以,你必須先將其轉換爲Expression<Func<T, bool>外查詢的

Expression<Func<CastInfo, bool>> castPredicate = castCondition; 

然後用

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c))); 

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile())); 

爲了讓編譯器推斷表達式類型,我會創建一個像這樣的自定義擴展方法:

using System; 
using System.Linq.Expressions; 

namespace LinqKit 
{ 
    public static class Extensions 
    { 
     public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr; 
    } 
} 

,然後簡單地使用

var castPredicate = castCondition.ToExpression(); 

它仍然有許多工作要做查詢,即下面不工作:

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c))); 
+2

感謝詳細的解釋。我試過你的代碼,不幸的是我有一個'LINQ表達式'的警告,其中__castCondition_0.Invoke([c])'不能被翻譯,並且會在本地進行評估。'所以,當它編譯並運行時,條件不會被添加到SQL和查詢選擇所有行。你有什麼建議嗎? – Igor

+0

其實你提到'ExpressionStarter'可以用於'Expression'的施法者。所以這工作'var castExpr =(Expression >)castCondition; context.Name.AsExpandable()。其中​​(n => n.CastInfo.Any(castExpr.Compile()))。Count()';內聯變量不起作用出於某種原因 – Igor

+0

內聯不起作用,因爲「擴展器」無法識別「ExpressionStarter.Compile()」。聽起來像不完整的LinqKit工作:( –

相關問題