'Midnight Peach'에 해당되는 글 25건

  1. 2009.03.19 Midnight Peach 2.0 샘플 애플리케이션, 웹 애플리케이션 (1/2) 1
  2. 2009.03.18 Midnight Peach 2.0 샘플 애플리케이션, 솔루션의 생성과 DBML 파일의 생성
  3. 2009.03.17 Midnight Peach 2
  4. 2008.12.15 MP Default 패키지 내 일부 클래스의 이름 변경
  5. 2008.12.08 MP 버그 하나
  6. 2008.10.26 MPML (Midnight Peach Macro Language)
  7. 2008.10.15 MP 튜토리얼에서 사용된 샘플 프로젝트
  8. 2008.10.15 MP의 데이터베이스 트랜잭션 지원
  9. 2008.10.14 MP 1.1 업데이트
  10. 2008.10.10 MP의 구성

Midnight Peach 2.0 샘플 애플리케이션, 웹 애플리케이션 (1/2)

Midnight Peach 2009. 3. 19. 12:15

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

이번 시간에는 메모패드의 프론트 엔드인 웹 애플리케이션을 만들어보겠습니다.

전체적인 모습은 다음과 같습니다.

비지니스 레이어 생성

먼저 앞 시간에 만든 솔루션에 Memopad.Web이라는 웹 애플리케이션 프로젝트를 추가하고, 생성된 웹 애플리케이션 프로젝트의 참조에 Memopad.Biz 프로젝트를 추가합니다.

이제 MP가 나서서 비지니스 레이어 코드를 만들 차례입니다.

MP를 실행시키고 비지니스 레이어 생성 버튼을 누릅니다.

Memopad.Biz를 빌드하여 생긴 DLL을 선택하고 확인을 누릅니다.

(내장 패키지와 커스텀 패키지를 선택할 수도 있으나 현재로서는 Default라는 내장 패키지 만이 제공되고 있으므로 기본값 그대로 선택하면 됩니다.)

MP가 코드를 생성할 테이블과 뷰를 지정합니다.

기본값은 모든 테이블과 뷰에 대해 코드를 생성하는 것입니다.

확인을 눌러 다음으로 넘어갑니다.

DBML을 변경해야 한다는 안내가 나타났습니다.

LINQ to SQL는 모든 값 저장 방식과 버전 번호 방식이라는 두 가지 동시성 검사 방법을 지원합니다.

MP에서는 현재 버전 번호 방식만 지원하니다.

따라서 모든 테이블은 IsVersion 속성이 지정된 컬럼을 가져야 하며, 그렇지 않은 경우에는 모든 컬럼의 UpdateCheck 속성이 “Never”로 설정되어야 합니다.

위 안내 메시지는 이 규칙을 위반한 테이블의 목록을 보여주며, 자동으로 이 규칙을 적용하는 방법을 제공합니다.

메모패드의 테이블들은 버전 번호 컬럼이 없으니까 모든 컬럼의 UpdateCheck 속성을 “Never”로 변경하는 방법을 사용하겠습니다.

화면 하단의 변경 버튼을 누르면 변경할 dbml 파일을 선택하는 대화 상자가 나타납니다.

dbml 파일을 선택하면 dbml 파일이 수정되어, 버전 번호 컬럼이 없는 모든 테이블의 컬럼의 UpdateCheck 속성이 “Never”로 변경됩니다.

여기서 주의할 점이 있습니다.

dbml 파일이 변경되면 반드시 비주얼 스튜디오에서 dbml 파일을 열어 저장을 해주어야 한다는 것입니다.

저장을 하지 않으면 Memopad.designer.cs 파일이 새로 생성되지 않아 dbml의 변경 내용이 반영되지 않습니다.

(참고로 Memopad.designer.cs은 Memopad.dbml을 입력값으로 하여 sqlmetal.exe 툴이 생성한 코드입니다.)

이제 비지니스 코드 생성의 3단계인 설정화면이 나타났습니다.

네임 스페이스 등의 변수 값을 수정하거나 템플릿을 통해 생성될 파일의 저장위치를 변경할 수 있긴 한데, 일반적인 경우에는 기본값을 그대로 사용하는 것이 무난할 것입니다.

다만 연결 문자열과 컬렉션 타입은 눈여겨 볼 만 합니다.

연결 문자열의 값은 DB 연결 문자열을 말하는데, “server=xxx.xxx.xxx.xxx; database=Memopad;….” 하는 식으로 직접 연결 문자열을 지정할 수도 있고, 기본값처럼 구성 파일(app.config / web.config)의 연결 문자열 설정을 사용하겠다고 지정할 수도 있습니다. (기본값)

기본값으로 연결 문자열의 이름이 솔루션의 이름과 동일한 “Memopad”로 설정되어 있는 것을 기억하시기 바랍니다.

또 한가지 흥미로운 변수는 컬렉션 타입니다.

기본값으로는 아마도 가장 보편적인 컬렉션일 List<T>를 사용하는 것으로 되어 있는데, 경우에 따라서는 BindingList<T> 같은 컬렉션을 사용하도록 설정할 수도 있습니다.

물론 모든 컬렉션을 다 사용할 수 있는 것은 아니고 Add(T item) 이라는 메서드를 구현하고 있는 컬렉션만 사용 가능합니다.

확인을 누르면 이제 비지니스 코드가 생성되고 화면에 표시됩니다.

화면의 가운데에는 템플릿과 엔터티의 목록이 계층 구조로 표시되고, 오른쪽에는 해당 템플릿과 엔터티로 생성된 코드가 표시됩니다.

템플릿은 데이터베이스 범위(1)와 (뷰를 포함한) 테이블 범위(2)로 구분됩니다.

또한 생성된 코드는 파일 단위로 특정 위치에(3) 저장(4) 할 수 있으며 클립보드로 복사(5)할 수도 있습니다.

하지만 생성된 코드를 저장하는 편리한 방법은 화면 왼쪽에 있는 저장 항목(6)을 클릭하는 것입니다.

여기서는 이전 포스트에서 생성한 솔루션을 지정하도록 하겠습니다.

윈도우 탐색기에서 보면 생성된 파일들을 볼 수 있는데, 이를 모두 비주얼 스튜디오에 추가하도록 합시다.

탐색기에서 비주얼 스튜디오 솔루션 탐색기로 파일을 끌어다 놓아도 되고, 비주얼 스튜디오에서 “기존 항목 추가” 컨텍스트 메뉴를 사용하는 방법도 있지만, 가장 편리한 방법은 “프로젝트에 포함” 컨텍스트 메뉴를 사용하는 것일 것입니다.

먼저 솔루션 탐색기에서 모든 파일 보기 명령을 활성화 시킵니다.

이제 폴더에는 있으나 프로젝트에는 포함되지 않은 파일이 화면에 표시됩니다.

(혹시 파일이 보이지 않으면 솔루션을 닫았다 다시 여시기 바랍니다.)

포함되지 않은 파일들을 선택한 후 컨텍스트 메뉴에서 “프로젝트에 포함”을 선택하여 프로젝트에 추가합니다.

여기까지 해서 비지니스 레이어가 만들어 졌습니다.

비지니스 레이어를 사용하여 웹 애플리케이션을 만드는 과정은 다음 포스트에서 이어서 다루도록 하겠습니다.

:

Midnight Peach 2.0 샘플 애플리케이션, 솔루션의 생성과 DBML 파일의 생성

Midnight Peach 2009. 3. 18. 14:31

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

오늘부터 몇 회에 걸쳐 Midnight Peach 2.0을 사용하는 샘플 애플리케이션을 만들어보도록 하겠습니다.

이를 통해 Midnight Peach를 활용하면 개발 방법이 어떻게 바뀌는지, 또 그로 인해 얻게 되는 득과 실은 무엇인지에 대해 감을 잡을 수 있기를 기대합니다.


‘메모패드’ 라는 단순한 애플리케이션을 만들어 볼 것인데, 그 전에 이 ‘단순한’ 이란 형용사 대한 제 고민을 고백해야겠습니다.

한 때 C로 작성된 Hello world의 여러 가지 버전이 유행한 적이 있습니다.

(리치의 책에 등장하는) 오리지널 한 줄 짜리 버전 부터 시작해서 점점 복잡하고 정교한 버전으로 변화해 가는데, 그 중에는 C에서 유사 클래스(함수 포인터를 가진 구조체)를 구현한 버전이 있나 하면, Hello World를 출력하기 위한 프레임웍을 구축한 것으로 (제가 보기에는) 짐작되는 버전도 있었습니다.

Midnight Peach의 샘플 애플리케이션을 만드려니 뜬금없이 그 이야기가 생각이 납니다.

샘플이 너무 단순하여 필드에서의 요구사항들(보안, 확장성, 성능 등)을 생략하면 샘플의 유용성이 떨어질 것이고, 필드에서 개발하는 수준으로 샘플을 만들면 원래 샘플이 보여주려고 했던 핵심이 가리워질 것입니다.

대부분의 문제가 그렇듯이 이 문제 역시 취사선택의 문제인 듯 합니다.

메모패드는 샘플이 원래 보여줄려고 한 부분, 즉 Midnight Peach를 프레임웍 한 개발 방법을 보여주는 것에 주안을 두도록 하겠습니다.

굳이 이 이야기를 미리 말씀드리는 이유는, Midnight Peach를 필드에서 사용하실 때 메모패드를 기준으로 삼는 우를 범하지 마시라는 노파심에서였습니다.


메모패드는 아래 그림에서 보듯이 일종의 방명록 입니다.

사용자가 메모를 남기는 웹과 관리자가 메모들 관리하는 관리자 툴로 이루어져 있습니다.

아래 스샷은 각각 웹과 관리자 툴의 모습입니다.

데이터베이스의 스키마는 아주 간단한데, 메모와 카테고리라는 두 개의 테이블로 이루어져 있습니다.


아래 .sql 파일을 실행하여 데이터베이스를 생성한 후 본격적으로 샘플 애플리케이션을 만들어 보도록 하겠습니다.

Memopad.sql


Midnight Peach 2.0에는 전용 비주얼 스튜디오 솔루션을 생성하는 기능이 있습니다.

Midnight Peach를 실행한 후 솔루션 생성 버튼을 눌러 비주얼 스튜디오용 솔루션을 생성합니다.

솔루션 명과 솔루션이 생성될 디렉토리를 지정합니다.

확인을 누르면 아래와 같은 질문이 나타납니다.

Yes를 눌러 바로 솔루션을 열거나, No를 눌러고 비주얼 스튜디오를 실행시켜 방금 만든 솔루션을 엽니다.

생성된 솔루션의 구조를 살펴봅시다.

비지니스 레이어(Memopad.Biz)와 그 단위 테스트(Memopad.BizTest), WCF 서비스(Memopad.Service), 서비스 레이어(Memopad.ServiceBiz)에 해당하는 프로젝트가 각각 생성되었습니다.

또한 각 프로젝트에 필요한 참조도 추가되었는데(예를 들어 비지니스 레이어 단위 테스트 프로젝트는 비지니스 레이어 프로젝트가 필요합니다.), 이에는 MP가 생성한 프로젝트 뿐 아니라 System.Conmfiguration.dll과 같은 시스템 어셈블리도 포함됩니다.

붉은 색으로 표시된 부분은 추가된 폴더 혹은 프로젝트 아이템인데, MP가 생성하거나 사용자가 작성한 코드가 들어가게 됩니다.


이제 DBML 파일을 작성할 차례입니다.

Memopad.Biz 프로젝트의 Memopad.dbml 파일을 엽니다.

속성 창을 보면 Serialization Mode 속성이 기본값이 None이 아닌 Unidirectional로 지정되어 있는 걸 볼 수 있습니다.

이는 WCF 서비스를 통해 엔터티를 전송할 때 필요한 설정인데 WCF를 사용하지 않는다면 기본값 그대로 두어도 무방합니다.

자세한 내용은 MSDN을 참조하십시오.

서버 탐색기에서 Memo와 Category 테이블, 그리고 MemoView 뷰를 디자이너 화면으로 끌어옵니다.

그럼 디자이너의 화면은 다음과 같습니다.

솔루션을 빌드하여 무사히 빌드가 되는지 확인합니다.


여기까지가 본격적인 코딩을 위한 위밍업이었습니다.

다음 포스트 부터는 LINQ를 비롯해 다소 복잡한 코드가 등장합니다.

맛있는 담배를 한 대 하면서 머리를 잠시 식힌 후 또 달리도록 합시다.

:

Midnight Peach 2

Midnight Peach 2009. 3. 17. 13:36

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

Midnight Peach가 2.0으로 메이저 업그레이드 됐습니다.

수 십 개의 버그 수정과 더불어 소소한 변화가 있었지만, 2.0 버전의 주요 목표는 WCF 서비스 코드를 생성하는 것이었습니다.

이번 버전에서는 WCF 서비스 코드를 생성하고 사용하기 위해 아래와 같은 세 가지 기능이 준비되어 있습니다.

  • WCF 서비스 생성
  • WCF 서비스 호출 래퍼 생성
  • 비동기 호출 래퍼 생성

또한 Midnight Peach를 프레임웍으로 사용하도록 미리 설정된 비주얼 스튜디오 솔루션을 생성하는 기능도 추가되었습니다.

버튼 클릭 한 번 만으로 잘 구조화된 솔루션을 생성해내는 이 기능을 이용하면 개발을 좀 더 빨리 시작할 수 있습니다.

각각의 기능에 대해서는 이어질 별개의 포스트에서 다루도록 하겠습니다.

 

배포 준비는 다 됐는데 라이센스와 배포 방법을 아직 결정하지 못하였습니다.

결정되는대로 블로그를 통해 알려드리도록 하겠습니다.

혹시 공식적인 배포를 통하지 않고 미리 야매로 써보고 싶으신 분은 살짝 연락(댓글)을 줘보십시오.

:

MP Default 패키지 내 일부 클래스의 이름 변경

Midnight Peach 2008. 12. 15. 14:20

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

처음부터 계속 마음이 걸려왔던 일인데, Manager 클래스들(ContactManager, MemoManager 등)의 이름이 항상 마땅찮았습니다.

MP의 처음 버전에서는 MemoManager의 이름이 MemoDto 였습니다.

즉 Memo 엔터티를 전달하는 역할을 하는 클래스(Data Transfer Object, 제가 만든 말이 아니고 공식 용어입니다.) 라는 의미였습니다.

하지만, MemoManager를 예를 들어 생각해 보면, MP의 아키텍쳐에서 MemoManager의 역할은 일반적인 데이터 전달 객체 이상입니다.

경우에 따라서는 MemoManager에 비지니스 로직이 담기는 경우도 있습니다.

(엄밀히 말하자면, MemoManager의 여러 부분 클래스 중 MP가 생성해 낸 부분 클래스에는 데이터 전달 객체로서의 역할만 하고, 사용자가 작성한 부분 클래스에는 비지니스 로직이 들어가는 것이 MP의 설계 의도입니다.)

그래서 MemoDto라는 이름은 적당하지 않은 것 같아 고심하다 장고 끝에 MemoManager라고 결정을 하였었습니다.

하지만 이 MemoManager라는 이름도 별로 마음에 들지 않습니다.

Manager가 다소 애매모호한 의미를 가지고 있어, MemoManager라는 이름을 들었을 때 직관적으로 MemoManager의 역할이 연상되지 않기 때문입니다.

(이렇게 애매모호하여 잘못된 이름의 또 다른 예로 GeneralUtility 같은 이름을 들 수 있겠습니다. 경험 상 이러한 이름의 클래스가 나타나게 되면 클래스 간의 설계에 빈 틈이 있을 확률이 높았습니다.)


결국 MemoManager를 MemoBiz로 변경하기로 하였습니다.

아래는 MP의 Defulat 패키지가 생성해 낸 코드의 구조입니다.

이전과 비교해서 이름이 바뀐 폴더나 파일에는 밑줄을 그어 두었습니다.

하나씩 살펴보면,

  1. Managers 폴더 -> Biz 폴더
  2. [Entity]Manager -> [Entity]Biz  // [Entity]는 각 엔터티(예, Memo)의 이름
  3. EntityManager -> EntityBiz
  4. ManagerRepository -> Business

와 같습니다.


각 Biz 객체에 접근하는 Facade 역할을 하는 ManagerRepository 클래스의 이름이 Business로 변경되었기 때문에, 위의 코드를 사용하는 코드도 아래와 같이 변경되어야 합니다.

:

MP 버그 하나

Midnight Peach 2008. 12. 8. 15:47

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

EntityManager<T> 클래스의 T GetFirst<U>(Expression<Func<T, bool>> where, Expression<Func<T, U>> orderBy, bool ascending) 메서드에서 버그를 발견하였습니다.

정렬 식을 null 이고 내림차순으로 첫번째 요소를 가져오면(GetFirst(null, null, false)와 같이  ) 오동작을 합니다.

query = query.OrderByDescending(orderBy) 문장이 실행되어야 하지만, if (orderBy != null) 조건에 의해  query = query.OrderBy(orderBy) 문장이 실행되기 때문입니다.

아래는 수정된 코드 입니다.

중간에 query.Count()로 쿼리식이 실행되는 부분이 마음에 들지는 않지만, 현재로서는 이게 최선의 방법인 것 같습니다. 좀 더 고민을 해봐야겠습니다.

수정된 버전은 클릭원스에 반영되어 있으므로 MP를 실행하면 자동으로 새 버전을 다운받게 됩니다.

:

MPML (Midnight Peach Macro Language)

Midnight Peach 2008. 10. 26. 13:44

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

MP의 템플릿은 고유한 문법을 가지고 있습니다.

이 문법으로 기술된 언어를 Midnight Peach Macro Language, 줄여서 MPML 라고 합니다.

이번 포스트에서는 이 MPML의 문법에 대해서 살펴보도록 하겠습니다.

 

이전 포스트인 MP의 구성에서 말씀드렸듯이, MP는 패키지 DBML에 정의되어 있는 엔터티 클래스를 원본으로 하여 (패키지에 포함된) 템플릿을 치환하는 방식으로 코드를 생성합니다.

이때 템플릿에는 엔터티의 클래스를 어떻게 치환하여야 하는지에 대한 정보가 기술되어 있습니다. 이 정보가 일정한 규칙(문법)을 따라야만 치환이 정상적으로 이루어집니다.

바로 이 규칙(문법)을 정의해 놓은 것이 MPML입니다.

 

만일 사용자가 MP에 내장된 Default 패키지를 수정하여 사용하여야 한다면, 패키지에 포함된 템플릿을 수정하여야 합니다.

템플릿을 수정하기 위해서는 MPML의 문법에 대해서 알 필요가 있습니다.

결과적으로 MPML을 알아야 하는 이유는 패키지를 커스터마이즈 하여 사용자의 필요에 맞는 코드를 생성하기 위해서라고 하겠습니다.

 

MPML은 매크로를 정의하는 (언어라고 부르기도 힘들 정도록) 대단히 단순한 언어입니다. 따라서 문법도 무척 간략합니다.

먼저 MPML의  EBNF는 다음과 같습니다.

(참고로 EBNF, 즉 확장된 바커스-노어 폼은 언어의 사양을 정의하는 메타 언어입니다. EBNF에 대한 설명은 이 포스트의 범위를 벗어나므로 위키피디아의 항목 등을 참고하시기 바랍니다.)

 

template = {database | table loop | table | column loop | comment | variable | character} ;

database = "<#D " , "CamelName" | "PascalName" | "DataContext" , " D#>" ;

table loop = "<#A " , table loop type , ":", {loop seperator} , ":", {character | table} , " A#>" ;

table loop type = "AL" | "TB" | "VW" ;

loop seperator = "[N]" | "[T]" | character ;

table = "<#T " , "CamelName" | "PascalName" | "DbName" | "Schema" | "BeginCommentIfView" | "EndCommentIfView" , " T#>" ;

column loop = "<#O " , column loop type , ":" , {loop seperator} , ":", {character | column} , " O#>" ;

column =  "<#C " , "CamelName" | "PascalName" | "DbName" | "Type" | "DbType" | "BeginCommentIfReadOnly" | "EndCommentIfReadOnly",  " C#>" ;

column loop type = "AL" | "PK" | "NP" | "IN" | "NI" | "RO" | "NR" | "NU" | "NN";

variable = "<#V ", {character}, " V#>";

comment = "<#- ", {character}, " -#>";

character = [a-z] | [A-Z] | [0-9] | white space ;

 

위  EBNF에 대해서 실제 템플릿 코드와 함께 하나씩 살펴봅시다.

 

1. template = {database | table loop | table | column loop | variable | comment | character} ;

EBNF의 정의에 의하면 이 라인은, '템플릿은 database, table loop, table, column loop, variable, comment, character 요소의 반복으로 이루어져 있다'는 의미가 되겠습니다. 따라서 위 EBNF의 두 번째 라인부터는 각 요소들에 대한 정의가 이어집니다.

 

2. database = "<#D " , "CamelName" | "PascalName" | "DataContext" , " D#>" ;

database요소에 대한 정의입니다. "|" 는 "또는"의 의미이므로,  database는 "<#D "로 시작하고, "CamelName" 이나 "PascalName" 이나 "DataContext"가 이어지고, " D#>"로 끝난다는 말이 되겠네요.

그리고 이때 "CamelName" 이나 "PascalName" 이나 "DataContext"는 각각 데이터베이스의 소문자 이름, 대문자 이름, DataContext 클래스 이름으로 치환되도록 미리 정해진 속성입니다.

 

예를 들어 아래와 같은 템플릿을 만나면,

internal static <#D DataContext D#> CreateDataContext()
{
    return CreateDataContext(false, true);
}

MP는 <#D DataContext D#>를 이렇게 해석합니다.

'<#D 로 시작하고 D#>로 끝나니 이것은 (DBML) 데이터베이스에 대한 매크로이구나. 또한 DataContext 라고 했으니 이 부분을 DBML에 정의되어 있는 데이터베이스의 DataContext 객체 이름으로 치환해야겠구나'

따라서 다음과 같은 코드가 생성됩니다. (DBML의 DataContext가 MemopadDataContext라고 가정합니다.)

internal static MemopadDataContext CreateDataContext()
{

    return CreateDataContext(false, true);
}

 

database요소의 가능한 값은 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
PascalName 대문자로 시작하는 이름 <#D PascalName D#> ==> Memopad
CamelName 소문자로 시작하는 이름 <#D CamelName D#> ==> memopad
DataContext DataContext의 형식 이름 <#D DataContext D#> ==> MemopadDataContext

 

3. table loop = "<#A " , table loop type , ":", {loop seperator} , ":", {character | table} , " A#>" ;

table loop 요소는 조건에 해당하는 엔터티(테이블 혹은 뷰)를 반복하며 코드를 생성하는 요소입니다.

"<#A"와 "#A>" 태그로 시작하고 끝나는데, 태그의 내부는 ":" 으로 구분된 세 부분으로 이루어져 있습니다.

이는 각각 반복할 엔터티의 종류, 각 반복 시의 구분자, 반복 내용인데, EBNF 문법에서 알 수 있듯이, 첫번째 부분은 한 번만 나오는 반면에 두번째와 세번째 부분은 0회 이상 반복될 수 있습니다.

 

4. table loop type = "AL" | "TB" | "VW" ;

table loop type 요소는 반복할 엔터티의 종류를 지정합니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
AL 모든 엔터티

<#A AL:: A#> ==>

Contact, Member, Memo, Reading, ReadingView를 반복

TB 테이블인 엔터티만

<#A TB:: A#> ==>

Contact, Member, Memo, Reading를 반복

VW 뷰인 엔터티만

<#A VW:: A#> ==>

ReadingView를 반복

 

5. loop seperator = "[N]" | "[T]" | character ;

엔터티(테이블 혹은 뷰)와 엔터티의 속성(컬럼)이 반복될 때 각 반복 사이에 삽입될 문자열을 지정합니다. 반복 사이이기 때문에 마지막 반복 후에는 문자열이 삽입되지 않습니다.

가능한 값의 목록은 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
[N] 줄바꿈(\r\n)을 반복 사이에 삽입합니다.

<#A TB:[N]:<#T PascalName T#> A#> ==>

Contact

Member

Memo

Reading

[T] 탭(\t)을 반복 사이에 삽입합니다.

<#A TB:[T]:<#T PascalName T#> A#> ==>

Contact    Member    Memo    Reading

character 사용자가 지정한 문자열을 반복 사이에 삽입합니다. 아래 character 요소를 참조하십시오.

<#A TB:, :<#T PascalName T#> A#> ==>

Contact, Member, Memo, Reading

 

6. table = "<#T " , "PascalName" | "CamelName" | "DbName" | "Schema" | "BeginCommentIfView" | "EndCommentIfView" , " T#>" ;

table 요소는 엔터티의 여러가지 속성에 대한 매크로를 가지고 있습니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 Contact 테이블의 경우)
PascalName 엔터티의 대문자로 시작하는 이름 <#T PascalName T#> ==> Contact
CamelName 엔터티의 소문자로 시작하는 이름 <#T CamelName T#> ==> contact
DbName SQL 서버 테이블(뷰)의 이름
SQL 서버 테이블(뷰)의 이름과 엔터티의 이름은 다를 수 있습니다.
<#T DbName T#> ==> Contact
(테이블과 엔터티의 이름이 동일)
Schema SQL 서버 테이블(뷰)의 스키마.
일반적으로는 "dbo"이며, SQL 서버 2005 이상에서만 지원.
<#T Schema T#> ==> dbo
BeginCommentIfView 엔터티가 뷰인 경우 "/*"를 반환 <#T BeginCommentIfView T#> ==>
(Contact는 테이블이기 때문에 아무 것도 생성되지 않음)
<#T BeginCommentIfView T#> ==> /*
(엔터티가 Contact가 아니라 ReadingView인 경우)
EndCommentIfView 엔터티가 뷰인 경우 "*/"를 반환 <#T EndCommentIfView T#> ==>
(Contact는 테이블이기 때문에 아무 것도 생성되지 않음)
<#T EndCommentIfView T#> ==> */
(엔터티가 Contact가 아니라 ReadingView인 경우)

 

여기까지의 요소들이 합쳐진 템플릿의 예를 볼까요?

<#A AL:[N]:
public static <#T PascalName T#>Manager <#T CamelName T#> { get; private set; }
A#>

1)모든(AL) 엔터티에 대해 2)각 반복에 있어 줄바꿈문자([N])를 구분자로 하여 3)"public static <#T PascalName T#>Manager <#T PascalName T#> { get; private set; }"라는 문자열을 반복하라는 의미가 됩니다.

또한, 반복되는 "public static <#T PascalName T#>Manager <#T CamelName T#> { get; private set; }" 문자열에 있어서도 "<#T PascalName T#>"와 "<#T CamelName T#>"를 각각 엔터티의 파스칼 이름과 카멜 이름으로 치환을 하게 됩니다.

결과적으로 이 템플릿에 샘플 프로젝트의 DBML을 대입하면 아래와 같은 코드가 생성됩니다.

public static ContactManager contact { get; private set; }
public static MemberManager member { get; private set; }
public static MemoManager memo { get; private set; }
public static ReadingManager reading { get; private set; }
public static ReadingViewManager readingView { get; private set; }

 

7. column loop = "<#O " , column loop type , ":" , {loop seperator} , ":", {character | column} , " O#>" ;

column loop 요소는 table loop 요소가 엔터티에 대한 반복을 나타내는 것 처럼 엔터티의 속성에 대한 반복을 나타냅니다.

table loop 요소와 마찬가지로 ":" 으로 구분된 세 부분으로 이루어져 있는데, 역시 반복할 엔터티 속성의 종류, 각 반복 시의 구분자, 반복 내용에 해당됩니다.

이 중 반복 시의 구분자는 table loop와 동일하며, 반복 내용은 table 대신 column 요소가 사용된다는 점만 제외하면 동일합니다.

 

8. column loop type = "AL" | "PK" | "NP" | "IN" | "NI" | "RO" | "NR" | "NU" | "NN";

column loop type 요소는 반복할 엔터티 속성의 종류를 지정하는 것입니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명
AL ALl. 모든 컬럼  
PK Primary Key. 기본키인 컬럼  
NP Not Primary key. 기본키가 아닌 컬럼  
IN INcremental. 자동증가 컬럼  
NI Not Incremental. 자동증가가 아닌 컬럼  
RO Read Only. 읽기 전용 컬럼  
NR Not Read only. 읽기 전용이 아닌 컬럼  
NU NUllable. 널을 허용하는 컬럼  
NN Not Nullable. 널을 허용하지 않는 컬럼  

 

9. column =  "<#C " , "PascalName" | "CamelName" | "DbName" | "Type" | "DbType" | "BeginCommentIfReadOnly" | "EndCommentIfReadOnly",  " C#>" ;

엔터티의 속성에 대한 정보를 나타내는 요소입니다.

매크로 설명 (샘플 프로젝트의 Contact.Name 컬럼의 경우)
PascalName 엔터티 속성의 대문자로 시작하는 이름 <#C PascalName C#> ==> Name
CamelName 엔터티 속성의 소문자로 시작하는 이름 <#C CamelName C#> ==> name
DbName SQL 서버 테이블(뷰)의 컬럼 이름
SQL 서버 테이블(뷰)의 컬럼 이름과 엔터티의 속성 이름은 다를 수 있습니다.
<#C DbName C#> ==> Name
(테이블의 컬럼과 엔터티의 속성 이름이 동일)
Type 엔터티 속성의 C# 형식 <#C Type C#> ==> string
DbType SQL 서버 테이블(뷰) 컬럼의 형식 <#C DbType C#> ==> nvarchar(50)
BeginCommentIfReadOnly 읽기 전용 컬럼인 경우 "/*"를 반환 <#C BeginCommentIfReadOnly C#> ==>
(Contact.Name은 읽기 전용 컬럼이 아니기 때문에 아무 것도 생성되지 않음)
EndCommentIfReadOnly 읽기 전용 컬럼인 경우 "*/"를 반환 <#C EndCommentIfReadOnly C#> ==>
(Contact.Name은 읽기 전용 컬럼이 아니기 때문에 아무 것도 생성되지 않음)

 

10. variable = "<#V ", {character}, " V#>";

variable 요소는 런타임에 사용자가 입력한 값으로 치환되는 매크로 입니다.

즉 위의 모든 요소는 DBML 파일에 정의된 엔터티에 의해 치환이 되는 반면에, variable 요소는 사용자가 지정한 값에 의해 치환이 됩니다.

따라서 variable요소에는 미리 정해진 매크로가 없습니다.

사용자가 런타임에 입력하여야 할 변수의 이름은 패키지 마다 상이한데, 이는 패키지 정의 파일(pds)의 <Variable> 요소에 정의되게 됩니다.

 

예를 들어 기본 패키지인 Default의 경우 패키지 정의 파일에는 아래와 같은 코드가 있습니다.

<Variable>
  <Name>Namespace</Name>
  <Value>Memopad.Biz</Value>
  <Description>생성될 코드의 네임스페이스</Description>
</Variable>
<Variable>
  <Name>ConnectionString</Name>
  <Value>ConfigurationManager.ConnectionStrings[0].ConnectionString</Value>
  <Description>연결 문자열</Description>
</Variable>
<Variable>
  <Name>CollectionType</Name>
  <Value>List</Value>
  <Description>Get이 반환할 제네릭 컬렉션 형식. Add(T item) 메서드를 가지고 있어야 함.</Description>
</Variable>

이는 UI 상으로는 아래 그림과 같이 표시됩니다.

빨간 줄은 사용자가 입력하여야 할 변수의 이름이고, 파란 줄은 실제 입력하는 값입니다.

위 그림과 같이 Namespace 라는 변수에 Memopad.Biz 라는 값을 입력하였다면,

템플릿에서 <#V Namespace V#> 라는 매크로는 Memopad.Biz로 치환되는 것입니다.

 

10. comment = "<#- ", {character}, " -#>";

comment 요소는 코드의 생성과는 상관 없이 템플릿에 주석을 달기 위해 사용하는 요소입니다.

반복하지만 comment 요소는 코드로 치환되지 않습니다. 단지 템플릿 내의 주석을 달기 위한 용도로만 사용됩니다.

 

11. character = [a-z] | [A-Z] | [0-9] | white space ;

MPML의 정의에 의하면 character 요소는 '영어 대소문자, 숫자, 화이트 스페이스의 0번 이상 반복' 으로 정의되어 있긴 하지만, 그냥 일반적인 문자열로 생각하셔도 무방합니다.

EBNF의 규칙에 의하면 누구나 알 수 있을 만큼 명백한 요소에 대해서는 정의를 생략할 수 있습니다. MPML의 정의에 있어서도 white space 라는 요소는 생략되어 있습니다.

:

MP 튜토리얼에서 사용된 샘플 프로젝트

Midnight Peach 2008. 10. 15. 22:26

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

다운로드


Memopad가 사용하는 데이터베이스의 스키마는 다음과 같습니다.

SQL 파일은 위 소스에 포함되어 있습니다.


위 스키마를 가지고 생성한 DBML은 다음과 같습니다.



그리고 아래는 프로젝트의 모든 폴더를 펼친 그림 입니다.



샘플 프로젝트를 간단하게 하기 위해 데이터 액세스 레이어와 비지니스 로직 레이어를 분리하지 않았습니다.

Memopad.Biz 프로젝트는 데이터 액세스 레이어를 포함한 비지니스 로직 레이어입니다.


B 폴더는 MP가 생성한 코드이며,  A와 C 폴더는 각각 사용자가 작성한 커스텀 로직을 가지고 있는 엔터티와 엔터티 매니저 폴더 입니다.

A와 C 폴더에 있는 모든 클래스들은 부분 클래스로 구현되어 있습니다.

따라서 데이터베이스의 스키마가 변경되거나 하여 DBML 파일이 수정된다면, MP로 다시 코드를 생성하게 되는데, 이때 새로 생성된 코드를 B 폴더에 덮어 쓰면 A와 C 폴더에 있는 사용자가 작성한 코드의 내용은 유지되게 됩니다.


Memopad.Test는 Memopad.Biz에 대한 단위 테스트 프로젝트입니다.

현재는 Memo 엔터티에 대한 단위 테스트 코드 만이 작성되어 있습니다.


단위 테스트를 수행하려면 다음과 같이 App.config 파일의 연결 문자열을 적절하게 수정하여야 합니다.


:

MP의 데이터베이스 트랜잭션 지원

Midnight Peach 2008. 10. 15. 18:03

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

MP는 LINQ to SQL을 기반으로 하고 있기 때문에, LINQ to SQL가 지원하는 것과 동일하게 데이터베이스 트랜잭션을 지원합니다.

바로 샘플 코드를 보면서 이야기하도록 하겠습니다.

 

샘플은 MemoManager 클래스에 트랜잭션을 타는 삽입 / 갱신 / 삭제 메서드를 추가하는 것 입니다.

MemoManager 클래스에 대해서는 이전 튜토리얼들을 참조하시기 바랍니다.

 

삽입

먼저 트랜잭션을 타는 삽입 작업을 봅시다.

 

 

MemopadDataContext 객체를 생성하고 Memo 엔터티 두 개를 삽입합니다.

SubmitChanges 메서드는 항상 내부적으로 트랜잭션을 타고 있습니다.

따라서 두 엔터티의 삽입 중 하나라도 에러가 발생하면 모든 엔터티의 삽입은 롤백 됩니다.

 

이를 사용하는 단위 테스트 코드를 볼까요?

 

 

Memo 엔터티 두 개를 트랜잭션을 태워 삽입하려고 합니다.

만일 트랜잭션이 커밋되면 삽입하기 전과 삽입한 후의 레코드의 갯수는 다를 것입니다.

하지만 삽입 작업 중 에러가 발생하면 트랜잭션이 롤백 되면 삽입 전후의 레코드의 갯수가 동일할 것입니다.

이 테스트에서는 그것을 체크하고 있습니다.

 

417번 줄은 (Memo의 Subject 에는 null을 입력할 수 없는 것을 이용한) 의도적으로 삽입 에러를 발생시키 위한 코드입니다.

 

테스트 결과는 물론 파란불 입니다.

즉, 첫번째 삽입이 수행된 후의 두번째 삽입에서 에러가 발생하여 첫번째 삽입을 롤백시킨 것을 알 수 있습니다.

 

갱신

이번에는 트랜잭션을 타는 갱신 메서드의 예를 봅시다.

 

 

75번과 78번 줄에서 Clone 메서드를 통해 원 객체의 복사본을 만든 이유는 memo0과 memo1이 로드된 DataContext와 갱신되는 DataContext가 서로 다르기 때문입니다.

로드하는 데 사용된 DataContext는 아마도 위 UpdateInTransaction 메서드를 호출하는 곳에서 생성되었을 것입니다.

반면에 갱신하는 데 사용되는 DataContext는 73번 라인에서 생성됩니다.

Attach 메서드에 대한 자세한 설명은 MSDN을 참고하시기 바랍니다.

 

이 메서드에 대한 단위 테스트 코드는 다음과 같습니다.

 

 

역시 null 값을 허용하지 않는 Memo.Subject 속성에 의도적으로 null을 집어넣어(434 번 줄) 트랜잭션의 롤백을 일으키고 있습니다.

 

삭제

마지막으로 삭제 메서드를 살펴 봅시다.

 

 

복사본을 만들어 DataContext에 Attach 하는 것은 갱신의 경우와 동일합니다.

그리고 DeleteOnSubmit 메서드를 호출하여 삭제 작업을 기록해 둡니다.

실제 삭제는 106번 줄에서 SubmitChanges 메서드가 호출될 때 일어나는데, 기록된 삭제 작업 중 하나라도 에러가 발생하면 전체 삭제가 롤백 됩니다.

 

이에 대한 단위 테스트 코드는 아래와 같습니다.

 

 

코드에서 드러나지는 않지만, Memo 객체는 Reading 객체와 참조 관계를 가지고 있습니다.

따라서 Reading 객체에 연결된 Memo 객체를 삭제하려고 하면 에러가 발생합니다.

450번 라인에서 list[0] 객체는 Reading에서 참조되지 않지만, list[3]는 Reading에서 참조되고 있는 객체입니다.

그래서 두 Memo 객체를 트랜잭션으로 묶어 삭제하는 작업은 롤백이 되어, 삭제 전과 후의 전체 레코드의 갯수는 동일합니다.

테스트는 이 사실을 검증합니다.

:

MP 1.1 업데이트

Midnight Peach 2008. 10. 14. 01:01

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

설명 컬럼

패키지의 변수 테이블에 설명 컬럼이 추가되었습니다.

 

CollectionType

기본 내장 패키지인 Default에 새로운 변수 CollectionType이 추가되었습니다.

기존에는 Get 메서드가 반환하는 컬렉션 타입이 List<T>로 고정되어 있었는데, 이제는 이를 직접 지정할 수 있습니다.

 

예를 들어 CollectionType을 기본값인 List로 그냥 두면,

와 같은 코드가 생성되나, 이를 BindingList로 지정하면,

로 변경됩니다.

 

CollectionType에 지정할 수 있는 타입은 ICollection<T> 제네릭 인터페이스를 구현하여야 합니다.

이 요구사항만 충족하면 Kimgwajang.Collection.Vector와 같은 커스텀 컬렉션도 사용할 수 있습니다.

 

컬렉션에 대한 삽입 / 갱신 / 삭제

엔터티 단위가 아닌 컬렉션 단위로 Insert / Update / Delete를 실행할 수 있게 되었습니다.

이제 많은 수의 엔터티를 삽입 / 갱신 / 삭제 할 때는 각 엔터티를 N번 전달하는 대신에 엔터티의 컬렉션을 전달할 수 있게 되었습니다.

즉, 기존에는 아래와 같이 작성하던 코드를

 

다음과 같이 좀 더 간단하게 작성할 수 있게 되었습니다.

 

이는 단순히 삽입 / 갱신 / 삭제를 호출하는 코드의 양이 조금 줄어든 것 이상의 의미가 있습니다.

컬렉션을 대상으로 삽입 / 갱신 / 삭제를 하면 컬렉션의 각 엔터티에 대해 매번 DataContext 객체를 생성하는 것이 아니라, 컬렉션 전체에 대해 하나의 DataContext 객체만을 생성하기 때문에, DataContext 객체를 생성하는 오버헤드를 줄일 수 있습니다.

 

아래 코드는 EntityManager<T>에 새로 추가된 Delete 메서드 입니다.

코드에서 보듯이 DataContext 객체가 하나만 생성됨을 알 수 있습니다.

또한 반환값으로 삭제된 엔터티의 갯수가 반환되는 것도 확인할 수 있습니다.

 

삽입 / 갱신과 엔터티의 상태 추적

또한 삽입과 갱신의 경우에는, 전달된 컬렉션의 모든 엔터티에 대해 무조건 삽입 혹은 갱신하는 것이 아니라, 엔터티의 상태에 따라 삽입 혹은 갱신 작업이 결정됩니다.

예를 들어 아래와 같은 코드가 있다고 합시다.

 

291번 줄에서 list에는 5 개의 메모가 담기게 됩니다.

그 중 하나의 엔터티를 수정하고, 컬렉션 전체에 대해서 Update를 호출합니다.

반환값인 count에는 Update 메서드에 의해 갱신된 엔터티의 갯수가 들어가는데, 여기서는 이 값이 1이 됩니다.

즉, 5 개의 엔터티가 있는 컬렉션에서 한 엔터티를 수정하고, 이 컬렉션을 Update 하면 갱신된 엔터티의 숫자가 1인 것을 알 수 있습니다.

 

이것이 가능한 이유는 각 엔터티가 자신의 상태를 가지고 있고, Update 메서드가 이 상태를 추적하여 갱신 여부를 판별하기 때문입니다.

 

주의 : 현재 버전의 MP에서 삭제 작업에는 엔터티의 상태 추적이 사용되지 않습니다. Delete에 컬렉션이 전달되면 컬렉션 내의 모든 엔터티가 (그 상태에 관계 없이) 삭제됩니다.

 

엔터티의 상태

엔터티는 세 가지 중 한 가지 상태를 가집니다.

 

 

각 상태 간의 변경은 아래 상태 다이어그램과 같습니다.

데이터베이스에서 로드되었는지 혹은 사용자가 생성하였는지에 따라, 엔터티는 Clean 혹은 Created 상태를 가지게 됩니다.

 

그림에서 보듯이, Created 상태의 엔터티는 Insert 메서드가 실행됨에 의해 Clean 상태로 밖에 변할 수 없습니다.

Clean 상태는 사용자가 엔터티의 값을 변경하면 Dirty 상태가 되었다가, Update 메서드가 실행되고 나면 다시 Clean 상태로 변경됩니다.

또한 Created 상태의 엔터티가 Clean 상태를 거치지 않고 Dirty 상태가 될 수도 없고, Clean 상태의 엔터티가 Created 상태가 될 수도 없습니다.

 

이렇게 엔터티의 상태를 추적하는 이유는 Insert 와 Update 메서드에 엔터티의 컬렉션이 전달될 때, 컬렉션 전체에 대해서가 아니라 해당하는 엔터티에 대해서만 삽입 혹은 갱신 작업을 하기 위해서입니다.

즉, Insert 메서드는 전달 받은 컬렉션에 있는 Created 상태의 엔터티에 대해서만 실행이 되고 Update는 컬렉션의 Dirty 엔터티에 대해서만 실행이 되는 것입니다.

 

한 가지 주의하여할 할 점은, 커스텀 로직을 추가하는 경우에는 엔터티의 초기 상태를 직접 설정하여야 한다는 것입니다.

엔터티 객체가 생성되면 기본값으로 엔터티 상태는 EntityStaus.Created가 됩니다.

하지만 아래와 같이 EntityManager의 Get 메서드 대신 데이터베이스에서 엔터티를 로드하는 코드를 작성한다면, 반드시 엔터티의 상태를 EntityStatus.Clean으로 변경하는 코드를 포함하고 있어야 합니다.

(그러지 않으면 이렇게 생성된 엔터티들은 갱신이 되지 않습니다.)

 

 

코드의 마지막에서 호출하는 ToCleanCollection 메서드는 EntityManager에 정의되어 있는 헬퍼 메서드입니다.

컬렉션의 각 원소의 엔터티 상태를 EntityStatus.Clean으로 설정한 후, 새로운 컬렉션에 담아 반환하는 일을 합니다.

 

 

위 메서드는 List<T>형만 반환하는 특화된 버전인 반면에, ToCleanCollection 메서드에는 제네릭 버전도 있습니다.

 

 

아래는 제네릭 ToCleanCollection을 사용하는 예제입니다.

 

:

MP의 구성

Midnight Peach 2008. 10. 10. 14:35

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

MP의 주요 구성 요소를 도해하면 대략 아래와 같은 그림이 됩니다.

DBML과 패키지는 코드를 생성하기 위해 사용자가 제공하는 요소이며, 스키마는 코드 생성을 위해 DBML에 있는 데이터베이스 모델을 추상화시킨 엔터티입니다.

 

코드 생성기가 코드를 생성하기 위해서는 두 개의 입력이 필요한데, 스키마와 패키지가 그것입니다.

스키마는 스키마 추출기가 DBML 파일에서 추출하며, 패키지는 MP의 내장 패키지 혹은 사용자 정의 패키지로 제공될 수 있습니다.

 

MP의 범용성은 바로 이 두 가지 입력에서 기인합니다.

DBML에 있는 데이터 모델을 바로 이용하지 않고 스키마 라는 추상 구성요소를 두고 있기 때문에, 다른 데이터베이스 시스템을 지원할 수 있습니다.

만일 MySql이나 Oracle을 지원해야 한다면, 각각 MySql과 Oracle용 스키마 추출기만 추가하면 될 것입니다.

 

MP는 LINQ to SQL에 기반을 두고 있기 때문에 MS SQL 서버 외의 다른 데이터베이스는 지원하지 않습니다만, MP의 전신이라고 할 NOW 라는 코드 생성기에서는 위와 같은 구조에 의해 다른 데이터베이스를 지원합니다. (NOW는 아직 공개할 만한 수준이 되지 않아 공개하지 못하고 있습니다)

 

MP의 범용성을 이루는 두 번째 요소는 패키지 입니다.

패키지는 한 개의 패키지 정의 파일(확장자가 pds)과 여러 개의 템플릿 파일(확장자가 tpl)로 이루어져 있습니다.

 

패키지 정의 파일은 패키지와 그에 속한 템플릿에 대한 각종 설정이 저장되는 XML 파일 입니다.

예를 들어 아래 그림은 패키지 정의 파일에 대한 설정을 지정하는 것입니다.

 

패키지 정의 파일에는 두 개의 XML 요소(Element)가 있습니다.

패키지에서 사용되는 변수를 정의하는 Variable과 템플릿의 정보를 설정하는 Template 요소가 그것입니다.

(GeneratedCode 라는 요소도 있지만 이는 MP 내부에서 임시로 사용되기 때문에 여기서는 제외합니다.)

아래 그림은 패키지 정의 파일의 XSD를 XSD 디자이너에서 연 화면 입니다.

 

패키지 정의 파일과 함께 패키지를 이루는 두 번째 구성품은 템플릿입니다.

템플릿은 매크로가 포함된 단순한 텍스트 파일입니다.

코드 생성기는 템플릿에 포함된 매크로를 스키마에 있는 적절한 값으로 치환시켜 코드를 생성합니다.

 

예를 들어 MP가 생성한 아래 코드에 대해 생각해봅시다.

이 코드를 생성하는 템플릿은 아래와 같습니다.

이 템플릿에 스키마 추출기가 추출해 낸 Memo 라는 스키마 엔터티를 전달하면 위와 같은 코드가 생성되는 것입니다.

이 경우 Memo를 템플릿의 소스라고 할 수 있겠습니다.

 

마지막으로 템플릿은 적용 범위를 가집니다.

템플릿의 소스가 데이터베이스이냐 테이블이냐에 따라서, 이는 "database" 혹은 "table" 중의 하나입니다.

 

위 템플릿 예제는 "table" 적용 범위를 가진 템플릿입니다. 따라서 템플릿의 소스는 테이블인 Memo가 됩니다.

반면에 아래 템플릿과 그에 의해 생성된 코드는 "database" 적용 범위를 가진 템플릿의 예입니다.

 

 

: