티스토리 뷰
.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 테이블입니다.
r[그림2] 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 쿼리식이 해석 되는 것을 볼수있습니다.
[그림3] 브레이크 포인터를 통해 LINQ 식의 Custom LINQ Provider 내부를 디버깅하는 화면
다음 시간에는, 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
- Managed Extensibility Framework
- 엄준일
- c#
- Team Foundation Server 2010
- .NET
- 팀 파운데이션 서버
- POWERUMC
- 땡초
- Windows 8
- Visual Studio 11
- ALM
- Visual Studio 2008
- Silverlight
- umc
- test
- TFS
- LINQ
- monodevelop
- github
- ASP.NET
- testing
- TFS 2010
- Visual Studio
- Team Foundation Server
- 비주얼 스튜디오
- 비주얼 스튜디오 2010
- .NET Framework 4.0
- mono
- MEF
- Visual Studio 2010