티스토리 뷰
					.NET/C#
					
				Custom LINQ Provider - [3]. Custom LINQ Provider 만들기 (IQueryProvider)
POWERUMC 2008. 3. 17. 01:37지난 시간에 이어, 이번 시간에는 실제 Provider 를 구현해 보도록 하겠습니다. 지난 시간에 언급하였듯이, 실제 쿼리식을 해석하고, 동작이 가능한 형태로 바꾸는 작업을 아래의 Provider 에서 할 수 있습니다.
IQueryProvider
실제로 외부 서버나 서비스 등에 필요한 쿼리를 만들 수 있는 인터페이스입니다. 이 인터페이스는 4개의 메서드를 지원합니다. 중복된(제너릭/비제너릭) 메서드를 제외하면 2개의 메서드만 제대로 구현하시면 됩니다.
우선 소스부터 나갑니다.
| 
 public class SampleProvider : IQueryProvider 
{ 
       private SampleContext context; 
       public SampleProvider(SampleContext context) 
       { 
             this.context = context; 
       } 
       #region IQueryProvider Members 
       public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 
       { 
             return (IQueryable<TElement>)context; 
       } 
       public IQueryable CreateQuery(Expression expression) 
       { 
             throw new NotImplementedException(); 
       } 
       public TResult Execute<TResult>(Expression expression) 
       { 
             var exp             = expression as MethodCallExpression; 
             var func     = (exp.Arguments[1] as UnaryExpression).Operand as Expression<Func<Person, bool>>; 
             var lambda   = Expression.Lambda<Func<Person, bool>>(func.Body, func.Parameters[0]); 
             var r = context.DataSource.Where(lambda.Compile()); 
             return (TResult)r.GetEnumerator(); 
       } 
       public object Execute(Expression expression) 
       { 
             throw new NotImplementedException(); 
       } 
       #endregion 
}  | 
소스에는 특별한 주석은 없으며, 차근차근 서술식으로 의문을 풀어드리도록 하겠습니다.
Constructor
이전에 봤던 SampleContext 객체에서 Provider 프로퍼티를 보셨을 겁니다. 프로퍼티는 다음과 같이 정의되어 있습니다.
public IQueryProvider Provider
{
        get 
        {
               if( provider == null )
                       provider = new SampleProvider(this);
               return provider;
        }
}
SampleProvider 에서 SampleContext 에 정의된 DataSource 나, 다음 회차에서 볼 원격 탐색을 하거나, 로그를 남길 수 있도록 하기 위해 SampleContext 를 참조해야 합니다.
CreateQuery<T> Method
이 메서드는 Expression, 즉 표현식의 인자를 받는 메서드입니다. IQueryable<TElement> 를 리턴하며, TElement 의 타입은 Person 이 되겠습니다.
context.expression = expression;
return (IQueryable<TElement>)context;
와 같이 매우 보잘 것 없는 내용으로 구현되었습니다. 하지만, 원격 개체에 연결하기 위한 쿼리를 만들기 위해서 이곳에서 실제 SQL 쿼리의 “SELECT” 구문과 같은 쿼리를 만드시면 됩니다.
Execute<T> Method
쿼리식을 이곳에서 해석해서 실행합니다. SampleContext 객체에서 GetEnumerator 메서드는
public IEnumerator<Person> GetEnumerator()
{
        return provider.Execute<IEnumerator<Person>>(this.expression);
}
보시는 것과 같이, SampleProvider 의 Execute 메서드를 호출합니다. 리턴 타입이 IEnumerator<Person> 인 것을 미루어 보아, 쿼리 호출 후 반환되는 결과값을 리턴하는 것이라는 것을 짐작할 수 있습니다.
여기에서도 어김없이 몇 가지 재미있는 표현식(Expression) 이 나옵니다.
먼저, MethodCallExpression 을 보겠습니다. 이 Expression 은 쿼리식에서 호출한 메서드를 가져오는 녀석입니다. 아래의 샘플을 보겠습니다.
Expression<Func<string,string,bool>> func = (s1,s2) => ( s1.Substring(0,2) == s2 );
BinaryExpression be        = func.Body as BinaryExpression;
MethodCallExpression me    = be.Left as MethodCallExpression;
Console.WriteLine(me.Method.Name);
이항 연산식의 “s1.Substring(0,2) == s2” 의 좌측식 부분을 가져와서 이것을 MethodCallExpression 으로 변환하여 실행 메서드 내용을 가져옵니다. 감이 잡히시나요?
답은
| 
 Substring  | 
이됩니다.
이런 방법으로 실제 메서드를 원격 서버의 쿼리식에 맞도록 변환 할 수 있습니다. 
위의 func 를 아래와 같이 바꾸어
Expression<Func<string,string,bool>> func = (s1,s2) => ( s1.ToUpper() == s2 );
결과는 “ToUpper” 가 출력 되지만, MSSQL 쿼리로 “UPPER” 로 변환 하는 것과 같이 쿼리식을 자유자제로 변형 할 수 있습니다.
그럼, 다음으로 나오는 UnaryExpression 을 보겠습니다.이 표현식은 단항 연산자가 있는 식을 나타냅니다. bool 연산식에서 부정 NOT 등과 같이 하나의 피연산자를 사용하는 bool 연산자입니다.
들어가기 전, 문제를 하나내겠습니다. UnaryExpression 는 단항 연산자를 나타내는 표현식 클래스입니다. 그럼, 이항 연산자를 나타내는 클래스는??
그렇습니다. 이항 연산을 나타내는 클래스는 BinaryExpression 입니다. 후후,,, 기억하고계시는군요.
다음은 UmcBlog 의 Article 테이블입니다.
UmcBlogDataContext db      = new UmcBlogDataContext();
var query    = from article in db.Articles
              where article.Title.Contains("Umc")
              select article;
MethodCallExpression me    = query.Expression as MethodCallExpression;
UnaryExpression ue         = me.Arguments[1] as UnaryExpression;
Console.WriteLine(me.Method.Name);
Console.WriteLine(ue.Operand);
이것의 결과는 아래와 같습니다.
| 
 Where 
article >= article.Title.Contains(“Umc”)  | 
전 위의 UnaryExpression 의 결과가 이렇게 나오는지 잘 이해가 가지 않습니다. 분명 단항 연산자는 피연산자가 하나일 경우를 일컷습니다. UnaryExpression.Operand 속성은 단항 연산의 피연산자를 가져온다고 했는데, 위의 결과는 피연산자가 LambdaExpression 을 말하는 것 같군요. 피연산자를 LambdaExpression 의 대리자인 “article” 을 일컷는 것이라 생각되지만 확실히 감이 서질 않습니다. 누가 아시는 분 답변 좀 부탁합니다ㅡ,.ㅡ;
LambdaExpression
이것은 우리가 흔히 사용하는 바로 그 “람다식”을 표현하는 Expression 입니다. 여러가지 Expression 을 조합해서 약간의(?) 동적으로 람다식을 만들어 낼 수도 있습니다. (준비된 표현식을 이용하여…)
Expression<Func<string,string,string>> func   = ( s1, s2 ) => ( s1 + s2 );
var le       = Expression.Lambda(func.Body, func.Parameters.ToArray());
var result1 = func.Compile();
var result2 = le.Compile();
Console.WriteLine( result1("Umc","Blog" ) );
Console.WriteLine( result2.DynamicInvoke("Umc","Blog") );
func 는 표현식을 명시적으로 컴파일되며, le 는 표현식을 이용해 람다식을 만든것입니다.
위에 보이는 Compile() 메서드를이용하여 IL 코드로변환하게됩니다.
| 
 IL (Intermediate Language)코드란? 
.NET 에서 MSIL 이라고도 부릅니다. 컴파일러에 의해 실행이 가능하도록 중간 언어로 컴파일 된 것을 말합니다. 비로소, IL 코드는 .NET Framework의 CLR(Common Language Runtime) 의 JIT(Just-In-Time) 컴파일러에 의해 Native 코드로 컴파일이 됩니다.   | 
Compile() 메서드는 아래와 같이 생겼답니다.
internal Delegate Compile(LambdaExpression lambda)
{
    this.lambdas.Clear();
    this.globals.Clear();
    int num2 = this.GenerateLambda(lambda);
    ExpressionCompiler.LambdaInfo info2 = this.lambdas[num2];
    ExpressionCompiler.ExecutionScope scope2 =
        new ExpressionCompiler.ExecutionScope(
                      null,
                      lambda,
                      this.lambdas.ToArray(),
                      this.globals.ToArray(),
                      info2.HasLiftedParameters);
    return info2.Method.CreateDelegate(lambda.Type, scope2)
}
private int GenerateLambda(LambdaExpression lambda)
{
    this.scope = new ExpressionCompiler.CompileScope(this.scope, lambda);
    DynamicMethod method2 = new DynamicMethod(“lambda_” + ExpressionCompiler.iMethod++,
                                              lambda.Body.Type,
                                              this.GetParameterTypes(lambda),
                                              typeof(ExpressionCompiler.ExecutionScope),
                                              true);
    ILGenerator generator2 = method2.GetILGenerator();
    this.GenerateInitLiftedParameters(generator2);
    this.Generate(generator2, lambda.Body, ExpressionCompiler.StackType.Value);
    generator2.Emit(OpCodes.Ret);
    int num2 = this.lambdas.Count;
    this.lambdas.Add(new ExpressionCompiler.LambdaInfo(lambda,
                                                       method2,
                                                       this.scope.HasLiftedParameters));
    this.scope = this.scope.Parent;
    return num2;
}
이 소스 코드가 중요한게 아니라, 바로이 Compile() 메서드가 실행 코드로 변환해 준다는 것 입니다. 그래서, LambdaExpression 예제 코드의 실행 결과는
| 
 UmcBlog 
UmcBlog  | 
동일한 결과가 나타나게 됩니다. 때문에, SampleProvidier의 Execute<T> 메서드 내의
context.DataSource.Where(lambda.Compile());
코드는 어떠한 람다식이라도 실행 가능한 형태로 변환해주게 되며, DynamicInvoke 를 호출하여 동적으로 컴파일된 람다식을 실행하게됩니다.
SampleContext 의 Provider 를 이용한 쿼리 만들기
여기까지해서 SampleContext 와 SampleProvider 클래스에 대한 설명은 다 한것 같습니다. 그럼, 이 Custom LINQ Provider 를이용하여 LINQ 식을만들어보면…
| 
 class Program 
{ 
       static void Main(string[] args) 
       { 
             SampleContext context = new SampleContext() ; 
             context.DataSource = new List<Person> {  
                 new Person { Name="엄준일", Age=29}, 
                 new Person { Name="내동생", Age=26}, 
                    new Person { Name="울누나", Age=31}, 
                    new Person { Name="멍멍이", Age=6}, 
                    new Person { Name="발발이", Age=5} 
             }; 
             var result = from r in context 
                                  where r.Age > 20 
                                  select r; 
             result.ToList().ForEach( o=>Console.WriteLine(o.Name )); 
       } 
}  | 
내용은 간단합니다. SampleContext 객체를 만들어, DataSource 에 임의의 데이터를 넣고, LINQ 쿼리식의 결과를 출력하는 예제입니다.
실제로 소스를 실행해 보면,
| 
 엄준일 
내동생 
울누나  | 
가 출력이 되고, LINQ 쿼리식에 브레이크 포인터를 걸어 디버깅해 보면, 순차적으로 SampleContext 와 SampleProvider 를 타면서 LINQ 쿼리식이 해석 되는 것을 볼수있습니다.
다음 시간에는, Custom LINQ Provider 를 통해 원격 개체를 탐색하는 방법에 대해 살펴 보도록하고, 이만 마치겠습니다. 이번 한주도 즐겁게 시작하시기 바랍니다^^//
'.NET > C#' 카테고리의 다른 글
| Custom LINQ Provider - [5]. LINQ To Naver Open API (0) | 2008.03.30 | 
|---|---|
| Custom LINQ Provider - [4]. Query(쿼리)를 이용한 원격 개체 탐색 (0) | 2008.03.27 | 
| Custom LINQ Provider - [2]. Custom LINQ Provider 만들기 (IQueryable) (0) | 2008.03.13 | 
| Custom LINQ Provider - [1]. 소개 (1) | 2008.03.10 | 
| LINQ 의 OUTER JOIN 작업 (0) | 2007.09.04 | 
					댓글
						
					
					
					
				
			
										공지사항
										
								
							
								
								
									최근에 올라온 글
									
							
								
								
									최근에 달린 댓글
									
							
								
								- Total
 
- Today
 
- Yesterday
 
									링크
									
							
								
								- ***** MY SOCIAL *****
 - [SOCIAL] 페이스북
 - [SOCIAL] 팀 블로그 트위터
 - .
 - ***** MY OPEN SOURCE *****
 - [GITHUB] POWERUMC
 - .
 - ***** MY PUBLISH *****
 - [MSDN] e-Book 백서
 - .
 - ***** MY TOOLS *****
 - [VSX] VSGesture for VS2005,200…
 - [VSX] VSGesture for VS2010,201…
 - [VSX] Comment Helper for VS200…
 - [VSX] VSExplorer for VS2005,20…
 - [VSX] VSCmd for VS2005,2008
 - .
 - ***** MY FAVORITES *****
 - MSDN 포럼
 - MSDN 라이브러리
 - Mono Project
 - STEN
 - 일본 ATMARKIT
 - C++ 빌더 포럼
 - .
 
									TAG
									
							
								
								
							
					- mono
 - ASP.NET
 - testing
 - TFS 2010
 - Windows 8
 - TFS
 - Visual Studio 2008
 - umc
 - c#
 - Silverlight
 - monodevelop
 - Team Foundation Server
 - POWERUMC
 - .NET
 - 비주얼 스튜디오
 - Visual Studio
 - github
 - MEF
 - ALM
 - Visual Studio 2010
 - Managed Extensibility Framework
 - Visual Studio 11
 - test
 - 비주얼 스튜디오 2010
 - Team Foundation Server 2010
 - .NET Framework 4.0
 - 땡초
 - 엄준일
 - 팀 파운데이션 서버
 - LINQ