이전 시간에 Custom LINQ Provider 에 대해서 살며시 알아보았습니다. 이것을 다시 한번 요약하자면, .NET Framework 이 제공하는 공급자가 아닌, 개발자에 의해 통합 쿼리식을 이용하여 전혀 다른 데이터를 질의 하기 위해 사용합니다.
 
 
Entity Class 만들기
 
우리는 이 단원에서 간단한 Entity Class 를 이용할 것입니다. List<> 클래스에 간단한 데이터를 담을 수 있도록 선언된 클래스입니다.
 
public class Person
{
       public string Name { get; set; }
       public int Age      { get; set; }
}
 
 
Context 객체 만들기
 
우선 여기서 IQueryable<> 인터페이스를 살펴보겠습니다. MSDN 이 말하길 이 인터페이스는 “데이터 형식이 알려진 특정 데이터 소스에 대한 쿼리를 실행하는 기능을 제공합니다.” 라고 합니다. 물론, 제네릭 형식의 인터페이스므로 “데이터 형식이 알려진…” 이라고 하였으나, IQueryable 과 같은 비 제네릭 인터페이스는 제네릭이 아닌 IEnumerable 을 구현해야 합니다. 마찬가지로 IQueryable<> 인터페이스는 IEnumerable<> 을 구현해야 합니다.
 
이 Context 객체는 IQueryable<> 인터페이스를 구현함으로써, 쿼리식을 이용할 DataSource 에 대해 독자적인 Provider 로 식을 연산할 수 있습니다.
 
public class SampleContext : IQueryable<Person>
{
       internal Expression expression;
       private SampleProvider provider;
       public List<Person> DataSource { get; set; }
      
       #region IEnumerable<Person> Members
 
       public IEnumerator<Person> GetEnumerator()
       {
             return provider.Execute<IEnumerator<Person>>(this.expression);
       }
 
       #endregion
 
       #region IEnumerable Members
 
       System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
       {
             return (this as IEnumerable<Person>).GetEnumerator();
       }
 
       #endregion
 
       #region IQueryable Members
 
       public Type ElementType
       {
             get { return typeof(Person); }
       }                         
 
       public Expression Expression
       {
             get { return Expression.Constant(this); }
       }
 
       public IQueryProvider Provider
       {
             get
             {
                    if( provider == null )
                           provider = new SampleProvider(this);
 
                    return provider;
             }
       }
 
       #endregion
 
}
 
아까 잠시 설명드린 IQueryable<> 인터페이스가 제공하는 프로퍼티와 메서드는 모두 중요한 역할을 하고 있습니다.
 
Provider Property
우리는 DataSource 라는 속성에 특정 데이터를 넣게 되고, 이 데이터의 쿼리식을 연산할 Provider 의 객체를 리턴해야 합니다. 하나의 인스턴스에 하나의 객체만을 생성하기 위해서 null 체크를 하고, SampleContext 를 매개변수로 생성된 Provider 를 리턴합니다. 아직 구현되지 않았고, 잠시 후에 살펴보겠습니다.
 
Expression Property
Expression 속성은 Expression.Constant 라는 메서드를 사용합니다. 쿼리식을 표현하기 위해 Lambda Expression 과 같은 표현을 사용하게 되며, 이 Expression 은 다양하게 구문을 해석하기 위한 클래스를 제공해 줍니다.
 
기본적인 Expression 은 표현식을 처리할 수 있습니다.
 
Expression<Func<string,string,string>> ff = (s1,s2) => (s1 + s2);
Console.WriteLine( ff.Body );
 
위의결과는표현식의 Body(몸통) 부분을가져오는예입니다.
 
(s1 + s2)
 
신기하지 않나요?? ^^
 
가령, BinaryExpression 은 이항 연산과 관련된 식을 표현합니다. ( xxxExpression 은 Expression 을 상속 받습니다 ) +, *, EQ, AND 등이 여기에 속합니다.
 
Expression<Func<int,int,int>> f = (v1,v2) => v1 + v2;
BinaryExpression be = (BinaryExpression)f.Body;
Console.WriteLine( "{0} {1} {2}", be.Left, be.Right, be.NodeType );
 
위의결과는
 
v1 v2 Add
 
의 결과가 나옵니다.
 
그럼, ConstantExpression 의 예를 보도록 하죠. 이 Expression 은 상수 값이 있는 표현식을 처리할 수 있습니다.
 
Expression<Func<int, bool>> f = (v1) => ( v1 == 5 );
BinaryExpression be = (BinaryExpression)f.Body;
ConstantExpression ce = (ConstantExpression)be.Right;
Console.WriteLine( ce.Value );
 
결과는
 
5
 
가 됩니다.
 
그럼 문제를 하나 내 보도록 하겠습니다. 이후에 이 예제에서 나오게 될 LINQ 식입니다. 이 LINQ 식에서 ConstantExpression 은 어디에 해당할까요?
 
var result = from r in p
              where r.Age > 20
              select r;
 
설마 잘 모르시겠다고요?? ConstantExpression 은 상수의 값이 있는 식을 가져온다고 하였습니다. 저 쿼리식 중에 상수의 값이 어디 있는지 찾아보시면 답은 나올 겁니다.
 
ElementType Property
이 속성은 반복되는 식의 요소, 즉, 위에서 우리가 만들 Person 클래스는 List<Person> 객체에 담을 것이기 때문에, Person 클래스의 타입을 리턴하시면 됩니다.
 
GetEnumerator Methods
이 메서드도 굉장히 중요한 부분입니다. 잠시 예를 들어 보죠. 보통 DataBase 에서 SELECT 문을 이용하여 데이터를 조회할 때, 또는 프로시져 내의 SELECT 구문을 만나면 당장 이런저런 처리를 하여 결과를 뱉어냅니다.
 
그럼 여기서 또 하나의 문제를 내보겠습니다. 다음의 서로 다른 환경의 쿼리식을 10000번 반복했을 때, 수행시간은 누가 더 빠를까요?
1. 실제 Database 쿼리를 10000 번 수행
2. LINQ To SQL 쿼리식을 10000 번 수행
SELECT *
FROM p r
WHERE r.Age > 20
var result = from r in p
          where r.Age > 20
            select r;
 
언듯 생각하기에는, 둘의 결과는 비슷하리라 생각이 들기도 합니다. 2번은 Database Connection 이 맺어질 것이고, 더 느리면 느렸지, 빠르진 않다고 생각할 수 도 있겠죠. 좀 애매하네요 ^^;
 
하지만, 잠시 언급한 “비슷하다, 2번이 더 느릴것이다”라는 예상은 완전히 빗나갑니다.
 
1번의 경우 무지하게 오래 걸립니다. 왜냐하면, 질의에 대해 바로 바로 결과를 반환합니다.
2번의 경우 질의 결과를 반환하지 않습니다. 그렇기 때문에 굉장히 빠르게 10000번의 쿼리를 실행합니다.
 
 
LINQ 의 쿼리식은 다릅니다. 왜 2번이 답이 되는지 봅시다.
 
var result = from r in p
              where r.Age > 20
              select r;
 
의 쿼리식이 있을 경우, 예상과 다르게 런타임이 저 구문을 지날 때 result 는 어떠한 데이터의 질의 결과를 가지고 있지 않습니다. 즉, 질의에 대한 결과값은 전혀 없는, 그냥, SampleContext 객체일 뿐입니다. 런타임이 위의 쿼리식을 만나게 되면, Provider 에게 표현식을 위임하게 되고, 식을 해석만 합니다. 이것이 Database 에 연결된다고 할지라도, Database 에 요청할 쿼리식(SQL) 만을 만들 뿐입니다.
 
바로, 이 GetEnumerator 메서드가 호출되는 시점에, 데이터를 가공하여 결과를 반환하게 됩니다. 마찬가지로, Database 에 연결되는 작업이라면 이 시점에서 실제로 Database Connection 이 열리게 되고, 쿼리하여 결과를 반환할 수 있게 되는 것입니다.
 
 
다음 회차에서 IQueryProvider 인터페이스를 구현해 보겠습니다.
저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by 땡초 POWERUMC

댓글을 달아 주세요