2015-10-14 2 views
8

В моем коде C# у меня есть 2 запроса WHERE, оба из которых я могу вызвать IQueryable, и все это скомпилировано до SQL, и у обоих из них есть много общей логики ,Использование функции C# в Entity Framework Query

Я считаю, что это не дублирование этого похожего вопроса: Using Function in Select Clause of Entity Framework Query, потому что в моем сценарии функция, о которой идет речь, может быть преобразована в 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) 
    ); 
} 

Таким образом, сокращение дублирования (Yay!)

Но то EF будет жаловаться, что он не знает, что делать с UserOwnsTemplate, несмотря на то, что он может справиться с логикой в ​​SQL отлично.

AFAICT нет прекрасного способа решить эту проблему. Я думаю мои варианты:

  • Включите UserOwnsTemplate в UDF, функцию SQL, определенную в базе данных.
    • Но я не могу создать UDF из C# lamda, я должен определить SQL, который будет более сложным.
  • Присвоить Expression<Func<Template,bool>>, что UserOwnsTemplate определяет в качестве переменной, а затем построить соответствующую Expression<Func<DataReturn ,bool>> для версии DataReturn вручную, используя Expression.AndAlso склеить два «положения» вместе.
    • Мета-программирование. Ughhh. Я сделал это раньше в другом проекте, и это было противно, и кошмар для поддержания.
  • Live с дублированием.
    • Вероятно, что произойдет, если ТО не сообщит иначе. ;)

Можно ли увидеть какие-либо другие варианты, доступные?

Могу ли я что-либо сделать, чтобы заставить EF анализировать функцию в SQL? (на ум приходит фраза «inling», но я не знаю, что я думаю об этом?)

Может ли кто-нибудь увидеть способ преобразования ret.Request.Template в IQueryable, чтобы я мог просто назовите другой метод расширения WhereIsOwnedBy?

Любые другие предложения НА ВСЕ?

+1

Что касается вашего 2-ого пункта пули, я думаю, что немного AndAlso не требует каких-либо пререкание дерева выражений. Вы всегда можете выполнить 'set.Where (выражение) .Where (anotherExpression)'. Трудность возникает из-за того, что ваше выражение зависит от пользователя и что вы хотите применить его к другому пути от корня IQueryable (temp vs. ret.Request.Template). Я не вижу, как обращаться с ними, не создавая собственного выражения. Возможно, есть и другой способ, но я ожидаю, что это приведет к существенному изменению запросов, которые вы делаете. – hgcummings

+0

Дубликат http://stackoverflow.com/questions/7066305/how-to-stay-dry-whilst-using-linq-to-entities-and-helper-methods? – Dan

ответ

0

Проблема заключается в том, что ваш метод становится частью дерева выражений и что EF не может его оценить. В принципе, вы можете оценить части дерева выражений, прежде чем запускать запрос. Взгляните на Re-Linq: https://relinq.codeplex.com/ Он имеет класс PartialEvaluatingExpressionTreeVisitor, который может оценивать все деревья частичного выражения, то есть он найдет ваш метод, оценит его и введет фактическое дерево выражений. Это произойдет с определенной производительностью, но это может быть не очень важно, и вам придется оценивать чистый дизайн и производительность.

1

Вы можете сохранить свой синтаксис и заставить его работать, но вам нужно будет вызвать дополнительный метод на внешнем IQueryable <>.

Трюк заключается в том, чтобы вручную заменить IQueryable <> .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(); 
} 
+0

Некоторые сорта: с картой AOP (шаг предварительной сборки, который переписывает часть кода), тогда стоимость выполнения будет равна нулю. Вы просто отметите свои функции атрибутом, и AOP заменит вызов функции содержимым функции. Слишком плохо Roslyn поддерживает только перезапись времени разработки: http://stackoverflow.com/questions/7833954/can-i-use-roslyn-for-compile-time-code-rewriting – Guillaume86

Смежные вопросы