[英]Using C# function in Entity Framework Query
在我的C#代碼中,我有2個WHERE查詢,這兩個查詢都可以調用IQueryable並將整個對象編譯為SQL,並且兩個查詢都有很多通用邏輯。
我相信這不是類似問題的重復: 在Entity Framework Query的Select子句中使用Function,因為在我的場景中,有問題的函數可以轉換為SQL-EF只是沒有意識到它可以這樣做。
查詢大約是:
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<Template> set, User user)
{
return set.Where(temp =>
temp.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id));
}
和
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<DataReturn> set, User user)
{
return set.Where(ret=>
ret.Entity.Id == user.Entity.Id
&&
ret.Request.Template.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id));
}
因此,用於“擁有模板”的基本BusinessLogic規則,然后用於“如果公司匹配並擁有模板,則擁有DataReturn”的基本推論
如您所見,僅考慮C#,就可以輕松地將它們重構為:
private static bool UserOwnsTemplate(User user, Template temp)
{
return temp.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id);
}
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<Template> set, User user)
{
return set.Where(temp => UserOwnsTemplate(user, temp));
}
public static IQueryable<DataReturn> WhereIsOwnedByUser(this IQueryable<DataReturn> set, User user)
{
return set.Where(
ret =>
ret.Entity.Id == user.Entity.Id
&&
UserOwnsTemplate(user, ret.Request.Template)
);
}
從而減少重復(是!)
但是, EF會抱怨說,盡管它可以很好地處理SQL中的邏輯,但它不知道如何處理UserOwnsTemplate
。
AFAICT沒有解決此問題的好方法。 我認為我的選擇是:
UserOwnsTemplate
轉換為UDF,即數據庫中定義的SQL函數。
UserOwnsTemplate
定義的Expression<Func<Template,bool>>
UserOwnsTemplate
為變量,然后使用Expression.AndAlso
手工為DataReturn版本構建相關的Expression<Func<DataReturn ,bool>>
,並將兩個“子句”粘合在一起。
誰能看到其他可用的選項嗎?
我可以做些什么來強迫EF將函數解析為SQL嗎? (我想到“內含”一詞,但我不是100%知道我的意思嗎?)
誰能看到將ret.Request.Template轉換為IQueryable的方法,以便我可以在其上調用其他WhereIsOwnedBy擴展方法?
還有其他建議嗎?
您可以保留語法並使其起作用,但是您需要在外部IQueryable <>上調用其他方法。
訣竅是用一個副本手動替換IQueryable <>。Expression,在副本中您用相應的Expression>替換函數調用。
因此,想法是做這樣的事情:
public static class MyLinqExtensions
{
public static IQueryable<T> InlineFunctions<T>(this IQueryable<T> queryable)
{
var expression = TransformExpression(queryable.Expression);
return (IQueryable<T>)queryable.Provider.CreateQuery(expression);
}
private static Expression TransformExpression(System.Linq.Expressions.Expression expression)
{
var visitor = new InlineFunctionsExpressionVisitor();
return visitor.Visit(expression);
}
private class InlineFunctionsExpressionVisitor : System.Linq.Expressions.ExpressionVisitor
{
protected override System.Linq.Expressions.Expression VisitMethodCall(System.Linq.Expressions.MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.IsStatic
&& methodCallExpression.Method.DeclaringType == typeof(MyDeclaringType)
&& methodCallExpression.Method.Name == "WhereIsOwnedByUser")
{
var setArgumentExpression = methodCallExpression.Arguments[0];
var userArgumentExpression = methodCallExpression.Arguments[1];
var methodInfo = ... // Get typeof(IQueryable<Template>).MethodInfo
var whereConditionExpression = ...// Build where condition and use userArgumentExpression
return Expression.MethodCallExpression(methodInfo, setArgumentExpression, whereConditionExpression);
}
return base.VisitMethodCall(methodCallExpression);
// Some ideas to make this more flexible:
// 1. Use an attribute to mark the functions that can be inlined [InlinableAttribute]
// 2. Define an Expression<Func<>> first to be able to get the Expression and substritute the function call with it:
// Expression<Func<IQueryable<Template>, User, IQueryable<Template>>> _whereIsOwnedByUser = (set, user) =>
// {
// return set.Where(temp => UserOwnsTemplate(user, temp));
// };
//
// public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<Template> set, User user)
// {
// // You should cache the compiled expression
// return _whereIsOwnedByUser.Compile().Invoke(set, user);
// }
//
}
}
}
然后您可以執行以下操作:
public static IQueryable<DataReturn> WhereIsOwnedByUser(this IQueryable<DataReturn> set, User user)
{
return set.Where(
ret =>
ret.Entity.Id == user.Entity.Id
&&
UserOwnsTemplate(user, ret.Request.Template)
)
.InlineFunctions();
}
問題是您的方法成為了表達式樹的一部分,而EF無法對其求值。 原則上,可以在觸發查詢之前評估表達式樹的各個部分。 看看Re-Linq: https ://relinq.codeplex.com/它有一個PartialEvaluatingExpressionTreeVisitor類,可以評估所有部分表達式樹,即它將找到您的方法,對其進行評估並注入實際的表達式樹。 這將付出一定的性能成本,但可能並不重要,您必須評估干凈的設計與性能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.