2017-12-10 22 views
2

을 IQueryable 출력 제공 LINQ 쿼리에 Func을 사용하는 방법 : 내 서비스 클래스에내 서비스에서 된 IQueryable을 반환하는 다음 쿼리 (간체 버전) 제공

var query = 
      (from item in _entityRepository.DbSet() 
      where 
       MyCondition 
      orderby Entity.EntityID descending 
      select new DTOModel 
      { 
       Id = Entity.EntityID, 

       ..., 

       //My problem is here, when I trying to call a function into linq query: 
       //Size = Entity.IsPersian ? (Entity.EntitySize.ConvertNumbersToPersian()) : (Entity.EntitySize) 

       //Solution (1): 
       //Size = ConvertMethod1(Entity) 

       //Solution (2): 
       //Size = ConvertMethod2(Entity) 
      }); 

또한 나는 다음과 같은 한 코드를 따라 나는이 다음과 같은 오류가 본

//Corresponding to solution (1): 
Func<Entity, string> ConvertMethod1 = p => (p.IsPersian ? p.EntitySize.ConvertNumbersToPersian() : p.EntitySize); 

//Corresponding to solution (2): 
Expression<Func<Entity, string>> ConvertMethod2 = (p) => (p.IsPersian ? p.EntitySize.ConvertNumbersToPersian() : p.EntitySize); 

그리고 :

솔루션에 해당하는

생성 오류 (1) :

내 쿼리

LINQ 표현식 노드 유형 'Invoke'는 LINQ to Entities에서 지원되지 않습니다. (2) 솔루션에 해당하는

생성 오류 :

그것의 컴파일 오류 : 방법, 위임 또는 이벤트가

감사 어떤 고급 도움을 많이 기대된다.

답변

1

이 진짜로으로 ORMs와 함께 IQueryable<>에 의해 노출 된 leaky abstraction이다.

메모리에서 실행할 때 작업을 가리키고 것이다 첫번째 시도; 그러나 ORM을 사용할 때는 그렇지 않습니다. Func<> 코드를 컴파일이라는 이유는 엔티티에 LINQ 작동하지 않습니다 귀하의 첫 번째 코드이다. SQL로 쉽게 변환 될 수있는 표현식 트리를 나타내지는 않습니다.

두 번째 시도

자연 시도 솔루션이지만, 때문에 식 트리에 코드의 다소 마법의 변환의 휴식. 선택을 쓰는 동안 Expression 개체에 대해 코딩하지 않습니다. 그러나 코드를 컴파일 할 때; C#은 자동으로이를 표현식 트리로 변환합니다. 불행하게도, 쉽게 믹스에 실제 Expression 항목을 가져올 수있는 방법은 없습니다.

는 당신이 필요로하는 것은 : 당신이 ORM에 떨어져 당신의 쿼리를 보내려고하는 경우에

  1. 자리 표시 기능이 발현
  2. 식 트리를 다시 작가에 대한 참조를 잡아.

당신이 당신의 쿼리와 끝까지하는 것은 무엇인가 같다 : 이것은 당신이 전에 식 트리 를 다시 쓸 수

public static class ExpressionExtensions 
{ 
    public static TT Inline<T, TT>(this Expression<Func<T, TT>> expression, T item) 
    { 
     // This will only execute while run in memory. 
     // LINQ to Entities/EntityFramework will never invoke this 
     return expression.Compile()(item); 
    } 

    public static IQueryable<T> ApplyInlines<T>(this IQueryable<T> expression) 
    { 
     var finalExpression = expression.Expression.ApplyInlines().InlineInvokes(); 
     var transformedQuery = expression.Provider.CreateQuery<T>(finalExpression); 
     return transformedQuery; 
    } 

    public static Expression ApplyInlines(this Expression expression) { 
     return new ExpressionInliner().Visit(expression); 
    } 

    private class ExpressionInliner : ExpressionVisitor 
    { 
     protected override Expression VisitMethodCall(MethodCallExpression node) 
     { 
      if (node.Method.Name == "Inline" && node.Method.DeclaringType == typeof(ExpressionExtensions)) 
      { 
       var expressionValue = (Expression)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke(); 
       var arg = node.Arguments[1]; 
       var res = Expression.Invoke(expressionValue, arg); 
       return res; 
      } 
      return base.VisitMethodCall(node); 
     } 
    } 
} 

// https://codereview.stackexchange.com/questions/116530/in-lining-invocationexpressions/147357#147357 
public static class ExpressionHelpers 
{ 
    public static TExpressionType InlineInvokes<TExpressionType>(this TExpressionType expression) 
     where TExpressionType : Expression 
    { 
     return (TExpressionType)new InvokeInliner().Inline(expression); 
    } 

    public static Expression InlineInvokes(this InvocationExpression expression) 
    { 
     return new InvokeInliner().Inline(expression); 
    } 

    public class InvokeInliner : ExpressionVisitor 
    { 
     private Stack<Dictionary<ParameterExpression, Expression>> _context = new Stack<Dictionary<ParameterExpression, Expression>>(); 
     public Expression Inline(Expression expression) 
     { 
      return Visit(expression); 
     } 

     protected override Expression VisitInvocation(InvocationExpression e) 
     { 
      var callingLambda = e.Expression as LambdaExpression; 
      if (callingLambda == null) 
       return base.VisitInvocation(e); 
      var currentMapping = new Dictionary<ParameterExpression, Expression>(); 
      for (var i = 0; i < e.Arguments.Count; i++) 
      { 
       var argument = Visit(e.Arguments[i]); 
       var parameter = callingLambda.Parameters[i]; 
       if (parameter != argument) 
        currentMapping.Add(parameter, argument); 
      } 
      if (_context.Count > 0) 
      { 
       var existingContext = _context.Peek(); 
       foreach (var kvp in existingContext) 
       { 
        if (!currentMapping.ContainsKey(kvp.Key)) 
         currentMapping[kvp.Key] = kvp.Value; 
       } 
      } 

      _context.Push(currentMapping); 
      var result = Visit(callingLambda.Body); 
      _context.Pop(); 
      return result; 
     } 

     protected override Expression VisitParameter(ParameterExpression e) 
     { 
      if (_context.Count > 0) 
      { 
       var currentMapping = _context.Peek(); 
       if (currentMapping.ContainsKey(e)) 
        return currentMapping[e]; 
      } 
      return e; 
     } 
    } 
} 

: 다음의 식 헬퍼와

Expression<Func<Person, int>> personIdSelector = person => person.PersonID; 

var query = Persons 
    .Select(p => 
    new { 
     a = personIdSelector.Inline(p) 
    }) 
    .ApplyInlines(); 

ORM에 도착하면 트리에 직접 표현식을 인라인 할 수 있습니다.

1

이 된 IQueryable 그냥 일반적인 SQL 명령 같은 데이터베이스에 대해 SQL 구문 및 쿼리를 생성하기 때문에, 그것은 것으로 이해된다. func를 SQL 명령으로 해석 할 수 없으므로 그렇게 할 수 없습니다. 필요한 최소한의 데이터를 쿼리하고, IQueryable이 아닌 결과를 가지도록 쿼리를 강제 실행 한 다음 func을 사용하여 다시 쿼리해야합니다. DB에서 한 번에 너무 많은 데이터를 쿼리하는 것을 방지하기 위해 func가 필요한 경우이를 다르게 해결하거나 사용자 지정 DB 메서드를 추가하는 것이 좋습니다. 그 주위에는 여러 가지 방법이 있지만 결론은 IQueryable이 DB에 대한 다중 호출을 방지하고 가능한 한 최적화 된 호출을하기위한 의도로 다르게 구축된다는 것입니다. 나는 당신이 SQL을 작성하는 방법을 안다면 확실하지 않다. 그렇다면 기본적으로 IQueryable 호출이 논리적으로 SQL처럼 작동하도록 고려해야합니다. 잘하면이 도움이됩니다.

+0

당신의 도움을 위해 @Michael Puckett II를 많이 주셔서 감사합니다. 그러나이 경우 ToArray() 또는 다른 Enummerable 가족 메소드 (ToArray() 등)를 사용할 수 없다고 말해야합니다. –