요즘 참 할일도 많은데 할 수 있는 일이 점점 줄어든다. 필자는 블로그 버킷 리스트(bucket list)를 작성하는데 블로그가 사망하기 전에 꼭 해야 할 일을 목록으로 만들어 놓고 하나 하나씩 글을 써 나간다. 근데 할 일이 늘어만 간다. ㅠ

  • 당장 쓸 수 있는 글 39개
    사소한 개발 기술부터 심도있는 내용으로 흐리멍텅한 개념을 글을 쓰면서 잡아 나가는 것들

  • 개발 후 산출물로 쓸 글 37개
    오픈소스로 내놓을 계획, 또는 알고 있는 것들에 대한 증명이 필요하고 그 후에 쓸 수 있는 글

  • 연구개발 11개
    배우고 싶은 것, 하고 싶은 것, 해야 하는 것들이고 공부해야 쓸 수 있는 글들

아무튼 점점 쓸 것들이 늘어만 가지만, 하나 하나 하다보면 쓸게 없어 지는 날이 올거라 믿는다 >.,<

#1 - Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy)

다이나믹 프록시(Dynamic Proxy)는 최근 IoC(Inversion of Controls)라는 개념으로 확장되고 객체지향 프로그래밍(OOP)의 효율성을 극대화한다. 다이나믹 프록시 기법은 이미 오래 전부터 존재해 왔다.

C++의 스마트 포인터(Smart Pointer) 처럼 직접 포인터를 건들이지 않고 레퍼런스 카운팅이나 객체의 생명주기를 프록시 개체(Proxy Object)를 통해 관리하는 기법이 있고, RPC(Remote Procedure Call)과 같은 원격 프로시저 호출을 위해 프록시 패턴(Proxy Pattern)이 기반에 깔려 직접 원격 구현을 숨기고 간단한 인터페이스만 제공해 주는 패턴 등 매우 다양하다.

최근의 다이나믹 프록시(Dynamic Proxy)는 IoC(Inversion of Control), DI(Dependency Injection), ORM(Object Relation Mapping), AOP(Aspect Oriented Programming) 등 객체를 다루는 프레임워크에서 기본적으로 채용할 만큼 객체를 다룸에 있어 가장 기본적인 기법 또는 기술에 속한다.

필자가 오늘 다루어 볼 내용은 이런 프록시 패턴(Proxy Pattern)이 기반이 되는 개체 제어를 위한 기법이다.

먼저 다이나믹 프록시(Dynamic Proxy)에 대해 예전에 잠깐 쓴 글을 참고하자.

과거 2009년도 닷넷엑스퍼트 재직 시절에 다이나믹 프록시(Dynamic Proxy)를 만들었다가, 2011년 좀 더 나은 객체 디자인으로 완전히 다시 만든 다이나믹 프록시(Dynamic Proxy) 프레임워크가 지금 설명할 Umc.Core.Dynamic.Proxy 이다.

Umc Core 프레임워크는 오픈 소스로 공개되어 있으며 누구든지 소스 코드를 열람할 수 있다. 현재 소스 코드는 다음의 링크를 통해 제공된다.

그 외에 필자가 공개한 소스 코드도 여럿 있으니 관심있는 분들은 참고하기 바란다.

 

 







#2 - 코드부터 보자.

만약 관리 언어(Managed Language)의 동작 매커니즘을 이해하지 못했다면 마치 마법과도 같은 기법으로 보일 것이다. 아래의 코드는 완벽하게 돌아가는 소스 코드이다.

using System;  
using Umc.Core.Dynamic;  

namespace ConsoleApplication1
{  
public interface IPerson
{
    string Name { get; set; }
    string Email { get; set; }
}  

class Program
{
    static void Main(string[] args)
    {
        var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();
        var person = (IPerson)Activator.CreateInstance(type);

        person.Name  = "POWERUMC";
        person.Email = "powerumc at 지멜";

        Console.WriteLine("Name : {0}, Email : {1}", person.Name, person.Email);
    }
}
}  

한번 위의 코드를 보자. IPerson 인터페이스는 IPerson을 구현하는 클래스가 반드시 있어야 객체를 생성할 수 있다. 다음과 같이 말이다.

public class Person : IPerson
{
    public string Name { get; set; }
    public string Email { get; set; }
}   

C#의 상식으로 보아도 인터페이스(Interface)는 인스턴스를 생성할 수 없으며, 인스턴스를 생성하기 위해 구현 클래스가 반드시 존재햐여 하는 것 쯤은 알고 있을 것이다. 즉, Person 클래스가 있어야 IPerson person = new Person(); 으로 객체를 생성할 수 있다. 분명, 처음 코드의 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 가 뭔가의 마법을 부리는 것이 확실하다고 보면 된다.

#3 - 다이나믹 프록시(Dynamic Proxy) 의 다른 구현 방법들

방금 언급한 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 코드가 바로 class 로 구현조차 되지 않은 클래스를 동적(Dynamic)으로 런타임에 생성한다. 이 동적 클래스를 런타임에 생성하기 가장 쉬운 방법이 있는데, 그것은 .NET Framework에서 제공하는 CodeDomProvider 이다.

간단히 CodeDomProvider를 언급만 하고 넘어가보자. CodeDomProvider는 C#, VB.NET의 객체 표현을 Code Dom으로 표현할 수 있는데, 각각 CSharp, VB.NET Provider를 제공해 주고, 런타임에 컴파일을 한다. 다만, 컴파일 시간이 동적 객체 생성 기법 중에 가장 느리다.

그리고 C# 3.0부터 지원하는 람다 표현식(Lambda Expression)을 이용하는 방법이다. 이 람다 표현식도 내부적으로 CodeDom과 매우 유사하다. 단점이라면, 객체의 이름(Class Name)과 주소(Namespace) 등을 원하는데로 줄 수 없다. LambdaExpression 클래스의 맴버들이 많은데, 이것들로 모든 구현이 가능하다. 간단한 예제 코드를 보자.

Expression<Func<string, string>> expression = (s) => "Hello " + s;               
var func = expression.Compile();               
var ret = func("POWERUMC");   
Console.WriteLine(ret);

이 코드를 실행하면 “Hello POWERUMC” 라는 결과가 나오는 것을 확인할 수 있다.

이렇게 알게 모르게 이미 .NET Framework에서 다이나믹 프록시(Dynamic Proxy) 기법을 쓰는 곳이 의외로 많다. C# 컴파일러는 익명의 표현식들을 내부적으로 컴파일하면서 메서드로 만들어 버리기도 한다. 이렇게 똑똑한 컴파일러 덕분에 람다 표현식이나 LINQ의 성능이 매우 좋다는 것이다.

이 외에 동적메서드(DynamicMethod) 방법이 있는데, 얘는 그냥 넘어가도 무관하다.

#4 - 다이나믹 프록시(Dynamic Proxy) 개체가 생성되는 일련의 순서

동적 개체가 만들어지려면 어떤 순서가 필요할까?

먼저 객체지향 언어라는 점을 생각해볼때, 객체의 주소(Namespace)와 이름(Type Name)이 필요하다. 필자의 프레임워크 코드 중 Umc.Core.Dynamic.Proxy.Builder 네임스페이스의 ModuleBuilderExtension와 TypeBuilderExtension이 그 역할을 한다.

Type을 생성하기 위해서는 생성자 하나 이상이 반드시 필요한데, Object를 상속하는 타입을 만들어야 한다. 아시다시피 public class Person 클래스는 암묵적으로 public class Person : Object 를 상속하게 된다. 그래서 타입을 생성하고 하나의 생성자를 만들고 나면, 부모 객체인 Object의 생성자(Constructor)를 호출을 해주어야 한다. 당연히 부모가 있어야 자식이 있는 것과 같다.

이 때 반드시 주의해야 하는 것이 있다. 생성자는 인스턴스를 생성하는 생성자가 있고, static 생성자가 있다. 만약 static 생성자인 경우는 부모 객체인 Object 생성자(Constructor)를 호출하면 안된다.

Object 생성자를 타입의 생성자 안에서 호출하려면 다음과 같은 코드를 이용하면 된다.

il.Emit(OpCodes.Ldarg_0);  
l.Emit(OpCodes.Call, typeof(object).GetConstructors([0\]);  

이제 IPerson의 프로퍼티(Property)를 구현한다.

IPerson의 프로퍼티 목록은 리플랙션(Reflection)을 이용하여 쉽게 얻어낼 수 있다. 간단히 typeof(IPerson).GetProperties(); 면 된다. (프레임워크 내부에선 더 복잡할 수 있음)

C#의 프로퍼티는 컴파일을 하게 되면 프로퍼티가 메서드로 치환된다. 예를 들어, public string Name { get; set; } 코드는 다음 처럼 컴파일러가 치환한다.

public string get_Name() { ... }  
public void set_Name(string name) { ... }  

그러므로, 다이나믹 프록시를 구현할 때도 프로퍼티를 선언해 준 후에 get, set 메서드를 구현해 주어야 한다. 이를 Umc.Core에서 다음고 같이 구현하였다.

public ICodeLambda Get()
{  
var type       = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  
var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, propertyBuilder.PropertyType, String.Concat("get_", propertyBuilder.Name), Type.EmptyTypes, null, false);  

propertyBuilder.SetGetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());
}  

public ICodeLambda Set()
{  
var type = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  

var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, typeof(void), String.Concat("set_", propertyBuilder.Name), new Type[] { propertyBuilder.PropertyType }, null, false);  
method.DefineParameter(1, ParameterAttributes.HasDefault, "value");  

propertyBuilder.SetSetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());

}  

그리고 get, set 메서드의 내부 코드는 다음과 같이 구현된다.

public ITypeLambda GetSet()
{  
this.TypeLambda.FieldAccessor.FieldAttribute = FieldAttributes.Private;  
var field = this.TypeLambda.Field(this.propertyBuilder.PropertyType, String.Concat("__", propertyBuilder.Name));  

var get = this.Get();
{
    get.Return(field);
}  

var set = this.Set();
{
    set.IL.Emit(OpCodes.Ldarg_0);
    set.IL.Emit(OpCodes.Ldarg_1);
    set.IL.Emit(OpCodes.Stfld, ( (IValuable<FieldBuilder>)field ).Value);

    set.Return();
}  

return this.TypeLambda;
}  

위의 코드가 그 흔한 프로퍼티의 구현부인 public string Name { get { return this._name; } set { this._name = value; } } 를 구현하는 코드가 되겠다. 별거 아닌 코드지만 내부적으로 프로퍼티와 메서드를 생성하고, 그 구현 코드를 IL 코드로 재구현 하면 역시 조금 난이도가 높아진다.

메모리에서 개체 포인터의 엑세스를 방지하기 완료 방법

지금까지는 메모리에 동적인 코드들을 IL 코드들로 구현하였다. 모든 구현이 완료되었으면 더 이상 메모리에서 IL 코드가 위변조 되거나 .NET 코드 정책에 위배되지 않도록 엑세스 접근을 막아야 한다.

이 코드는 Umc.Core.Dynamic.Proxy.Lambda.TypeLambda 클래스의 CreateType() 메서드가 완료시킨다. 이 코드는 다음과 같이 구현 되었다.

public Type ReleaseType()
{  
if (this.TypeBuilder == null)
{
    throw new NullReferenceException(this.TypeBuilder.GetType().Name);
}  

return this.TypeBuilder.CreateType();
}   

코드 구현의 완료를 알렸다면 이제 동적 타입(Dynamic Type) 개체가 완성이 된다. 즉, 맨 처럼 IPerson 인터페이스를 구현하는 동적 개체(Dynamic Object)인 Person 개체의 타입이 되는 것이다. 즉, 이 Person 개체는 런타임에서 메모리상에 만들어진 것이 되겠다.

이리하여 Activator.CreateInstance(type) 으로 메모리상에 만들어진 Person 클래스를 생성할 수 있게 되는 것이다.

다이나믹 프록시(Dynamic Proxy) 개체의 모습

메모리상에서 만들어진 Person 개체의 타입은 내부적으로 규칙을 정해서 만들었다. Person 객체를 생성하여 이 객체의 타입을 보면 괴상한 이름으로 지어졌다.

var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();  
var person = (IPerson)Activator.CreateInstance(type);  

// 결과 -> dynamic_46c8ecaab09b41cbaa814511f69f1974

이 타입이 속한 주소(Namespace)와 모듈(Module)을 직접 디스크에 저장할 수도 있는데, 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 처럼 사용할 수도 있다.

또 다이나믹 프록시(Dynamic Proxy)를 활용해 중간에 로깅(Logging)을 하거나 트랜잭션(Transaction) 처리를 할 수 있는 AOP 를 구현할 수도 있는 것이다. (AOP 구현 기법 중 하나이며, 다른 방법이 더 있다.)

또, 사용 편의성을 위해 고안된 것이 체이닝(Chaining) 방법을 이용하여 런타임 코드를 더 쉽게 만드는 방법을 Umc Core 프레임워크에서 제공한다. 다음의 코드를 보면 쉽게 이해가 될 것이다.

using System;  
using System.Linq;  
using System.Reflection;  
using Umc.Core.Dynamic.Proxy.Lambda;  

namespace ConsoleApplication1
{  
internal class Program
{
    private static void Main(string[] args)
    {
        string typeName = Guid.NewGuid().ToString("N");
        string methodName = Guid.NewGuid().ToString("N");

        var assembly = new AssemblyLambda().Assembly();
        {
            var module = assembly.Module();
            {
                var type = module.Public.Class(typeName);
                {
                    var constructor1 = type.Public.Constructor();
                    {
                        constructor1.Emit.EmitWriteLine("This is constructor");
                        constructor1.Return();
                    }

                    var constructor2 = type.Private.Static.Constructor();
                    {
                        constructor2.Emit.EmitWriteLine("This is (private static) constructor");
                        constructor2.Return();
                    }

                    var method = type.Public.Static.Method(methodName);
                    {
                        method.Emit.EmitWriteLine("This is emitted writeline");
                        method.Return();
                    }
                }

                var releaseType = type.ReleaseType();
                var obj = System.Activator.CreateInstance(releaseType);
                var releaseMethod = releaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);

                Console.WriteLine("Release type is {0}", releaseType.AssemblyQualifiedName);
                Console.WriteLine("Release method is {0}", releaseMethod.Name);

                releaseType.GetMethod(methodName).Invoke(null, null);

                Console.WriteLine("".PadLeft(50, '-'));

                releaseType.GetMethods().ToList().ForEach(m => Console.WriteLine("Method Name is " + m.Name));
            }
        }
    }
}
}



// 결과 ->  
This is (private static) constructor  
This is constructor  
Release type is bdfd27e5e7ed446d8702fe172733d937, 4a8f6dd24e1f4054b551cca95ad0870b, Version=0.0.0.0, Culture=neutral,  PublicKeyToken=null  
Release method is 389b4a43a1ba4dc1b1391b5b06633645  
This is emitted writeline  
Method Name is 389b4a43a1ba4dc1b1391b5b06633645  
Method Name is ToString  
Method Name is Equals  
Method Name is GetHashCode  
Method Name is GetType  

위의 코드와 같이 좀 더 직관적으로 동적 코드를 만드는 방법을 Umc Core에서는 제공해 주고 있다.

재미있는 것은 메서드의 이름이다. 결과 출력 중 Method Name is 389b4a43a1ba4dc1b1391b5b06633645 이 있는데, 놀랍게도 메서드의 이름이 숫자로 시작한다. C# 언어 사양 중 변수의 타입, 변수 등의 이름은 반드시 알파벳 문자로 시작해야 함에도 불구하고, 숫자로 시작하다니…

이는 C#의 언어 사양과 CIL(Common Intermediate Language) 언어의 사양이 다르기 때문이다. 심지어 숫자가 아닌 특수문자로 시작해도 된다. 앞서 말했다시피 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 가 가능하다.

비록 대부분의 Umc Core 소스 코드를 공개하지 않았지만, 미공개 코드 중 코드 난독화 툴도 함께 포함이 되어있고, Junk Assembly를 만드는 툴, AOP based Compile 툴 등도 포함이 되어있다. ㅎㅎㅎ; 아쉽지만 여전히 미공개 분은 공개하지 않을 예정이다. 더불어, 관심있는 독자라면 CIL 언어 스팩의 사양을 살펴보는 것도 좋을 것이다.

결론

지금까지 살펴본 다이나믹 프록시(Dynamic Proxy)를 살펴보았다. 이 기법은 IoC, DI, AOP, ORM 등 최신 객체 제어 기술들의 가장 근간이 되는 기법이다. 여러분들이 최신 기술들로 IoC, DI, AOP 등을 구현하고 싶다면 당장 오픈소스를 이용하면 될 것이다.

아마 필자처럼 .NET 기반으로 다이나믹 프록시(Dynamic Proxy)를 직접 구현해서 쓰는 것은 매우 드문 일이다. 이미 검증된 오픈 소스들도 많기 때문에 굳이 이를 구현해서 쓸 필요는 없다.

하지만 이런 오픈 소스를 이용하여 쓰는 것은 그리 어렵지 않겠지만, 이런 오픈 소스를 이용하여 어떻게 코드를 만드느냐는 성능이나 내부적으로 동작하는 효율성에 영향을 미칠 수 있다. 즉, 자신이 오픈 소스를 이용하여 만든 코드가 효율적인 코드, 그리고 메모리상에서 Clean Code로 동작하느냐는 별개의 문제이다.

필자와 같이 다이나믹 프록시(Dynamic Proxy)를 구현해 보았다면 여러분들이 얼마나 위험한 코드를 많이 만드는지 알 수 있다. 즉, 오픈 소스를 써보기만 했다면 절대로 알 수 없는 고급 기술이다.

ASP.NET MVC 등과 같은 프레임워크에 IoC 등이 통합되면서 내부적으로 이와 유사한 방법을 쓴다. 특히 웹 개발에 있어 IoC와 같은 프레임워크는 굉장히 위험할 수 있다. 설명하자면 지금까지 쓴 내용보다 더 복잡하고 많은 내용으로 설명이 필요할지도 모른다.

만약 관리 언어(Managed Language) 등으로 만들어진(자바도 구현 기법이 유사함) 응용 프로그램의 고성능 튜닝, 트러블 슈팅, .NET 플랫폼에 관심이 많은 독자라면 반드시 이해하고 넘어가야 할 내용들이다. 그리고 오픈 소스를 이용하여 다이나믹 프록시(Dynamic Proxy)를 간접적으로 사용만 한다면 가볍게 읽고 넘어가도 좋다. 소소한 개발 일상의 이야깃 거리도 될 것이다.


필자는 이 외에도 다이나믹 프록시(Dynamic Proxy)를 바탕으로 이를 IoC와 통합하고 AOP, ORM 등을 구현하는 방법도 알아볼 것이다. Umc Core는 오픈 소스로 공개되어 있으며 위키 페이지를 만들겸 하나씩 정리하고자 한다. 

'Umc Projects > Umc.Core' 카테고리의 다른 글

Umc Core IoC 통합 컨테이너 #1  (0) 2013.05.24
Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy) #1  (0) 2013.05.23
Umc.Core 미공개 Preview  (6) 2008.05.14
Umc.Core 란?  (0) 2007.12.01
Posted by 땡초 POWERUMC

댓글을 달아 주세요

Microsoft Research 프로젝트로 알아보는 새로운 세대의 시작

Microsoft Research 프로젝트는 Microsoft 에서 진행하는 오픈된 기술과 연구를 하는 R&D 조직으로, 새로운 비즈니스와 기술을 결합하는 프로젝트입니다. 최근 들어 Microsoft Research 사이트의 프로젝트는 작년과 비교해 엄청나게 늘어났습니다. 작년까지만 해도 불과 10~30개의 오픈된 프로젝트가 현재 수백 개의 프로젝트로 늘어난 것이 굉장히 놀랍니다. 그만큼 기술의 트랜드가 빠르게 변한다는 반증이 되겠지요.

Microsoft Research 프로젝트를 열람해 보는 것은 매우 중요합니다. 왜냐하면 당장 2~3년 내에 현실화되는 기술들도 있으며, 현재의 기술이 작년의 Microsoft Research 프로젝트와 통합된 것이 많기 때문이죠. 앞으로 나와는 상관 없는 것들도 있지만, 간접적으로 영향권에 들게 될거라고 생각합니다. 과연 스마트 폰이 어느샌가 내 호주머니 속에 들어가게 될지 모르는 것 처럼 말이죠^^

그럼 Microsoft Research 프로젝트에서 진행하고 있는 재미있는 프로젝트를 소개해 드립니다. 말씀 드렸다시피 이 프로젝트들은 언제 사라질지 모르는 것들이지만, 가치가 있는 것들은 모조리 비즈니스/웹/개발 영역에 접목이 될 수 있습니다.

   

Microsoft Research 진행 프로젝트 소개

A programming language for composable DNA circuits

http://research.microsoft.com/en-us/projects/dna/

이 프로젝트는 프로그래밍 언어의 핵심 로직을 시각화(Visualization)해 주는 프로젝트입니다. 가령 아래와 같은 로직이 있다고 치면, 참 답답하죠. 왜냐하면 제가 무슨 뜻인지 모르니까요^^

directive sample 20000.0 1000

directive plot <kkks t^ kkksr>; <kkppl t^ kkpp x^>; <kpp t^ kppr>;

<kkk t^ kkkr>; <kkl t^ kk x^>; <k t^ kr>; <kkpl t^ kkp x^>; <kp t^ kpr>

directive scale 10.0

def bind = 0.0003 (* /nM/s *)

def unbind = 0.1126 (* /s *)

def Init = 50

def Low = 1

def Excess = 100

   

new x@ bind,unbind

new t@ bind,unbind

   

def SpeciesL(N,al,a) = N * <al t^ a x^>

def SpeciesR(N,a,ar) = N * <a t^ ar>

def BinaryLRxLR(N,al,a,b,br,cl,c,d,dr) = (* A + B ->{N} C + D *)

new i

( constant N * t^*:[a x^ b]<i cl t^ i t^ dr>:t^*

| constant N * Excess * x^*:[b i]:[cl t^]<c x^>:[i]:<d>[t^ dr]

)

   

new e1l new e1 new kkk new kkkr new kkl new kk new k new kr

new e2l new e2 new kkpase new kkpaser new kpasel new kpase

new kkks new kkksr new kkpl new kkp new kkppl new kkpp

new kp new kpr new kpp new kppr

   

( SpeciesL(1,e1l,e1) (* E1 *)

| SpeciesR(10,kkk,kkkr) (* 10 KKK *)

| SpeciesL(100,kkl,kk) (* 100 KK *)

| SpeciesR(100,k,kr) (* 100 K *)

| SpeciesL(1,e2l,e2) (* E2 *)

| SpeciesR(1,kkpase,kkpaser) (* KKPase *)

| SpeciesL(1,kpasel,kpase) (* KPase *)

| BinaryLRxLR(Init,e1l,e1,kkk,kkkr,e1l,e1,kkks,kkksr) (* E1 + KKK ->{r} E1 + KKKs *)

| BinaryLRxLR(Low,e2l,e2,kkks,kkksr,e2l,e2,kkk,kkkr) (* E2 + KKKs ->{r} E2 + KKK *)

| BinaryLRxLR(Init,kkl,kk,kkks,kkksr,kkpl,kkp,kkks,kkksr) (* KK + KKKs ->{r} KKP + KKKs *)

| BinaryLRxLR(Init,kkpl,kkp,kkks,kkksr,kkppl,kkpp,kkks,kkksr) (* KKP + KKKs ->{r} KKPP + KKKs *)

| BinaryLRxLR(Low,kkppl,kkpp,kkpase,kkpaser,kkpl,kkp,kkpase,kkpaser) (* KKPP + KKPase ->{r} KKP + KKPase *)

| BinaryLRxLR(Low,kkpl,kkp,kkpase,kkpaser,kkl,kk,kkpase,kkpaser) (* KKP + KKPase ->{r} KK + KKPase *)

| BinaryLRxLR(Init,kkppl,kkpp,k,kr,kkppl,kkpp,kp,kpr) (* KKPP + K ->{r} KKPP + KP *)

| BinaryLRxLR(Init,kkppl,kkpp,kp,kpr,kkppl,kkpp,kpp,kppr) (* KKPP + KP ->{r} KKPP + KPP *)

| BinaryLRxLR(Low,kpasel,kpase,kpp,kppr,kpasel,kpase,kp,kpr) (* KPase + KPP ->{r} KPase + KP *)

| BinaryLRxLR(Low,kpasel,kpase,kp,kpr,kpasel,kpase,k,kr) (* KPase + KP ->{r} KPase + K *)

)

어찌되었건 이런 코드는 다양한 방법으로 시각화를 해줍니다. 아래는 제가 시뮬레이션해 보니 이런 결과가 나오네요.

중요한 것은 이런 형태의 시각화(Visualization)은 지속적으로 발전하고 있습니다. 모델링(Modeling) 과 DSL(Domain Specifically Language) 의 중요성과 함께 지속적으로 발전하게 될 테니까요.

이 데모는 실버라이트로 작성되어 웹에서 직접 테스트해 보실 수 있습니다. http://lepton.research.microsoft.com/webdna/

   

   

Ajax View

http://research.microsoft.com/en-us/projects/ajaxview/

AJAX 기술로 직격타를 받고 성장한 것이 바로 Web 2.0 입니다. 그리고 Web 2.0을 넘어 Web 3.0이 언급이 되었고, 더 나아가 SNS(소셜 네트워크 서비스)로 발전한 가운데, 가장 영향력을 미친 기술이 AJAX 입니다. 기술적으로 트래픽의 라운드 트립을 줄이고, 분산 아키텍처에 지대한 영향을 미쳤으며, 더 나아가 브라우징(Browsing) 사용성을 극대화한 기술입니다.

하지만 사실상, AJAX 기술은 불필요한 라운드 트립을 증가시킬 수 있는 가장 적절한 수단이기 때문에 잘 사용하는 것이 어려운 기술이기도 합니다. Ajax(Asynchronous JavaScript and XML) 는 순수한 자바스크립트 기술로써 많은 부분을 클라이언트에 의존하지만 자바스크립트와 더불어 HTML CodeDom, XML, DHTML 까지 확장되어 그 영역이 상당하게 복합된 기술이라고 보셔도 됩니다.

   

그렇다면 과연 AJAX 를 어떻게 잘 쓸 것인가에 대한 고민을 이 Ajax View 프로젝트가 도움을 줄 것 같습니다. 이 기술을 중심으로 파생되어 AJAX Performance Profiling, Monitoring 기술의 기반이 되는 것 같습니다. 자세한 내용은 위의 사이트 링크를 참고하세요.

   

Automated Test Generation (ATG)

일찍이 Microsoft 는 1990년대 이후부터 테스팅 기술에 대한 연구를 꾸준히 해온 정통 소프트웨어 기업입니다. 코드 레벨의 테스트는 물론이며, Windows 95 시절에 지원되기 시작한 Plug-and-Plug(하드웨어를 꽂으면 인식하는 기술) 등 상상하지도 못했던 많은 기능을 자동화 테스팅한 기업이기도 합니다. 지금 우리 세대에서 맛보고 있는 테스팅 기술은 Microsoft 의 실제 내부의 기술과는 매우 격차가 있지요. (인정합니다.^^;)

처음 공식적으로 나온 White Box Automation Test 도구인 PEX 가 Visual Studio 2008 시절부터 나오긴 하였지만, 완성된 기술은 아니며 계속 발전하는 기술입니다. PEX 와 관련하여 온라인 세미나를 찍은 것이 있는데 못찾겠군요.;; 대신 아래의 테스팅과 관련된 내용을 참고 하세요.

[ALM-Test] 6. Load Runner vs Visual Studio 2010 테스팅 비교 분석 - http://willstory.tistory.com/4 제공
[ALM-Test] 5. 테스트 계획
[ALM-Test] 4. 테스터(SDET) 의 역할
[ALM-Test] 3. 테스터에 대한 오해와 진실
[ALM-Test] 2. 왜 단위 테스트를 해야 하는가? [2]
[ALM-Test] 1. 왜 단위 테스트를 해야 하는가? [1]
[Testing] Moq.NET (T/B Driven Development)
[Testing] BDD (Behavior-Driven Development–행위 주도 개발)
[Testing] TDD (Test-Driven Development-테스트 주도 개발)

아무튼 이런 테스팅을 위해서 Dynamic Proxy 기술과 Dynamic MSIL Injection 같은 기술이 필요한데, 이미 이런 부류의 닷넷 기술이 존재하긴 합니다. 그 중에 대표적인 것이 Microsoft.CCI 와 Code Contract, Castle Dynamic Proxy, Mono Cecil, Moles 등등등…

하지만 이번 이 프로젝트는 이 기반 기술 들을 통합하려는 의지를 보이는 것 같습니다. 개인적으로 굉장히 기대를 하고 있는 프로젝트이기도 합니다.

   

Code Contracts

http://research.microsoft.com/en-us/projects/contracts/

Code Contracts 는 이미 유명한 기술입니다. 초기에 Microsoft Research 프로젝트로 진행 중이다가 Visual Studio 2008 시절에 릴리즈가 되었으며, .NET Framework 4.0 와 Visual Studio 2010 에는 아예 탑재 시켜버렸습니다. Code Contract 를 직역하면 코드 계약(Code Contract) 인데, 코드간의 명확한 명세를 코드 레벨에서 작성하는 것입니다. 이것도 예전에 온라인 세미나를 했었는데 못찾겠군요;;

명확한 코드 계약이 왜 필요하냐…? 라고 물으신다면 당시 세미나에서 예시를 든 것이, "당신이 회사를 다닌다면 회사와 계약을 합니다. 계약서에는 연봉 정보도 있고, 근태 규칙도 있고 여러 가지가 있습니다..." 마찬가지로 내가 만든 코드를 누군가 써야 할 때 바로 그 명세가 되는 것이 Code Contract 입니다.

이 기술로 파생될 수 있는 기술은 상당히 많습니다. 명확하게 코드를 계약하게 되면 테스트에 굉장히 용이하며, 더 나아가 자동화 테스트(PEX 와 같은)에서 훨씬 여유로워 집니다. 그리고 정적 분석(Static Analytics) 기술과 접목하여 잠재적인 코드의 계약 관계를 파악하여 미리 경고나 오류를 발생해 줄 수 도 있고요.

하지만 저의 경우는 그리 톡톡히 효과를 보지는 못했습니다. 왜냐하면 명확한 계약은 1:1 계약에서 효과가 있지만, 1:N, N:N 간의 계약에서는 그 계약 조건이 명확해 질 수가 없습니다. 현재 나온 PEX 기술과 Code Contract 를 조합하여 계약을 파생시키는 기술적인 부분이 부족하며, 계약의 제약 조건 등 아직은 적극적으로 사용하기에는 부족해 보입니다.

하지만 이 기술을 근간으로 하여 더 효과적인 많은 방법들이 위의 Automated Test Generation (ATG) 프로젝트 등으로 활발히 연구 중이며, 앞으로도 지속적으로 관심을 가질 기술은 분명합니다.

   

Composable Virtual Earth

http://research.microsoft.com/en-us/projects/cve/

제가 설명드릴 만큼 깊이 이해를 못하고 있기 때문에, 참고하세요^^; 중요한 것은 이미지 프로세싱 등의 기술로 효과적으로 운용을 하고자 하는 것 같습니다.

   

DryadLINQ

http://research.microsoft.com/en-us/projects/dryadlinq/

이 프로젝트는 C#의 LINQ+Parallel 기술을 접목하여 분산된 데이터의 접근성을 극대화한 기술입니다. 이미 잘 알고 있는 LINQ 와 .NET 4.0부터 제공되는 TPL(Task Parallel Library)를 이용하여 단순한 분산 데이터에 접근하는 방법입니다. 기존의 LINQ to SQL, Entity Framework 과 같이 단일 데이터 소스가 아닌 클러스터링 된 분산 데이터에 대상이 됩니다.

이 프로젝트는 기존에 존재하는 기술을 접목하여 새로운 기술의 탄생의 근원이기도 합니다. 하지만 생각해보면 분산된, 클러스터링된 데이터를 왜 DryadLINQ 를 써야 할까. 그만큼 대규모의 데이터면 '데이터베이스 관리자를 따로 둘텐데' 말이죠.

제 짧은 소견으로는 분명히 이 기술은 Microsoft Cloud 기술인 Azure 에 접목될 가능성이 농후합니다. 즉, Azure 기반의 클라우드 기술을 엔터프라이즈(Enterprise) 급으로 끌어올릴 수 있는 전략적인 기술이기도 합니다. 이런 부분에서 아직 Azure 는 완성된 기술은 아닙니다. 계속 발전하는 기술이지…

   

Doloto

http://research.microsoft.com/en-us/projects/doloto/

이 프로젝트는 정말인지 기대가 됩니다. 아까 말씀 드린 'Ajax View' 프로젝트와 연관이 있어 보이지만, 이 프로젝트는 나름대로 효과를 톡톡히 보여줄 것 같습니다.

문제는 Web 2.0 은 말씀 드린대로 AJAX 기술과 떨어질 수 없는 관계이기도 합니다. 그런데 데이터 처리를 서버&클라이언트로 분산하면서 결국은 서버를 거치게 되고, 원치않던 라운드 트립은 증가하게 되고, 결국은 사용자의 사용성(광범위한…)은 저하될 수 있습니다. 뭐가 문제일까요? AJAX 가 문제일까, Web 2.0 이 문제일까, 코드가 문제일까, 시스템이 문제일까….

Doloto 는 과분하게도 이런 문제를 큰 고민 없이 해결해 줍니다. 아래는 그래프는 Doloto 를 적용하면 대략 50%에 근사하게 성능이 개선되는 수치입니다. 성능을 개선하기 위해 특별히 코드를 변경할 필요도 없다고 합니다. 그렇다면 연관된 기술은 서버 코드/클라이언트 코드 분석 기술 이외에 캐싱(Caching) 일 텐데…

일단 기대가 됩니다.

   

ExtendedReflection - Dynamic Analysis Framework for .NET

http://research.microsoft.com/en-us/projects/extendedreflection/

이 기술은 'Automated Test Generation (ATG)' 과 없지 않아 연관이 될 수 있을 것 같습니다. 여기에서는 분석 도구라고 설명하지만, 이런 Low-Level의 구현이 가장 잘 되어 있는 프레임워크는 Mono.Cecil입니다. 그러고 보면 약간은 중복성이 있어 보이는 프로젝트이기도 합니다.

적어도 Microsoft.CCI 는 그렇다쳐도 Microsoft.Unity.ObjectBuilder, Castle.DynamicProxy와 Mono.Cecil은 .NET 오픈 소스 중에 가장 대표적인 Dynamic Proxy 및 MSIL 기술인데, 어찌될지 그냥 지켜보고 있습니다.

단순히 기존 존재하는 오픈 소스 대체용도인지, 다양한 기술을 접목하고자 하는 진정한 프레임워크 기반 기술인지는 두고 볼 일입니다.

   

F#

http://research.microsoft.com/en-us/projects/fsharpproj/

깜놀하셨죠? 바로 F# 도 Microsoft Research 에서 태생한 언어입니다. 그냥 그렇다구요^^

   

Graphical tools for text analysis

특별히 아래의 그림만으로 이해하시리라 믿고, 패스!

   

HD View

예전에 Silverlight 의 딥줌(DeepZoom) 을 기억하십니까? 저는 사실 결과물에 대해 다른 것은 없지만, 뭔가 이미지 프로세싱 측면에서 다른 접근 방식을 가지고 가는 것 같습니다. 워낙 자료도 적어서 뭐라고 설명 드리기는 힘들 것 같아요.

다만, 아래의 그림을 보시면 딥줌과 유사하지만, 그래도 유사할 것 같아요^^… 어떤 알고리즘인지가 궁금할 뿐;;

   

HD View SL

위의 'HD View' 의 실버라이트 버전입니다. 참고^^

   

   

정리

Microsoft 는 소프트웨어 개발 기업으로 세계에서 1위 기업입니다. 그 중, Microsoft Research 프로젝트는 여러분들에게 오픈된 프로젝트일 뿐이며, 내부적으로 더 많은 연구가 계속되고 있습니다. 예를 들어, http://codeplex.com 은 여러분들에게 공개된 오픈 소스 커뮤니티지만, Microsoft 내부에는 더 많은 프로젝트들이 수백 개씩 오픈 되어 있습니다. (제가 어떻게 아냐구요? Microsoft 직원이 쓴 책에 그렇다고 말하더군요^^)

다만, 그 중에서 저희에게 오픈된 기술 R&D 영역이 Microsoft Research 프로젝트입니다. 그리고 관심이 없으셔도 상관은 없답니다. 최근 기술 트랜드는 너무나도 빨리 나오고, 변하기 때문에 모두 따라가기가 벅차기도 합니다. 그리고 이 모든 것을 자세하게 알 수 없게 되었습니다. 중요한 것은 내가, 여러분들이 받아들일 기술/트랜드를 준비할 수 있겠지요. 모르고 아는 것과 알고 아는 것은 상당히 다릅니다.

직접적으로 이런 기술들이 나에게는 관련이 없지만, Microsoft 는 비즈니스/웹/시각화/클라우드에 지속적으로 시도를 하는 것을 알 수 있으며, 장차 알게 모르게 도움이 될 거라고 믿습니다. 그리고 이 서비스/기술을 이용하는 사람은 여러분들이 될 수도….^^

Posted by 땡초 POWERUMC

댓글을 달아 주세요

Mitsuru 블로그에 재미있는 LINQ 문제가 올라와 있네요..
 
문제는
 
var values1 = newstring[] { "1", "2", "3" };
var values2 = newstring[] { "A", "B" };
 
var q = ?
 
foreach (var v in q)
    Console.WriteLine(v);

 
 
뭐.. 내가 볼땐 조인하라는 말 같은데,, 저는 나름대로 아래와 같이 풀어 보았습니다.

답은 여러가지 나올 수 있겠죠? 같이 푸실 분은 아래를 보지 마시고, 원문 먼저 보시고 맞춰보세요 ^^

 
원문
 














 
var values1 = new string[] { "1", "2", "3" };
var values2 = new string[] { "A", "B" };
 
var q = from a in values1
               from b in values2
               orderby a+b ascending
               select a+b;
 
foreach (var v in q)
        Console.WriteLine(v);
 
실제 쿼리문만 보시면 될 것 같네요. 허벌나게 간단하죠?
 
엇. 쓰고보니, 두 번째 댓글에 비스므리 하게 답을 누가 달아놓았네요 +_+;
그래도 여기까지 쓴게 아까워서…
 
텨텨 =3=3=3
Posted by 땡초 POWERUMC
TAG LINQ, Quiz

댓글을 달아 주세요




 
소스 코드 다운로드시 코드의 Naver Open API Key 를 변경하여 사용하시기 바랍니다.

드디어 5회차까지 왔네요. 이전까지는 Custom LINQ Provider 를 만들기 위해 몸풀기 과정이었다면, 이번 시간에는 이것을 응용한 LINQ To Naver Open API 를 만들어 볼 것입니다. 아직 Custom LINQ Provider 에 대한 품새가 잘 그려지지 않는다면, 이번 예제를 통해 왜 Custom LINQ Provider 가 유용한 지에 대해 잘 알 수 있을 거라고 생각합니다.
 




 
LINQ To Naver Open API

우선 이 LINQ To Naver Open API 를 만들면서 몇 가지 고민을 하게 되었습니다.
 
1.      Naver Open API 는 조건 검색을 지원하지 않는다.
2.      query 파라메터를 통해 where 절의 조건을 넣게 되면, 수많은 쿼리 parsing 이 필요하다. 그렇기 때문에 구현하기 위해 좀 더 많은 시간이 필요하다.
3.     query 를 검색 한 후에, 검색 결과에 대해 where 조건을 만족시킨다. 현재 이 방법으로 구현되었으며, 기본적인 Lambda 식을 이용하기 때문에 구현하기 쉽다
 
.현재 3번의 방법으로 구현이 되었습니다. 그럼 어떤 모습으로 구현되었는지 한번 볼까요?
 
 
어떤가요?? 굉장히 편해하지 않습니까? 다음과 같이 블로그 검색도 지원합니다.
 
 
아래는 비디오 검색을 사용하고, 그 결과를 보여줍니다.
 
 
예제로 보여드리는 이 LINQ To Naver Open API 는 다음과 같은 검색을 지원합니다.
 
1.      NaverOpenAPIModel.Blog – 블로그 검색
2.      NaverOpenAPIModel.Kin – 지식in 검색
3.      NaverOpenAPIModel.WebKR – 웹 문서 검색
4.      NaverOpenAPIModel.Cafe – 카페 검색
5.      NaverOpenAPIModel.Shop – 쇼핑 검색
6.      NaverOpenAPIModel.Encyc – 백과사전 검색
7.      NaverOpenAPIModel.Krdic – 국어사전 검색
8.      NaverOpenAPIModel.Jpdic – 일어사전 검색
9.      NaverOpenAPIModel.Endic – 영어사전 검색
10.   NaverOpenAPIModel.News – 뉴스 검색
11.   NaverOpenAPIModel.Local – 지역검색
12.   NaverOpenAPIModel.Video – 동영상 검색
13.   NaverOpenAPIModel.Image – 이미지 검색
 
이중 책 검색과 실시간 검색어는 Request XML 스키마가 조금 달라서, 귀찮아서 빼버렸습니다 ㅡ,.ㅡ;
 
Context 클래스를 제네릭 클래스로 변경하면서 쉽게 LINQ 쿼리를 만들 수 있습니다.
 
 
쓸만한 LINQ To Naver Open API 가 되지 않았나 생각합니다.
이번 포스팅은 코드가 한 줄도 없는 포스팅이 되었네요. 코드는 첨부파일을 참고하시면 될 것 같고, 부수적인 내용은 1,2,3,4 회차 포스팅을 참고 하시면, 특별한 설명이 없어도 될 것 같습니다.
 
 
연재를 마치며…
 
처음에 외국 문서를 그냥 번역 또는 각색해서 올릴까 라고 생각했었지만, 제대로 된 Custom LINQ Provider 를 직접 손대기에는 난이도가 높았습니다. 외국 문서에 비해 비록 디테일이 떨어지지만, LINQ 의 내부를 훓어 보기엔 충분하다고 생각이 듭니다.
 
첨부된 LINQ To Naver Open API 소스코드를 참고하시면, 실제 웹 서비스에서 적용해도 될 만큼 다양한 LINQ Provider 가 나올 수 있길 기대해 봅니다. ( 다양한 테스트는 하지 못해서, 버그가 있을 수도 있습니다 )
 
그 동안 읽어주신 분들께 감사드립니다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
이번에는 쿼리를 이용하여 원격 개체 탐색을 하는 방법에 대해서 알아보겠습니다. 이 파트는 마치 LINQ To SQL 과 비슷하긴 하지만, 원격 개체라는 것의 대상을 SQL 서버에만 두는 것이 아니라는 것을 명심하셔야 합니다. 이번 예제는 SQL 서버를 이용하여 쿼리를 탐색하는 것이지만, 이 다음 파트인 LINQ To Naver Open API 를 보시면, 다양한 원격 개체에 접근 할 수 있다는 것을 알 수 있을 것입니다.

 
 
SampleContext 에 쿼리 Log 프로퍼티 추가
                                                  
SampleContext 의 소스는 2회차의 소스와 똑같습니다. 다만, 원격 탐색을 하기 위해 질의식을 어떻게 만들었는지 알 수 있도록 Log 프로퍼티를 추가합니다. 원격 서버에 원하는 데이터를 가져올 수 있도록, 질의를 해야 하는데, 그것이 SQL 서버면, SQL 쿼리식이 될 것이고, 또는 WMI 통한다라고 하면, WMI 쿼리식이 될 것입니다.
 
public class SampleContext : IQueryable<Person>
{
    // 생략
 
       public string Log
       {
             get { return this.provider.sbLog.ToString(); }
       }
}
 
 
SampleProvider 의 프로퍼티 추가와 Visitor 클래스 만들기
 
아래의 StringBuilder 는 쿼리식을 만들기 위한 객체입니다.
 
public class SampleProvider : IQueryProvider
{
    // 생략
 
       public StringBuilder sbLog = new StringBuilder();
}
 
그리고 IProvidor 의 Execute<T> 메서드의 내용을 변경하고자 합니다. 우선 테스트용으로 이렇게 작성하였고, 실제 원격 개체에 연결하기 위해 이후에 다시 이 메서드의 코드는 변경할 예정입니다.
 
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());
 
       TranslateExpression trans = new TranslateExpression();
       sbLog.Append( trans.Translate(expression) );
 
       return (TResult)r.GetEnumerator();
}
 
이전 소스와 비교해 보았을 때, 약간 틀린 점이 있습니다.
 
TranslateExpression trans = new TranslateExpression();
sbLog.Append( trans.Translate(expression) );
 
바로 이 부분인데, TranslateExpression 클래스는 C# 3.0 의 쿼리식을 실제 원격 서버에서 질의 할 수 있는 쿼리식으로 변경하는 클래스입니다. Translate() 메서드를 통해 LINQ 식을 텍스트로 변환하는 것입니다.
 
Visitor 패턴이란?
패턴을 구분할 때 Visitor 패턴은 행위 패턴에 속합니다. 간접적으로 클래스에 다형적인 기능을 추가합니다. 즉, 새로운 클래스를 많이 추가하기를 원치 안을 경우 선택하면 좋은 대안이 될 수 있습니다.
 
 
TranslateExpression 클래스
 
소스코드를 전체로 보면 좋겠지만, LINQ 의 내부를 살펴보는 기초적인 포스팅이니, 메서드별로 구분하여 설명 드리고자 합니다.
 
TranslateExpression 클래스의 맴버와 생성자는 다음과 같습니다. StringBuilder 의 sb 맴버는 하나하나의 식을 분석하여 쿼리를 만들 것입니다. 그리고 생성자의 expression 은 LINQ 의 표현식이겠죠?
 
생성자
 
public class TranslateExpression
{
       private StringBuilder sb   = new StringBuilder();
 
       public string Translate(Expression expression)
       {
             this.Visit(expression);
 
             return sb.ToString();
       }
}
 
Visit 메서드
 
Visit 메서드는 이 클래스에서 상당히 중요한 부분입니다. 위에 말씀드린 Visitor 패턴을 구현하고 있지요. Visitor 패턴은 다른 패턴과 유사한 점이 많기 때문에, 이것은 Interpreter 패턴처럼 보일 수도 있고, 확장한다면 Composite 패턴과도 같아 보일 수 있습니다. 하지만 패턴은 코드의 내용 보다는 관점을 어떻게 보느냐가 더 중요합니다.
 
Expression 클래스는 ExpressionType 의 열거형 맴버를 가지고 있습니다. 현재의 Expression 이 어떤 표현식을 가지고 있는지 명확히 나타내고 있습니다. 적당히 쿼리가 만들어 질 수 있는 정도만을 구현하였기 때문에 JOIN 이나 GROUPING 기능은 수행할 수 없답니다.
 
protected Expression Visit(Expression expression)
{
       switch (expression.NodeType)
       {
             case ExpressionType.Call:
                    return this.VisitCall((MethodCallExpression)expression);
 
             case ExpressionType.Constant:
                    return this.VisitConstant((ConstantExpression)expression);
 
             case ExpressionType.Lambda:
                    return this.VisitLambda((LambdaExpression)expression);
 
             case ExpressionType.MemberAccess:
                    return this.VisitMember((MemberExpression)expression);
 
             default:
                    throw new Exception( string.Format("{0} 지원하지않습니다", expression.NodeType));
       }
}
 
VisitCall 메서드
 
이 메서드는 MethodCallExpression 표현식을 분석합니다. LINQ 식의 모든 C# 메서드는 이것의 대상이 되는 것입니다.
 
MethodCallExpression 의 메서드는 하나 이상의 상수나 변수를 포함하거나, 리턴 타입이 있어야 합니다. 즉, void 형의 C# 메서드는 LINQ 식에 포함이 될 수 없습니다.
 
 
protected virtual Expression VisitCall(MethodCallExpression mce)
{
       switch (mce.Method.Name)
       {
             case "Where":
                    sb.Append("SELECT * FROM").Append( Environment.NewLine );
                    this.Visit(mce.Arguments[0]);
                    sb.Append(" AS T WHERE ");
 
                    UnaryExpression ue         = mce.Arguments[1] as UnaryExpression;
                    LambdaExpression le        = ue.Operand as LambdaExpression;
 
 
                    BinaryExpression be        = le.Body as BinaryExpression;
            if (be != null)
                this.VisitBinary(be);
                           break;
 
             case "StartsWith":
                    this.Visit(mce.Object);
                    sb.AppendFormat(" LIKE '{0}%'", mce.Arguments[0].ToString().Replace("\"",""));
                    break;
       }
 
       return mce;
}
 
mce.Arguments[1]
 
를 디버깅 해보면,
 
{r => (((r.Age >= 20) && (r.Age <= 30)) && r.Name.StartsWith("엄"))}
 
위와 같이 마치 람다식과 같이 생겼습니다. 하지만, 이것의 NodeType 은 Lambda 가 아닌 Quote 입니다. Quote 는 상수값이 포함된 표현식입니다. 이것의 피연산자를 람다표현으로 바꾸는 구문이
 
UnaryExpression ue         = mce.Arguments[1] as UnaryExpression;
LambdaExpression le        = ue.Operand as LambdaExpression;
 
이렇게 되고, BinaryExpression 으로 다시 표현이 가능할 경우, BinaryVisit 을 수행하게 됩니다.
 
그리고,
 
case "StartsWith":
       this.Visit(mce.Object);
       sb.AppendFormat(" LIKE '{0}%'", mce.Arguments[0].ToString().Replace("\"",""));
       break;
 
위 코드는 StartsWith 의 메서드를 SQL 쿼리식과 같이 LIKE ‘xxx%’ 처럼 바꾸는 역할을 하게 됩니다.
 
VisitLambda 메서드
 
VisitLambda 메서드는 LambdaExpression 을 분석합니다. LambdaExpression 은 Body 속성이 있으며, 이 Body 는 여러가지의 NodeType 이 올 수 있습니다. 현재 소스에서는 특별한 기능을 하지 않습니다.
 
protected virtual Expression VisitLambda(LambdaExpression le)
{
       return le;
}
 
 
VisitConstant 메서드
 
이 메서드는 ConstantExpression 을 분석합니다. 신기하게도 잘 살펴보면 이 ConstantExpression.Value 는 SampleContext 를 참조하고 있습니다. 이놈은 테이블을 참조 하고 있지만, Constant 로 가장하고 있습니다. 이 ConstantExpression 의 ElementType 을 가져와서 매핑되는 테이블로 변환해 줍니다.
 
protected virtual Expression VisitConstant(ConstantExpression ce)
{
       IQueryable q = ce.Value as IQueryable;
       if (q is IQueryable)
       {
             sb.AppendFormat(" ( SELECT * FROM {0} ) ", q.ElementType.Name)
                    .Append( Environment.NewLine );
       }
       else
       {
             sb.AppendFormat(" {0} ", ce.Value);
       }
 
       return ce;
}
 
그래서 ConstantExpression 은 IQueryable 로 가장하고 있지 않을 경우, 일반적인 상수값으로 취급할 수 있습니다.
 
 
VisitMember 메서드
 
이 메서드는 MemberExpression 의 표현을 분석합니다. LINQ 쿼리식의 맴버는 모두 여기에 해당됩니다.
 
protected virtual Expression VisitMember(MemberExpression me)
{
       sb.Append(me.Member.Name);
 
       return me;
}
 
예를 들어,
 
var result = from r in context
                     where r.Age >= 20 && r.Age <= 30 && r.Name.StartsWith("")
                     select r;
 
와 같은 식의 MethodCallExpression 은 Where 절이 될 것이고, 이곳의 람다 표현식으로 각각의 연산식을 분석해 보면,
 
각각의 BinaryExpression 은
 
r.Age >= 20
r.Age <= 30
r.Name.StartsWith("")
 
이 됩니다. 이 BinaryExpression.Left 는 r.Age 로 표현이 되지만, 우리가 변환할 쿼리식에서 “r.Age” 의 “r.” 은 필요가 없습니다. 때문에, MemberExpression 의 Member.Name 을 통해 “Age” 와 같이 오직 맴버 이름만을 표현하도록 하고 있습니다.
 
VisitBinary 메서드 ( Update 2008/03/27 - 오타 수정 )
 
이 메서드는 BinaryExpression 의 표현을 분석합니다. BinaryExpression 의 Left/Right 를 구현하고 있으며, 이 두 피연산자를 쪼개어내어 결합하는 기능을 하고 있습니다.
 
중요한 것은 BinaryExpressoin 의 Left/Right 는 그 안에 또 다른 BinaryExpression 을 포함할 수 있습니다.
 
var result = from r in context
                     where r.Age >= 20 && r.Age <= 30 && r.Name.StartsWith("")
                     select r;
 
와 같은 식의 BinaryExpression 은
 
be.Left = {((r.Age >= 20) && (r.Age <= 30))}
be.Right = {r.Name.StartsWith("엄")}
 
가 될 수 있습니다. 때문에, 위의 각각의 Left/Right 대한 VisitBinary 를 수행해야 합니다. 즉,재귀호출과도 같죠.
 
protected virtual Expression VisitBinary(BinaryExpression be)
{
       if (be.Left is BinaryExpression)
             this.VisitBinary((BinaryExpression)be.Left);
       else
       {
             this.Visit(be.Left);
       }
 
    switch (be.NodeType)
    {
        case ExpressionType.GreaterThan:
            sb.Append(" > ");
                    break;
 
        case ExpressionType.GreaterThanOrEqual:
                    sb.Append(" >= ");
                    break;
 
             case ExpressionType.LessThan :
                    sb.Append(" < ");
                    break;
 
             case ExpressionType.LessThanOrEqual:
                    sb.Append(" <= ");
                    break;
 
        case ExpressionType.Equal:
            sb.Append(" = ");
                    break;
 
             case ExpressionType.And:
             case ExpressionType.AndAlso:
                    sb.Append(" AND " );
                    break;
 
             case ExpressionType.Or:
                    sb.Append(" OR ");
                    break;
 
             default:
                    throw new Exception( string.Format("{0} 형식은지원하지않습니다", be.NodeType) );
    }
 
       if (be.Right is BinaryExpression)
             this.VisitBinary((BinaryExpression)be.Right);
       else
       {
             this.Visit(be.Right);
       }
 
    return be;
}
 
BinaryExpression.NodeType 은 기본적인 덧셈(+),뺄셈(-) 외에도 &&, ||, 또는 비트연상 등의 연산도 포함될 수 있습니다. 특히, &&, || 와 같은 조건식을 AND,OR 로 변환해 주어야 할 필요가 있습니다.
 
 
한번 프로그램을 실행해 볼까요?
 
다음과 같은 코드입니다.
 
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 && r.Age <= 30 && r.Name.StartsWith("")
                      select r;
 
             result.ToList().ForEach( o=>Console.WriteLine(o.Name ));
 
             Console.WriteLine("----------------------------");
             Console.WriteLine( context.Log );
       }
 
결과는 다음과 같습니다.
 
엄준일
엄호희(내동생)
----------------------------
SELECT * FROM
 ( SELECT * FROM Person ) AS T
 WHERE Age >= 20 AND Age <= 30 AND Name LIKE '엄%'
계속하려면 아무 키나 누르십시오 . . .
 
어떻습니까? 제법 쓸만한 QueryProvider 가 되었지요?
 
 
원격 서버에 연결
 
우리는 원격 서버를 MS-SQL 서버로 실습을 할 것입니다. 실습을 위해 테이블을 만들고, 테스트 데이터를 만들도록 하겠습니다.
 
Person 클래스와 같은 스키마와 실습 데이터를 넣었습니다.
 
CREATE TABLE Person
(
[Name] VARCHAR(50) NOT NULL,
[Age] INT NOT NULL
)
 
INSERT INTO Person(Name,Age) VALUES ('엄준일',29)
INSERT INTO Person(Name,Age) VALUES ('엄호희(내동생)',26)
INSERT INTO Person(Name,Age) VALUES ('엄혜진(울누나)',31)
INSERT INTO Person(Name,Age) VALUES ('멍멍이',6)
INSERT INTO Person(Name,Age) VALUES ('발발이',5)
 
그리고 SampleContext 의 GetEnumerator<Person> 메서드를 다음과 같이 수정하였습니다.
 
public IEnumerator<Person> GetEnumerator()
{
       provider.Execute<IEnumerator<Person>>(this.expression);
 
       SqlConnection cn           = new SqlConnection("Server=xxxx.kr;DataBase=xxxx;UID=xxxx;PWD=xxxx");
       SqlCommand cm              = new SqlCommand( Log, cn);
       cm.CommandType                   = System.Data.CommandType.Text;
 
       cn.Open();
       Console.WriteLine("DataBase Connection!");
 
       SqlDataReader reader       = cm.ExecuteReader();
                   
       List<Person> list          = new List<Person>();
       while (reader.Read())
       {
             list.Add( new Person {
                    Name   = reader["Name"].ToString(),
                    Age    = Convert.ToInt32(reader["Age"])
             });
       }
 
       reader.Close();
       cn.Close();
 
       return list.GetEnumerator();
}
 
위와 같이 실제 데이터베이스를 연결하여 쿼리를 수행하도록 하였습니다.
 
결과는 다음과 같습니다.
 
DataBase Connection!
엄준일
엄호희(내동생)
----------------------------
SELECT * FROM ( SELECT * FROM Person ) AS T WHERE Age >= 20 AND Age <= 30 A
ND Name LIKE '%'
계속하려면아무키나누르십시오 . . .
 
이 과정에서 변경된 소스는 특별히 자세한 설명이 필요 없을 것 같아서 첨부된 소스코드를 참고 하시기 바랍니다.
 

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
지난 시간에 이어, 이번 시간에는 실제 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 통해 원격 개체를 탐색하는 방법에 대해 살펴 보도록하고, 이만 마치겠습니다. 이번 한주도 즐겁게 시작하시기 바랍니다^^//

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 

이전 시간에 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

댓글을 달아 주세요




 
 
LINQ 의 출연
 
정말 .NET 이라는 세상이 많이 좋아진 것 같습니다. 강력한 Visual Studio 2008 이란 개발 툴에 차세대 프레임웍인 .NET Framework 3.5 로 아직까지 그것의 모두를 채 맛보지 못했습니다. C# 3.0 의 여운이 가시기도 전에 Silverlight 2.0 Beta 의 출시로 다시 한번 RIA 업계를 강타하였고, 올해는 너무나도 할게 많은 한 해가 될 것 같습니다.
 
C# 3.0 의 특징은
l Lambda Expression ( 람다식 )
l Object Initialize ( 객체 초기화 )
l Extension Methods ( 확장 메서드 )
l Anonymous Type ( 익명 타입 )
l 등등…
 
바로, C# 3.0 의 LINQ 는 이러한 람다식이라든지, 확장 메서드 등을 이용한 종합 예술이라고 생각합니다. 때문에, 위의 C# 3.0 의 특징을 이해하지 못한다면, LINQ 를 효율적으로 사용하기에 한계가 있을 것입니다. 때문에, 위의 C# 3.0 의 새로운 기능에 대해서 아직 잘 모르시는 분은 의 C# 3.0 을 참고하십시오.

 
 
LINQ Provider
 
Provider 란 사전적 의미로 “공급자” 정도로 해석할 수 있습니다. LINQ 는 Language Integrated Query 로써, 언어 통합 쿼리를 뜻합니다. 그럼, LINQ Provider 는 쿼리식과 같은 언어적 구문을 해석할 수 있는 공급자라고 생각하시면 됩니다.
 
.NET 이 제공하는 LINQ Provider 는 다음과 같습니다.
l LINQ To Objects
l LINQ To Entities
l LINQ To SQL – MS SQL 만 지원함
l LINQ To XML
 
바로 LINQ(Language Integrated Query ) 는 이렇게 다양한 Provider 들을 서로 다른 쿼리식이 아닌, 통합된 쿼리식으로 C# 3.0 에서 사용할 수 있답니다.
특히, LINQ To SQL 은 Visual Studio 에 함께 통합되어, 이전에 Typed DataSet 과 같이 Drag&Drop 을 지원하며, 자동으로 Entity 클래스를 생성하고, 이것에 대한 Context 객체를 생성해 줌으로써 Typed DataSet 과 Entity 객체의 장점을 모두 갖추며, 더욱 강력한 언어적 기능을 제공해 줍니다.
 
하지만, 이것은 단지 시작에 불과합니다. 기본적으로 제공하는 LINQ Provider 는 몇 개 되지 않지만, 개발자가 직접 LINQ Provider 를 만듦으로써 모든 대상을 통합된 쿼리식을 사용할 수 있습니다. 이토록 경이로울 수가… +_+;
 
 
Custom LINQ Provider
 
이미 해외의 많은 개발자들이 Custom LINQ Provider 를 만들어 내놓았습니다.
대표적인 사례는… ( 님 로그 참조 )
 
 
정말 대단합니다. LINQ To SQL 에 이은 LINQ To Oracle 과 LINQ To WMI, LDAP, Active Directory 등등… 대부분이 Open Source 이기 때문에, 그냥 가져다 쓰기만 하면 될 정도입니다. 이런걸 보고 있자면, 정말 우리(?)보다 훨씬 앞서가고 있는 개발자들이 태반이네요 ㅋ.ㅋ;;
이미 .NET Framework 3.5 Beta 버젼부터 나온 소스들도 있기 때문에, 소스중 현재 정식 .NET Framework 3.5 와 Namespace 체계가 다른것도 있습니다. 조금만 수정해 주시면, 위의 소스들을 컴파일 하시는데 어렵지 않으실 겁니다.
 
하지만, 저도 뭐하나 해봐야 할 것 같긴한데… 다음시간에 Custom LINQ Provider 를 살짝 만들어 보겠습니다. 기대는 하지 말아 주세요~ ^^;
Posted by 땡초 POWERUMC

댓글을 달아 주세요

이미 C# 3.0 에서 LINQ to Sql 란 말은 자주 들어 보았을 것이다. 많은 세미나 또는 블로그 포스트에서 LINQ to Sql 의 쿼리가 MSSQL 프로필러를 통해 실행되는 모습을 익히 보았을 것이다.
하지만 LINQ to Sql 을 이용하여 쿼리가 되는 것을 기록할 필요가 있다. 쿼리가 수행되는 시간, 쿼리 되는 빈도나 부하 등을 유지보수 하기 위해서 반드시 이 쿼리들이 로그에 기록되어야 한다.
 
그럼 오늘은 이 LINQ to Sql 이 어떻게 로그를 남기고 어떻게 로그에 기록하면 될지 알아보자.
 
먼저 콘솔 프로젝트를 만들어보자.
우선 LINQ to Sql 항목을 추가하고, [그림1] 과 같이 서버탐색기를 통해서 데이터베이스의 테이블을 끌어놓자. 여기서 사용하는 테이블은 UmcBlog 실제 웹서버 테이블을 끌어놓아 보았다 +_+
 
[
그림1] 서버탐색기를 통해 테이블을 추가함.
 
콘솔 프로젝트의 소스는 아래와 같이 무척 간단하다.
 
[
그림2] 콘솔 프로젝트 소스
 
그리고 데이터베이스의 DataContext 객체를 생성하면, 위와 같이 Log 프로퍼티가 제공될 것이다. Log 프로퍼티는 Stream 을 받을 수 있는 프로퍼티이다. Console.Out 을 통해 Console Output Stream 을 넣어보았다.
 
[
그림3] 콘솔 프로젝트 실행 결과
 
Console Output Stream 을 통해 LINQ to Sql 쿼리는 SQL Server 의 프로필러로 보내지는 쿼리를 콘솔에서도 확인할 수 있다.
 
그렇다면 이 Stream 을 텍스트 파일로 Log 를 남기는 것은 생각보다 간단하다.
아래는 웹 프로젝트로 만든 소스이다.
 
[
그림4] 웹 프로젝트 소스
 
[
그림5] 웹 프로젝트 실행 결과
 
[
그림6] Log.txt 에 LOG 가 남겨진다.
 
 
서버가 실행되는 위치에 StreamWriter 객체를 이용해 “Log.txt” 라는 텍스트 파일로 남길 수가 있다.
 
writer.Close()
 
라는 맨 마지막 줄의 빨간 밑줄이 보일것이다. 바로, Stream 을 반드시 닫아 주어야 한다. 그렇지 않으면 로그 파일을 열려있는 채로 프로세서가 잡아놓고 있을 테니 말이다.
UmcBlogDataContext 와 리플랙터로 DataContext 를 확인한 결과 Log 프로퍼티로 제공되는 Stream 은 닫혀지지 않기 때문에 반드시 Stream 을 닫아 주어야 한다.
 
그렇다면 개발자는 매번 이 Stream 을 생성하고 닫아주는 코드를 작성해야 하는 것일까? 그렇지 않다.
이 로그를 자동화 하는 방법이 있다.
 
[
그림7] UmcBlogDataContext 의 Base Class 지정하는 속성창
 
위와 같이 친절하게도 LINQ to Sql 디자이너는 생성된 DataContext 개체의 Base Class 를 지정할 수 있도록 되어 있다. 그럼 우리는 Base DataContext 가 될 수 있는 클래스를 이곳에서 상속받아 로그의 기록을 자동화 하도록 만들면 된다. 

음.. 다음에 기회가 된다면 DataContext 를 상속받아 로깅하는 간단한 예제를 만들어 보도록 하자. 텨텨텨 =3=3=3

Posted by 땡초 POWERUMC

댓글을 달아 주세요

LINQ QUIZ

.NET/C# 2007.11.05 00:44 |
LINQ QUIZ
 
간단한 퀴즈를 풀면서 LINQ 에 대한 지식을 테스트 합니다.
모든 샘플은 다음과 같은 네임스페이스가 선언되어 있습니다.
 
using System;
using System.Linq;
using System.Data.Linq;
using System.Xml.Linq;
using System.Collections;
 
그리고 다음과 같은 배열이 선언되어 있습니다.
 
string[] colors = { "green", "brown", "blue", "red" };
 
 
 
자!! 그럼 이제 도전해 보십시오.
 
 
Q1. 다음 표현의 결과는 무엇입니까?
 
colors.Max (c => c.Length)
 
(A) 5
(B) green
(C) brown
(D) Compile-time error
(E) Exception thrown
 
 
Q2. 다음 표현의 결과는 무엇입니까?
 
colors.OrderBy (c => c.Length).Single()
 
(A) 3
(B) red
(C) Compile-time error
(D) Exception thrown
 
 
Q3. 주어진 쿼리식을 보고 답하세요.:
 
var query =
 from c in colors
 where c.Length > 3
 orderby c.Length
 select c;
 
query 변수의 type 은 무엇입니까?
 
(A) int
(B) string
(C) IEnumerable<int>
(D) IEnumerable<string>
(E) IQueryable<int>
(F) IQueryable<string>
 
 
Q4. 다음의 출력 결과는 무엇입니까?
 
var query =
 from c in colors
 where c.Length == colors.Max (c => c.Length)
 select c;
 
foreach (var element in query)
Console.WriteLine (element);
 
colors 배열의 값은 { "green", "brown", "blue", "red" } 입니다.
 
(A) green followed by brown
(B) 5 followed by 5
(C) Compile-time error
(D) Exception is thrown
 
 
Q5. 위의 예제에서 Subquery 는 최대 몇번 실행합니까?
 
(A) 한번
(B) 두번
(C) 세번
(D) 네번
 
 
Q6. 다음 코드의 출력 결과는 무엇입니까?
 
var list = new List<string> (colors);
IEnumerable<string> query = list.Where (c => c.Length == 3);
list.Remove ("red");
 
Console.WriteLine (query.Count());
 
(A) 0
(B) 1
(C) 2
(D) Exception thrown
 
 
Q7. 다음 코드의 출력 결과는 무엇입니까?
 
string[ ] colors = { "green", "brown", "blue", "red" };
 
var query = colors.Where (c => c.Contains ("e"));
query     = query.Where (c => c.Contains ("n"));
 
Console.WriteLine (query.Count());
 
 
(A) 1
(B) 2
(C) 3
(D) 4
 
 
Q8. 다음 코드의 출력 결과는 무엇입니까?
 
string s = "e";
var query = colors.Where (c => c.Contains (s));
 
s         = "n"
query     = query.Where (c => c.Contains (s));
 
Console.WriteLine (query.Count());
 
(A) 1
(B) 2
(C) 3
(D) 4
 
 
Q9. 다음의 쿼리에서, 컴파일러는 쿼리 문법을 어떻게 해석합니까?
 
from c in colors
let middle = c.Substring (1, c.Length - 2)
where middle.Contains ("e")
select middle;
 
(A)  Enumerable.Let을 호출하는 것으로 번역이 된다.
(B)  where, select 구문에서 middle은 c.Substring (1, c.Length - 1)으로 확장된다.
(C) 임시로 익명 타입으로 해석한다.
 
 
Q10. 컴파일러가 여러 생성자를 포함하여 쿼리를 번역하려면…?
 
(A) Multiple Selects
(B) SelectMany
(C) Join
 
 
Q11. 다음 보기 중, LINQ 에서 같이 사용할 수 있는 JOIN 절을 고르시오(복수선택 가능)
 
I.    Inner joins
II.   Left outer joins
III. Full outer joins
IV. Non-equi inner joins
V.   Non-equi outer joins
 
 
Q12. LINQ 의 JOIN 절은 어떤 SQL 구문의 JOIN 을 사용할 수 있습니까?(복수선택가능)
 
I.    Inner joins
II.   Left outer joins
III. Full outer joins
IV. Non-equi inner joins
V.   Non-equi outer joins
 
 
Q13. LINQ to SQL 쿼리는 자신의 로컬 메서드를 호출할 수 있습니까?
 
A. Where 절에서만
B. In the final projection only
C. 쿼리의 어떤곳에서도 가능하다
D. 불가능하다
 
 
Q14. In LINQ to SQL, to request that an entity’s association properties be populated along with the entity (in a single round trip), you would:
 
(A) set DelayLoaded to false in the Association attribute
(B) set EagerLoad to true in the Association attribute
(C) use a DataLoadOptions object, and call AssociateWith
(D) use a DataLoadOptions object, and call LoadWith
 
 
Q15. 다음 코드의 출력 결과는 무엇입니까?
 
var city = new XElement ("city", "Seattle");
 
var customer1 = new XElement ("customer", city);
var customer2 = new XElement ("customer", city);
 
city.SetValue ("London");
Console.WriteLine (customer2.Element ("city").Value);
 
(A) Seattle
(B) London
(C) An exception is thrown
 
 
Q16. 다음 코드에서 element 이름 안의 네임스페이스는 무엇입나까?
 
XNamespace ns = "http://albahari.com/linqquiz";
 
var x =
 new XElement (ns + "customer",
    new XElement ("name", "Bloggs")
);
 
(A) "" (empty string)
(B) "http://albahari.com/linqquiz"
(C) "name"
 
 
Q17. XElement 또는 XDocument 클래스를 사용하여 파일로 XML 을 저장할 때, XML 선언문은 출력파일에 포함이 된다.
 
(A) 항상 포함된다.
(B) 전혀 포함되지 않는다.
(C) XDeclaration 객체를 사용할 경우에만
 
 
 
모든 문제를 푸셨나요? 의외로 긴가민가 한 부분이 많지요? ㅋ
그럼 채점에 들어가겠습니다.
해설까지 번역하려고 하였으나, 짧은 문장력으로 너무 힘드네요~
아래의 주소에 자세한 설명이 나와있으니 한번씩 둘러보시기 바랍니다^^
( Q14 번 번역 가능하신 분 구합니다 ㅡㅡㅋ )
 
 
 
답안
 
Q1. A
Q2. D
Q3. D
Q4. C
Q5. D
Q6. A
Q7. A
Q8. B
Q9. C
Q10. B
Q11. I and IV
      II and V
Q12. I
      II
Q13. B
Q14. D
Q15. A
Q16. A
Q17. A

Posted by 땡초 POWERUMC
TAG LINQ

댓글을 달아 주세요

LINQ TO Sql 은 데이터베이스를 엑세스 하고 쿼리하는데 데이터베이스와 어플케이션간에 많은 장벽을 없애버렸다. 바로 어플케이션에서 쉽게 SQL 쿼리를 사용할 수 있기 때문이다.
하지만, 이와 같이 장벽이 사라진 만큼 데이터베이스와 어플케이션의 더욱 더 깊은 이해가 요구 되어야 더욱 더 섬세하고 좋은 성능을 낼 수가 있을 것이다.
 
 
일반적인 데이터엑세스 과정
 
 
대략 위와 같은 논리적인(또는 물리적인) 3 Layer 형태가 갖추어 질 것이다.(위 그림 정말 못그렸다.;;)
그렇다면 LINQ TO Sql 의 사용은 Data Access Layer 가 가장 적절할 것 같다.
LINQ TO Sql 클래스를 만들어, 비쥬얼하게 멋진 다이어그램과 사용할 프로시져를 끌어다 놓는 것 만으로 SqlParameter 를 만드는 귀찮은 작업은 생략될 수 있을 것이다.
 
그렇다면 왜 Data Access 에서만 LINQ TO Sql 을 사용해야 할까
 
이유는 간단하다. 바로 디스어셈블러 때문이다.
데이터베이스의 ConnectionString 은 자동으로 app.config 에 기어 들어간다고 쳐도, 데이터베이스의 테이블 스키마와 관계 등이 그대로 노출 될 수 있다.

다음은 LINQ TO Sql 이 만들어 주는 Entity 클래스의 일부이다.
 
 
아래는 LINQ 쿼리가 어떻게 디스어셈블리 되는지 보여준다.
 
var joinList = from c in db.Comments
              group c by c.ArticleNo into g_c
              join a in db.Articles on g_c.Key equals a.ArticleNo
              orderby g_c.Key ascending
              select new
              {
                        g_c.Key,
                        a.Title,
                        CommentCount = g_c.Count()
              };
var joinList = db.Comments.GroupBy<Comment, int>(Expression.Lambda<Func<Comment, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(Comment), "c"), (MethodInfo) methodof(Comment.get_ArticleNo)), new ParameterExpression[] { CS$0$0000 })).Join(db.Articles, Expression.Lambda<Func<IGrouping<int, Comment>, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(IGrouping<int, Comment>), "g_c"), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), new ParameterExpression[] { CS$0$0000 }), Expression.Lambda<Func<Article, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(Article), "a"), (MethodInfo) methodof(Article.get_ArticleNo)), new ParameterExpression[] { CS$0$0000 }), Expression.Lambda(Expression.New((ConstructorInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>..ctor, <>f__AnonymousType0<IGrouping<int, Comment>, Article>), new Expression[] { CS$0$0000 = Expression.Parameter(typeof(IGrouping<int, Comment>), "g_c"), CS$0$0002 = Expression.Parameter(typeof(Article), "a") }, new MethodInfo[] { (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_a, <>f__AnonymousType0<IGrouping<int, Comment>, Article>) }), new ParameterExpression[] { CS$0$0000, CS$0$0002 })).OrderBy(Expression.Lambda(Expression.Property(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>), "<>h__TransparentIdentifier0"), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), new ParameterExpression[] { CS$0$0000 })).Select(Expression.Lambda(Expression.New((ConstructorInfo) methodof(<>f__AnonymousType1<int, string, int>..ctor, <>f__AnonymousType1<int, string, int>), new Expression[] { Expression.Property(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>), "<>h__TransparentIdentifier0"), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), Expression.Property(Expression.Property(CS$0$0000, (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_a, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(Article.get_Title)), Expression.Call(null, (MethodInfo) methodof(Enumerable.Count), new Expression[] { Expression.Property(CS$0$0000, (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)) }) }, new MethodInfo[] { (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_Key, <>f__AnonymousType1<int, string, int>), (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_Title, <>f__AnonymousType1<int, string, int>), (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_CommentCount, <>f__AnonymousType1<int, string, int>) }), new ParameterExpression[] { CS$0$0000 }));
 
일일이 탭도 맞추고 정렬한 후에야 비로소 볼만 하겠지만, 조막만한 쿼리가 어쨌든 알아보긴 힘들지만 역어셈블리가 되었다.
그렇다면 충분히 어셈블리의 IL 코드를 조작하여, 원하는 데이터를 리턴시키기엔 충분할 것이다. (쉽지 않겠지만^^;)
 
위의 무지막지한 디스어셈브리 코드를 보면, LINQ TO Sql 의 성능마저 궁금하게 만든다.
하지만, 성능 테스트는 다음으로 슬쩍 미루도록 해야겠다. (음하하;;)
왜냐하면 난 소중하니까 ^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요

LINQ 의 OUTER JOIN 작업

.NET/C# 2007.09.04 00:24 |
LINQ 를 이용한 OUTER JOIN 을 해보자.
이번 강좌를 지난 강좌에 이어 LINQ to SQL Classes 항목을 추가 하여야 한다.
이부분에 대해서는 다음의 URL 을 참고하기 바란다.
 
[.NET/C# 3.0] - LINQ to SQL Classes 와 LINQ의 JOIN 작업
http://umc.pe.kr/article/2007/09/02/LINQ-to-SQL-Classes-AND-LINQ-JOIN.aspx
 
 
우선 우리가 원하는 SQL 쿼리식을 보자
 
SELECT A.ArticleNo, C.Content, C.InsertDate
FROM Article A
LEFT OUTER JOIN Comment C ON C.ArticleNo = A.ArticleNo
ORDER BY C.InsertDate DESC
 
아주 간단한 OUTER JOIN 의 예이다.
이 쿼리의 결과는 다음과 같다.
 
 
위 그림은 보시다시피 결과 데이터의 중하단 쯤에 나온 결과이다.
쿼리를 보듯 NULL 값이 주루룩 있다. 당연히 INNER JOIN 되지 않은 데이터는 NULL 값이 표시될 것이다.
 
 
그럼 LINQ 의 확장 메서드를 통한 OUTER JOIN 을 해보자.
 
var joinList = db.Articles.GroupJoin(
             db.Comments,
             _article => _article.ArticleNo,
             _comment => _comment.ArticleNo,
             (_article, c) => c.DefaultIfEmpty().Select(
                    _comment => new
                    {
                           ArticleNo = _article == null ? -1 : _article.ArticleNo,
                           CommentContent = _comment.Content ?? string.Empty,
                           InsertDate = _comment.InsertDate.ToString() ?? string.Empty
                    }
             )
             ).SelectMany(obj => obj)
             .OrderByDescending(o => o.InsertDate);
 
 
위 식을 INNER JOIN 식과 비교해 보았을 때, 좀 더 부가적인 작업이 필요하다.
 
c.DefaultIfEmpty()
 
을 통해 JOIN 가능한 키가 없을 경우에 따로 처리를 해주어야 한다.
 
또한, 키가 없을 경우의 데이터 또한 nullable 의 ?? operator 를 통해 null 데이터에 대한 경우에 대한 작업을 해야한다.
 
SelectMany(obj => obj)
 
확장 메서드를 통해 반복이 가능한 순환자(열거자) 에 대한 키(Key)값을 모두 반환한다.
 
아무리 람다식을 이용해 코딩양을 줄였다고 하더라고, 사실 벅찬 감이 무척 많다..
두개의 테이블이지만, 3-4 개의 테이블이라고 생각하면, 아마도 SQL 쿼리를 이용하는 것이 나을 것이다.
 
 
그렇지만 다음의 LINQ 식을 이용한 쿼리를 보도록 하자.
 
var joinList = from a in db.Articles
             join c in db.Comments on a.ArticleNo equals c.ArticleNo into _c
             from c in _c.DefaultIfEmpty()
             orderby c.InsertDate descending
             select new
             {
                       ArticleNo = a.ArticleNo,
                       CommentContent = c.Content,
                       InsertDate = c.InsertDate.ToString()
             };
 
개체를 이용한 쿼리식에 비해 무척이나 SQL 쿼리와 가까워 졌다.
 
join c in db.Comments on a.ArticleNo equals c.ArticleNo into _c
from c in _c.DefaultIfEmpty()
 
를 보면, into 키워드를 통해 join 데이터를 _c 컬렉션에 담고
다시 _c.DefaultIfEmpty() 를 통해 null 데이터를 처리하고 있다.
 
into 키워드는 LINQ 의 출연과 함께, 쿼리식 뿐만 아니라 통계 쿼리에도 자주 등장하게 될 키워드이다.
 
그럼, 다음 아티클을 스스로 기대하며 이만^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요

LINQ 의 JOIN
 
기존 C# 2.0 의 Typed DataSet 의 개념과 LINQ 가 결합하여 LINQ to SQL Classes 라는
이름으로 굉장히 강력한 기능을 제공한다.
 
LINQ, 람다식, 익명 형식에 대한 내용은 다음의 URL 을 참고 하세요.
 
2009-06-20 아래의 경로로 접속할 수 없습니다.
LINQ의 발전과 C# 설계에 미치는 영향

 
LINQ to SQL Classes 항목 만들기
 


[새 항목 만들기] 에서 LINQ to SQL Classes 를 만든다.
그럼 .dbml 확장자를 가진 몇가지 파일과 디자이너 화면이 나타나게 된다.
 
여기서 사용하게 될 데이터베이스는 UmcBlog 소스와 함께 공개된 데이터베이스를 사용할 것이다.
 
 
Article 테이블은 블로그의 아티클을 저장할 테이블이다.
Comment 테이블은 아티클에 대한 댓글을 저장하는 테이블이다.


 

디자이너는 두 테이블간 관계가 맺어져 있다면, 이 두 테이블 간의 RULE 과 Key 등이
자동으로 매핑되는 것을 볼 수 있다.
 
 
 
DataBase 쿼리와 LINQ 를 이용한 JOIN 작업
 
SELECT A.ArticleNo, C.Content, C.InsertDate
FROM Article A
INNER JOIN Comment C ON A.ArticleNo=C.ArticleNo
Article, Comment 테이블의 JOIN 쿼리
 
간단하게 JOIN 하는 쿼리를 작성해 보았다.
결과는 다음과 같을 것이다.
 
 
Comment 테이블의 댓글의 ArticleNo 를 Article 테이블과 조인하여 나온 결과이다.



그렇다면 LINQ 를 이용한 JOIN 작업을 보자.

위에서 LINQ to SQL Classes 를 다음과 같이 객체를 생성한다.

 
UmcBlogDataContext db = new UmcBlogDataContext();
 
 
// INNER JOIN
var joinList = db.Articles.Join(db.Comments,
                           _article => _article.ArticleNo,
                           _comment => _comment.ArticleNo,
                           (a, c) => new
                           {
                               ArticleNo             = a.ArticleNo,
                               CommentContent = c.Content,
                               InsertDate            = c.InsertDate
                           }
                    ).OrderByDescending( o => o.InsertDate );
 
SQL 쿼리와 비교해 볼 때 다소 복잡해 보이는 감이 없지 않다.
C# 2.0 의 익명 메서드를 이용와 비교해볼 때 위와 같이 람다식 을 이용하여 그나마 짧게
작성한 코드이다.

 
물론 위와 같이 불편하게 쿼리를 작성하지 않아도 된다.
LINQ 의 쿼리식을 이용하여 마치 SQL 쿼리를 작성하듯 쿼리를 완성할 수 있다.

 
LINQ 식을 이용한 JOIN
 
var joinList = from a in db.Articles
                      from c in db.Comments
                      where a.ArticleNo == c.ArticleNo
      orderby c.InsertDate descending
                      select new {
                              ArticleNo = a.ArticleNo,
                              CommentContent = c.Content,
                              InsertDate            = c.InsertDate
                       };
 
위의 예제를 보면 from 절이 두개가 오는 것을 알 수 있다.
마치 PL/SQL 의 from Article A, Comment C 와 같이 사용하듯 하다.
 
물론 ANSI SQL 과 같은 방법으로 JOIN 이 가능하다.
그럼 아래의 구문을 보자.
 
// INNER JOIN 3
var joinList = from a in db.Articles
                       join c in db.Comments on a.ArticleNo equals c.ArticleNo
                      orderby c.InsertDate descending
                      select new {
                              ArticleNo            = a.ArticleNo,
                              CommentContent       = c.Content,
                              InsertDate           = c.InsertDate
                       };
 
위와 같이 join 문과 함께 on 뒤에 두개의 테이블을 조인을 하게 된다.
On 과 함께 ==(Equals) 을 사용할 수 없기 때문에 “equals” 을 통해 두 테이블을 조인하게 된다.
 
 
위의 예제를 보듯, 객체를 이용한 쿼리와 SQL 쿼리식과 같은 쿼리가 모두 가능하다는 것을
알 수 있다.
하지만, outer join 은 기존 방법과는 약간 다른 방법으로 조인을 하게된다.
다음시간에 LINQ 의 outer join 을 알아보도록 하자.
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 엔돌슨 2012.09.04 17:23 신고 Address Modify/Delete Reply

    궁금한것이 있습니다.
    join c in db.Comments on a.ArticleNo equals c.ArticleNo
    이렇게 조인되면 키가 1개만 매치되는 데 2건이상이면 어떻게 해야하나요?

    조인을 할때 조인되는 키가 여러개의 경우 어떻게 처리해야 하나요?

    • 땡초 POWERUMC 2012.09.06 00:40 신고 Address Modify/Delete

      JOIN 구문을 이렇게 사용하셔도 됩니다.
      http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/a8bf3932-611f-44d2-b4d8-6cac83b30406/

      또는 where 절을 이용하셔도 됩니다.
      from n1 in new[] { "Tom", "Dick", "Harry" }
      from n2 in new[] { "A", "B", "Tom", "Dick" }
      where n1 == n2 && n1.Length >= 3
      select n2