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

댓글을 달아 주세요

LINQ 의 OUTER JOIN 작업

.NET/C# 2007. 9. 4. 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