요즘 참 할일도 많은데 할 수 있는 일이 점점 줄어든다. 필자는 블로그 버킷 리스트(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

댓글을 달아 주세요

이미 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 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

댓글을 달아 주세요

우리는 C# 3.0 에 확장 메서드(Extension Methods) 가 언어적으로 지원된다는 말은 수없이도 들어보았다. 확장 메서드는 기존 C# 2.0 에 비해 굉장히 파격적이다. 추후 확장 프로퍼티와 확장 이벤트 등도 지원된다고 하니, 가히 언어적으로 파격적이다.
 
 
확장 메서드의 문제점
 
확장 메서드는 굉장히 파격적이다. 내가 C# 2.0 을 하면서 감히 이런 언어적 지원이 가능하리라곤 상상도 못했으니 그 아이디어 적이 면만으로도 충분히 놀랄만 하다.
확장 메서드는 원본 타입의 Type 에 따라 지원된다.
하지만, 자주 사용되는 string, int, bool 등과 같은 타입에 확장 메서드를 추가하게 되고, 프레임웍이 커짐에 따라 이런 확장 메서드의 양도 무한대(?)로 커질 가능성이 충분히 있다.
 
확장 메서드는 기본적으로 자신이 속한 namespace 에 한해 Intelisense 에 표시된다. 즉, 다른 namespace 의 확장 메서드를 사용하고 싶다면 using 문을 추가하면 된다.
바로, 여기에서 문제점이 도출된다.
기존 Utility 성격의 메서드나 해당 namespace 에 존재하는 많은 메서드들이 확장 메서드로 마이그레이션이 가능하다는 것이다.
C# 에서는 수많은 namespace 가 존재하고, 수많은 클래스가 존재한다. 만약, using 문을 추가해 다른 namespace 의 확장메서드를 사용하려 했으나, 중복되는 경우도 생길것이며, 구현하려는 기능이 일반 static 메서드였는지, 확장 메서드였는지 조차 기억하기엔 너무나도 번거로운 작업이 될 것이다.
 
일반적인 확정 메서드가 구현되는 상황을 보도록 하자.
 
class Program
    {
        static void Main(string[] args)
        {
                     string encrypt = "AAA".Encrypt();
 
                     Console.WriteLine(encrypt);
        }
    }
 
static class MyExtender
{
           public static string Encrypt(this string arg)
           {
                     return arg + "는 암호화 됨.(MyExtender)";
           }
}
 
Encrypt 메서드는 특정 문자열을 암호화 하는 확장 메서드이다. (암호화 코드가 있다는 가정임)
 
즉, 해당 클래스가 속해있는 namespace 에 한해 확장 메서드를 구현하고, 만약 다른 namespace 의 확장 메서드가 필요하게 되면 using 네임스페이스; 구문을 추가하여 다른 namespace 영역의 확장 메서드를 끌어다 쓰면 되는 것이다.
 
만약, using 문이 추가되면 될수록 우리가 기억 해야할 확장 메서드의 양은 얼마만큼 많아 지게 될지 알 수 없다.
 
수많은 고민을 통해 “모든 확장 메서드는 하나의 namespace 안에서 관리하자” 라는 생각을 해보았다. 즉, 확장 메서드가 필요하면 using 구문 하나만으로 모든 확장 메서드를 호출할 수 있도록 하는 구성이다.
하지만, 여기에 결정적인 단점이 있다. 이 부분은 뒷부분에 다시 설명 하도록 하겠다.
 
 
 
확장 메서드(Extension Methods) 설계
 
 
위 그림과 같이 확장 메서드를 지원하는 별도의 폴더와 namespace 를 만들었다.
이 namespace 는 솔루션 또는 프레임웍이 사용하는 모든 확장 메서드를 한군데에 모아 놓기 위한 namespace 이다.
 
즉,
 
using Umc.Core.Extender;
 
구문 하나만으로 모든 확장 메서드를 활용할 수 있다.
 
그럼 SecurityExtender.cs 파일과 TextExtender.cs 파일을 연속으로 보도록 하자.
본 소스는 예제용으로 제공되는 소스이니 크게 비중을 두지 말고 보도록 하자.
 
namespace Umc.Core.Extender
{
           public static partial class SecurityExtender
           {
                     public static Security.Security Security(this string arg)
                     {
                                return new Umc.Core.Security.Security(arg);
                     }
           }
}
 
namespace Umc.Core.Extender
{
           public static partial class TextExtender
           {
                     public static Umc.Core.Text.Text Text(this string arg)
                     {
                                return new Umc.Core.Text.Text(arg);
                     }
           }
}
 
위 두 클래스는 동일한 namespace 안에 존재하게 되며, 특히 partial 클래스로 선언되어 언제 어디서든 동일한 namespace 와 class 명을 갖게 된다면 확장 및 기능 추가가 가능하도록 했다.
 
아직 리턴 타입의 클래스 구현을 확인하진 못했지만, 어느정도 객체지향 프로그램을 해 본 사람이라면 일찌감치 감을 잡았을 것이라고 생각한다.
 
위 두 클래스가 지원하는 확장 메서드의 원본 타입은 string 이 되며, 해당 확장 메서드의 리턴 타입은 또 다른 인스턴트형의 클래스이다.
 
위처럼 구현한 이유는 확장 메서드를 쩜(.)을 찍고 구현할 수 있도록 소위 Depth(깊이)를 준 것이다. 여기에서 말하는 소위 Depth 라고 함은 다음의 그림을 보면 쉽게 이해가 갈 것이다.
 

 
위와 같이 해당 확장 메서드에서 쩜(.) 을 누르는 순간 새로운 메서드들이 Intelisense 에 나타나게 된다.
 
 
개발자 입장에서는 이렇게 직감적으로 코딩을 가능하게 하는 것도 “사용자 경험(UX)” 라고 말하고 싶다.
 
이렇게 설계가 가능하다면, 다음과 같이 최소한의 확장 메서드로 다양한 기능을 추가할 수 있다는 것이다.
 
그럼, Security 와 Text 클래스가 어떻게 구현되었는지 연속적으로 보도록 하자.
 
namespace Umc.Core.Security
{
           public class Security
           {
                     // SourceString 이 private 이므로 Object Initializer 를 사용할 수 없다.
                     public Security(string str)
                     {
                                this.SourceString = str;
                     }
 
                     // 암호화할 문자열
                     private string SourceString { get; set; }
 
                     public string Encrypt()
                     {
                                // 암호화 알고리즘이라고 가정.
                                return SourceString + "는 암호화 됨.";
                     }
 
                     public string Decrypt()
                     {
                                // 복호화 알고리즘이라고 가정.
                                return SourceString + "는 복호화 됨.";
                     }
           }
}
 
namespace Umc.Core.Text
{
           public class Text
           {
                     public Text(string str)
                     {
                                this.SourceString = str;
                     }
 
                     private string SourceString { get; set; }
 
                     public string Left(int len)
                     {
                                return SourceString.Substring(0, len);
                     }
 
                     public string Right(int len)
                     {
                                return SourceString.Substring(SourceString.Length - len , len);
                     }
 
                     // 기존 확장메서드 방식으로 인스턴스를 리턴하여 호출할 방법이 없음.
                     public static string Test()
                     {
                                return string.Empty;
                     }
           }
}
 
 
 
특히나 interface 를 통한 확장 메서드의 구현은 그 진가를 십분 발휘하게 될 것임이 분명하다.
그렇지만, 이러한 편리한 설계임에도 불구하고 단점이 존재하게 된다.
 
 
확장 메서드 설계의 문제점
 
바로 static 메서드로 구현된 메서드는 호출할 뾰족한 방법이 없다.
위의 확장 메서드의 설계 에서 보듯이, 구현된 클래스는 반드시 인스턴스를 생성하여 사용할 수 있도록 구성되었다.
 
아래와 같이
 
 
처럼 static 으로 구현된 메서드는 인스턴스를 생성하여도 호출할 방법이 없다.
우리가 설계한 확장 메서드는 생성자에 인자값을 전달하는 형식이지만, 더군다나 static 메서드는 생성자에 인자값을 전달한다고 하더라도, 생성자가 전달한 인자값을 활용할 방법이 없다는 것이다. 아마도 이 부분은 확장 프로퍼티라는 개념이 C# 3.0 정식 버젼에서 지원하느냐, 어떻게 지원하느냐가 관건이 될 것 같다.
 
 
마치며
 
C# 3.0 의 Lambda, LINQ, Extension Methods 는 새로 등장하는 시스템의 설계에 엄청난 파장을 불러 일으킬만한 이슈이다. 아직 완벽하게 완성된 C# 3.0 은 아니지만, 분명한 것은 수많은 Language(언어) 들의 표본이 C# 3.0 이 될 것이 분명하다.(이렇게 말하니 무슨 홍보대사 같네 ㅋ)
정식 C# 3.0 의 모습을 기대하면서 이만 ^^//
Posted by 땡초 POWERUMC

댓글을 달아 주세요

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]
 
 
 
지난 아티클에서 우리는 List<> 컬렉션에 특정 요소를 탐색하기 위해 delegate 와, 익명 메서드, 람다식을 이용한 방법을 알아보았다.
 
List<> 컬렉션의 Find와 FindAll 메서드는 List<> 클래스가 제공하는 메서드지만, 이와 비슷한 확장 메서드를 손수 구현해 보도록 할 것이다.
 
 
이번에도 다음의 List<> 컬렉션의 데이터를 가지고 예제를 만들어 볼 것이다.
 
List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);
 
지난 강좌에서 보듯 다음과 같이 List<> 클래스의 Find 메서드를 이용하여 특정 요소를 탐색한다.
 
int result = arr.Find(o => o == 2);
Console.WriteLine(result);
 
 
 
List<Int>.Find 메서드를 확장 메서드로 구현
 
확장 메서드를 구현하기 위해 MyExtension 이라는 클래스를 만들고 UmcFind 라는 메서드를 만들자.
 
static class MyExtension
{
           public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
           {
                     List<int> ret = new List<int>();
                     foreach (var v in arr)
                     {
                                if (p(v))
                                          return v;
                     }
 
                     return 0;
           }
}
 
위 확장 메서드는 다음과 같이 호출이 가능하다.
 
int result = arr.UmcFind(o => o == 2);
Console.WriteLine(result);
 
 
Public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
 
의 선언을 보자.
 
인자값에 오는 this 는 확장메서드를 지원할 원본 객체의 타입이다. 즉, 우리는 List<int> 에 대한 객체에만 확장 메서드를 지원하게 된다.
 
Predicate<Int> p
 
는 .NET Framework 에서 선언된 bool 을 리턴하는 델리게이트이다.
 
즉, o => o == 2 람다식의 “ o == 2 “ 의 구현부는 Predicate 델리게이트에게 위임된 것이다.
때문에,
 
if( p( v ) )
 
와 같이 Predicate 의 리턴이 bool 이므로 람다식의 조건을 True/False 로 리턴 받을 수 있다.
 
 
 
List<Int>.FindAll 메서드를 확장 메서드로 구현
 
아래와 같이 사용되는 List<int>.FindAll 메서드를 확장 메서드로 구현해 보자.
 
List<int> result = arr.FindAll(o => o >= 2 && o <= 3);
result.ForEach(o => Console.WriteLine(o));
 
 
이번에는 .NET Framework 가 제공하는 델리게이트를 사용하지 않고, 직접 델리게이트를 선언해 볼 것이다.
 
delegate TResult FindAllHandler<T, TResult>(T t);
 
FindAll 확장 메서드를 구현하기 위해 제네릭 델리게이트를 선언해 보았다.
첫번째 T 는 받을 인자 타입이고, 두번째 TResult 는 리턴 타입이 된다.
 
그럼 UmcFindAll 이라는 확장 메서드를 만들어 보자.
 
static class MyExtension
{
           public static List<int> UmcFindAll(
            this IEnumerable<int> arr, FindAllHandler<int,bool> p)
           {
                     List<int> result = new List<int>();
 
                     foreach (var v in arr)
                     {
                                if( p(v) )
                                          result.Add( v );
                     }
                     return result;
           }
}
 
위의 확장 메서드는 다음 처럼 사용이 가능하다.
 
List<int> list = arr.UmcFindAll(o => o >= 2 && o <= 3);
list.ForEach(o => Console.WriteLine(o));
 
 
 
보시는 바와 같이 List<> 클래스가 제공하는 FindAll 메서드와 동일한 기능을 하는 확장 메서드를 만들어보았다.
 
UmcFindAll 확장 메서드는 제네릭 델리게이트를 선언하여 예제를 만들어 보았지만, UmcFind 확장메서드를 유심히 본 독자라면 보는데 큰 어려움이 없을 것 같다.
 
 
이것으로. 람다식의 간략한 이용 방법과 람다식을 사용하는 확장 메서드에 대해서 알아 보았다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 상준 2011.03.24 14:17 Address Modify/Delete Reply

    글 너무 너무 잘봤습니다.
    하지만 이해하기 너무 힘드네요...
    실례지만 해당 소스코드도 있었으면..
    더좋은 글이 되지 안을까 생각합니다..

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]



C# 3.0 에서 LINQ 를 위해 많은 언어적 개념이 도입되었다.
확장 메서드(Extension Methods)와 Lambda Expression 등이 바로 그것이다.
그중 Lambda Expression(이하 람다식) 은 Ruby 에서 먼저 나왔다고 하지만, C# 3.0 에서의 람다식은 LINQ 와 확장 메서드와 굉장히 사슬처럼 엮여 있는 듯한 모습이다.
 
람다식은 대리자(Delegate)와 제네릭 메서드의 복잡한 식을 간결하게 줄여줄 수 있다.
 
=>
 
연산자를 이용하여, 익명 메서드(Anonymous Method) 의 여러줄의 코드를 단 한줄의 코드로 줄여줄 수 가 있다.
 
좀더 자세한 설명히 필요하다면 다음을 참고 하도록 하자.
http://msdn.microsoft.com/msdnmag/issues/07/09/BasicInstincts/Default.aspx?loc=ko
 
 
우리가 사용할 예제는 다음의 List<int> 컬렉션을 사용할 것이다.
 

List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);

 
 
List<> 컬렉션은 Find 라는 메서드를 제공한다. ( 확장 메서드가 아님 )
이 메서드는 컬렉션의 특정 요소를 찾기 위해 사용되어 지는데, C# 이 제공하는 Predicate<> 델리게이트를 통해 True/False 를 반환하는 델리게이트이다.
 
그럼 List<> 컬렉션의 특정 요소를 찾기 위해 다음과 같이 사용된다.
 

static void Main(string[] args)
{
           List<int> arr = new List<int>();
           arr.Add(1);
           arr.Add(2);
           arr.Add(3);
           arr.Add(4);
 
           Predicate<int> p = new Predicate<int>(Compare);
           int result = arr.Find(p);
           Console.WriteLine(result);
}
static bool Compare(int i)
{
           return i == 2;
}

 
 
Predicate<int> p = new Predicate<int>(Compare);
 
Predicate<> 대리자는 Compare 메서드에게 일거리를 할당한 것을 볼 수 있다.
 
그럼, 가상 메서드를 이용하여 코드양을 줄여 보도록 하자.
 

int result = arr.Find(new Predicate<int>(
                                delegate(int i)
                                {
                                          return i == 2;
                                }
                     ));
Console.WriteLine(result);

 
 
위와 같이 당연히 결과는 “2” 가 나타났다.
델리게이트를 통해 i==2 라는 요소를 찾기위해 int 인자와 bool 을 리턴하는 메서드를 만들어야 하지만, 위의 delegate 키워드의 구문은 가상 메서드를 만들게 됨으로써 코드양이 많이 줄어 들게 되었다.
 
하지만, 람다식을 이용하여 더 줄여보도록 하자.
 

Console.WriteLine(arr.Find(o => o == 2));

 
 
위 7개 라인을 단 한줄로 해결하였다.

 
사실 이 구문이 선듯 이해가 가지 않는 필자는 무진장 머리를 굴려보았다.
잘 이해를 하기 위해 람다식을 마치 메서드가 있는 구문이라고 머리속으로 상상해보기 바란다.
 
 
 
=> 연산자를 기준으로 왼쪽은 넘길 인자값이 되는 것이고, 오른쪽은 내용이 되는 것이다.
 
그래도 잘 이해가 안된다면 마치 메서드가 존재한다는 상상 훈련을 해보면 그리 오래 걸리지 않고 쉽게 이해가 될 것 같다.
다음 2편에서는 제네릭 대리자를 통해 람다식이 가능한 확장 메서드를 만들어 보도록 하자
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 지송 2010.06.17 12:57 Address Modify/Delete Reply

    람다식 찾다왔는데 역시 이해가 쉽게 써놓으셨네요.

    좋은 하루 되세요 !

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

XDocument 클래스
 
Framework 3.0 에 들어서면서 새로이 등장한 클래스이다.
그 이전 Framework 1.1, 2.0 에서 XML 작성을 해보았다면, 그 노가다와 다름없는 코딩을
해보았을 것이다.
 
XmlDocument 또는 XmlTextWriter, XmlTextReader 클래스 사용하여 편리하게 XML 작업을 했다.
XmlDocument 는 DOM 과 같은 형태로 작업이 가능하며, 빠른 탐색이 장점이다.
XmlTextWriter 와 XmlTextReader 는 Inline 작업이 가능하며, 간결한 사용법이 장점이었다.
 
기존 XmlTextWriter 로 XML 만드는 일반적인으로 작업하는 예
 
하지만, 오늘 XDocument 클래스를 알게된다면, 위 두 클래스를 쳐다도 안보게 될 상황이 생길 것 같다^^
 
 
XDocument 시작하기
 
기존 DOM 과 같은 형태로 객체를 생성하여 XML 을 만드는 방법이다.
 
// XElement 의 객체를 생성하는 방법
XDocument xdoc = new XDocument();
xdoc.Add(new XComment("XML TEST"));
XElement xele1 = new XElement("ROOT");
xele1.Add(new XElement("Child1", "1"));
xele1.Add(new XElement("Child2", "2"));
xdoc.Add(xele1);
Console.WriteLine(xdoc.ToString());
[코드1] 객체를 생성하여 XML 만들기
 
 
위와 같은 방법은 Element 에 가공이 필요할 때(attribute 추가) 사용하면 될 것 같다.
만약, 그러한 작업이 필요 없다면 더욱 간결하게 코딩 할 수 있다.
 
// params 로 선언된 XDocument 에 연속적으로 XElement 를 코딩.
xdoc = new XDocument(
        new XComment("XML TEST"),
        new XElement("ROOT",
               new XElement("Child1", "1"),
               new XElement("Child2", "2")));
        // 위의 결과와 동일
Console.WriteLine(xdoc.ToString());
[코드2] XDocument 의 params 인자로 연속적인 작업
 
XDocument 의 두번째 생성자는 params 키워드로 object 로 선언된 불특정 다수의 인자값을
받을 수 있다.
 
 
그렇기 때문에 위의 [코드2] 와 같은 연속적인 인자를 받는 코딩이 가능하다.
 
 
 
LINQ 를 이용한 Element 추출
 
위의 샘플과 같은 XML 데이터가 있을 경우, 이것을 추출하기 위해 탐색작업을 해야한다.
Framework 3.0 의 LINQ 는 간결하다 못해, 강력한 기능의 데이터 탐색 작업을 할 수 있다.
 
var result = from r1 in xdoc.Element("ROOT").Elements()
                where r1.Name == "Child1"
                select r1;
                      
foreach (var r in result)
{
        Console.WriteLine(r.Name);
}
[코드3] LINQ 를 이용한 XML 탐색 작업
 
 
XML Element Name 이 Child1 인 것을 탐색하는 구문이다.
기존 SQL 쿼리와 다른 것은 select 가 맨 마지막에 위치한다는 것이다.
 
데이터베이스의 SQL 구문과 C# 의 객체지향을 어느정도 이해한다면
그리 어렵지 않게 LINQ 쿼리를 만들어 낼 수 있을 것이다.
Posted by 땡초 POWERUMC
TAG c#, C# 3.0, XML

댓글을 달아 주세요


확장 메서드(Extension Method)
 
이름만 들어보아도 뭔가 심오한 분위기가 물씬 풍긴다.
쉽게 말하자만, 기존의 클래스를 확장한다고 해야할까?
만약, 클래스의 메서드를 확장하기 위해선 상속을 하고, 상속한 클래스에서 메서드를 선언하여 확장하는 방식이었다.
하지만 Framework 3.0 에서는 더 이상 메서드의 확장을 위해 클래스의 상속이 필요가 없다.
 
 
시작하기
 
심플한 샘플부터 보도록 하자.
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleTest1
{
        class Program
        {
               static void Main(string[] args)
               {
                       string url = "http://umc.pe.kr";
 
                       Console.WriteLine(url.GetUrl());
               }
        }
 
        public static class MyExtension
        {
               public static string GetUrl(this string url)
               {
                       return string.Format("<a href='{0}'>{0}</a>", url);
               }
        }
}
[코드1] 확장 메서드 사용예

 
 
 
 
public static class MyExtension
{
        public static string GetUrl(this string url)
        {
               …
        }
        ...
}
 
보시다시피 클래스와 메서드는 모두 static 으로 선언이 되었다.
 
MyExtension 클래스의 GetUrl 은 this string url 과 같이 1개있는 메서드다.
이것은 메서드를 확장할 타입이다. 즉, 우리는 string 을 확장한다는 표현인 것이다.
 
그렇다면 인자값을 받는 메서드를 확장해 보자.
 
 
확장 메서드는 반드시 static class 로 만들어야 한다.
그렇지 않으면 다음과 같은 오류 메시지를 맛보게 될 것이다.
 
 
보다시피, this string url 은 확장할 타입이라고 말한 바 있다.
바로 그 뒤로부터 비로서 확장 메서드의 인자값을 받을 수 있다.
 
 
그렇다면 어디까지 확장이 가능한가?
 
“단순히 클래스의 메서드만을 확장하는 것일까” 하고 이부분이 가장 궁금했다.
바로 delegate….
물론 delegate 란 것이, 특정 메모리 상의 위치를 참조하고 있고, 추가적인 동기/비동기 메커니즘 메서드를 제공하고 있다.
 
우선 답은 ‘가능하다’ 이다…
그렇다면 소스를 보자.
 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace ExtensionAsyncMethod
{
        public delegate object BeginRunHandler();
        public partial class Form1 : Form
        {
               public Form1()
               {
                       InitializeComponent();
               }
 
               private void button1_Click(object sender, EventArgs e)
               {
                       // 확장 델리게이트 생성
                       BeginRunHandler handler = new BeginRunHandler(Running);
                       // 확장 메서드 호출
                       handler.BeginRun(new AsyncCallback(EndCallback), handler);
                      
               }
 
               private object Running()
               {
                       // 특정 작업
                       System.Threading.Thread.Sleep(1000);
 
                       return "작업완료";
               }
 
               // 콜백 메서드
               private void EndCallback( IAsyncResult result )
               {
                       BeginRunHandler handler = (BeginRunHandler)result.AsyncState;
                       object objResult = handler.EndRun(result);
 
                       MessageBox.Show(objResult.ToString());
               }
        }
 
        public static class MyExtension
        {
               public static IAsyncResult BeginRun(this BeginRunHandler handler, AsyncCallback callback, object obj)
               {
                       return handler.BeginInvoke(callback, obj);
               }
 
               public static object EndRun(this BeginRunHandler handler, IAsyncResult result)
               {
                       return handler.EndInvoke(result);
               }
        }
}
[코드2] 델리게이트 확장 메서드

 
델리게이트를 생성 후에,
 
handler.BeginRun(new AsyncCallback(EndCallback), handler);
 
 
구문을 통해 델리게이트의 확장 메서드를 호출 할 수 있는 것을 볼 수 있다.
 
 
마치며…
 
정말이지 C# 2.0 에서 3.0 의 변화에서만 볼 수 있듯이, 이렇게 까지 언어적인 설계가 가능한 것만 보아도, 엄청난 아키텍쳐 포스가 느껴진다.
Framework 3.0… 다가갈수록 그 매력이 느껴지지 않는가?
너무나무 눈깜짝할 사이 Framework 3.5 가 떡하니 기다리고 있지만,
차근차근 스텝바이스텝… 기다리거라^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요