2016-10-06 5 views
2

SQL 그룹화를 수행하기 전에 UTC 시간을 로컬로 이동하는 방법을 찾으려고합니다. System.Linq.Dynamic (여기서 관리합니다 https://github.com/kahanu/System.Linq.Dynamic)을 사용하고 있습니다. 컴파일시 필요한 필드없이 동적 선택을하는 데 효과적입니다. 여기서는 모든 datetimes를 UTC로 저장합니다. 이 동적 선택에서 누군가가 시간, 년, 월 등에 groupby를하고 싶을 가능성이 있습니다. 혼란을 피하기 위해이 경우 현지 시간으로 데이터를 이동해야합니다.동적 Linq + 엔티티 프레임 워크 : 동적 선택에 대한 datetime 수정

예 :

var select = queryable.Select(string.Format("new ({0}, {1})", datetimeColumn, someOtherColumn)); 

일반적으로 우리 TSQL에서 또는 람다 표현식을 사용하여 엔티티 프레임 워크, 당신은 오프셋 원하는 당신에 추가 할 수 있습니다. 그러나 동적 linq 옵션에서는 Linq2Sql과 같이 DateTime.AddHours (x) 또는 DateTime.Subtract (x)와 같은 날짜 연산을 수행 할 수없는 것으로 보입니다. Entity Framework 6은 DbFunctions.AddHours(x) 등을 사용하기를 원합니다. 그러나 수정하지 않고 동적 linq 코드는 오류없이 DbFunctions를 허용하지 않습니다.

예 :

var select = queryable.Select(string.Format("new (System.Data.Entity.DbFunctions.AddHours({0},7) as {0}, {1})", datetimeColumn, someOtherColumn)); 

오류를 반환 : 없음 속성 또는 필드 '시스템'형 XXX에 존재하지 않는
(네임 스페이스를 제거하는 것은 도움이되지 않습니다). 원하는 코드를 사용

: 오류와

var select = queryable.Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn)); 

결과 : 방법 'System.DateTime있는 AddHours (더블)'방법 및이 방법을 인식하지 못하는 LINQ 엔티티에이 가게 표현으로 변환 할 수 없습니다.

SQL에 groupby보다 먼저 datetime 연산을 수행하게하고 싶습니다. groupby가 발생하면 사용자가 지역화 된 결과 집합을 볼 때 더 이상 UTC 개념이 없습니다.

엔티티 프레임 워크 확장을 전달하기 위해 github 포크를 일부 확장으로 업데이트하는 것이 좋지 않을까 싶습니다.하지만 그 전에는 다른 사람에게 솔루션이나 아이디어가 있는지 확인하고 싶었습니다.

참고 : SQL 데이터 저장소 기술을 변경할 가능성이 있으므로 DateTimeOffset을 사용하지 않습니다.

답변

5

사용자 지정 ExpressionVisitor을 사용하여 쿼리 식을 게시하고 지원되지 않는 메서드를 DbFunctions 개의 해당하는 것으로 바꿀 수 있습니다.

public static class QueryableExtensions 
{ 
    public static IQueryable BindDbFunctions(this IQueryable source) 
    { 
     var expression = new DbFunctionsBinder().Visit(source.Expression); 
     if (expression == source.Expression) return source; 
     return source.Provider.CreateQuery(expression); 
    } 

    class DbFunctionsBinder : ExpressionVisitor 
    { 
     protected override Expression VisitMethodCall(MethodCallExpression node) 
     { 
      if (node.Object != null && node.Object.Type == typeof(DateTime)) 
      { 
       if (node.Method.Name == "AddHours") 
       { 
        var timeValue = Visit(node.Object); 
        var addValue = Visit(node.Arguments.Single()); 
        if (timeValue.Type != typeof(DateTime?)) timeValue = Expression.Convert(timeValue, typeof(DateTime?)); 
        if (addValue.Type != typeof(int?)) addValue = Expression.Convert(addValue, typeof(int?)); 
        var methodCall = Expression.Call(
         typeof(DbFunctions), "AddHours", Type.EmptyTypes, 
         timeValue, addValue); 
        return Expression.Convert(methodCall, typeof(DateTime)); 
       } 
      } 
      return base.VisitMethodCall(node); 
     } 
    } 
} 

및 샘플 사용 :

var select = queryable 
    .Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn)) 
    .BindDbFunctions(); 
+3

환상적인 솔루션 여기

은 아이디어를 얻을 수있는 출발점이 될 것입니다. 고마워! 표현을 더 잘 이해하고 새로운 것을 배웠습니다. 우수한 솔루션과 바로 상자에서 일했습니다. 내가 원하는 약간의 변경을했지만 DbFunctions를 사용할 수있었습니다. 이것은 또한 다른 잠재적 인 사용법을 잠금 해제합니다. 이 답변이 앞으로 다른 사람을 도울 수 있기를 바랍니다. – TravisWhidden

+0

이반, 혹시이 질문을 볼 수 있니? 유행에서 유사하게, 다만 실시에 막히십시오. http://stackoverflow.com/questions/40271588/entity-framework-dayofweek – TravisWhidden

+1

예, 저기 있습니다 :) –