LazarusCompleteGuide:12.2

From 흡혈양파의 번역工房
Jump to navigation Jump to search

데이터베이스 접근 클래스

TDataset

TDataset 은 테이블로 된 데이터를 조작하는 모든 데이터베이스 컴포넌트가 파생되는 기반 컴포넌트이다. 이는 모든 데이터베이스 연산(operations)에 기반을 제공한다. 데이터 인식 애플리케이션과 관련된 프로퍼티와 메소드는 이미 TDataset에 정의되어 있기 때문에 어떤 TDataset 자손도 TDataset에 많은 기능을 추가하진 못한다. 이러한 자손들과 작업하기 위해서는 TDataset 에 대해서만 알면 된다. 사실상 모든 시각적 데이터베이스 컴포넌트는 TDataset 만 알고 있으므로 그 자손들 중 어느 것이나 작업할 수 있다.


TDataset의 핵심은 데이터를 포함하는 메모리 버퍼이다. 해당 데이터의 소스는 그 자손들에게 명시되어 있어야 한다. 데이터는 행(레코드)으로 조직되는데, 격자 내 행에 해당한다. 데이터베이스의 2차원적 구조를 완성하기 위해 수직 열도 제공된다. 이 열은 이름을 통해 가로로 접근할 수 있다: 필드. 필드 또는 열은 하나 또는 그 이상으로 존재할 수 있다. TDataset 클래스는 데이터를 검색하고 조작하기 위한 메소드를 도입한다. 또한 데이터에 커서를 도입하였다. 커서의 현재 위치에 있는 데이터만 (현재 레코드) 표시 및 조작 가능하다. 특정 행의 데이터를 변경하기 위해서는 커서를 다른 행으로 이동시켜야 한다. TDataset은 데이터를 검식 시 필요한 메소드와 프로퍼티를 제공한다.


TDataset 자체는 데이터로 접근을 제공하지 않는다. 실제 데이터는 자손 클래스에 의해 인출되며(fetch), 이는 TDataset이 관리하는 메모리 버퍼로 데이터가 복사되는 TDataset의 추상적 보호(abstract, protected) 메소드의 수를 오버라이드해야 한다. 메모리 내 데이터가 변경될 때마다 데이터베이스나 파일에 일어난 변경내용이 자동으로 적용된다. 이 모든 것은 사용자에게 숨겨지며, 사용자는 4가지 데이터 조작 메소드만 알면 된다. TDataset 은 추상 클래스로서, 메모리에서 절대 인스턴스화 되어선 안 된다. 수많은 자손들이 있으므로 접근할 데이터에 따라 사용할 자손을 하나 선택할 수 있다. TDataset에 동료(companion) 컴포넌트도 여러 개가 있는데 TDataset 자손과 작업을 위해선 이 또한 이해할 필요가 있다:

  • TField: TField 클래스는 현재 레코드 내 단일 필드를 표시하고, 필드의 값을 검색하거나 필드의 값을 설정하기 위한 메소드를 제공한다. 값은 문자열, 정수, 부동소수점, 통화(currency), TDateTime 타입과 같이 오브젝트 파스칼이 지원하는 기본 타입 중 어떤 것으로든 설정 또는 검색할 수 있다. BLOB 필드의 경우 값은 텍스트로 검색하거나 메모리 버퍼 내에서 검색 가능하고, 파일로 저장할 수도 있다. 값을 GUI에 어떻게 표시할 것인지 명시하도록 다양한 프로퍼티가 제공된다.
  • TDatabase 컴포넌트는 현재 레코드 내 다양한 필드로 접근을 제공하는 Fields 라는 이름의 배열 프로퍼티를 포함한다-이 배열 내 요소들은 TField의 자손들이다. 레코드 내 위치한 각 데이터 타입마다 다른 자손이 있다.
  • TDatasource 는 TDataset 의 동료 컴포넌트로서, 데이터세트의 변경 내용을 제3의 컴포넌트로 (주로 GUI 컨트롤) 전달한다. GUI 컨트롤은 절대로 TDataset 인스턴스와 직접 통신하는 일이 없지만 항상 TDatasource를 중재자(mediator)로 이용한다. 하나의 TDataset 는 다수의 TDatasource 인스턴스로 연결할 수 있다. 이는 다른 컴포넌트, 즉 GUI 컨트롤로 데이터 업데이트 이벤트를 선택적으로 분배할 수 있도록 해준다.
  • TDatamodule: 폼에 TDataset 또는 TDatasource를 위치시키는 것이 가능하긴 하지만 그다지 좋은 설계 연습은 아니다. GUI 로직을 데이터 로직과 분명하게 구분하기 위해서는 TDatamodule 자손을 생성하는 것이 좋겠다. TDatamodule 컴포넌트 자체는 어떤 기능도 없다. 이는 단지 데이터와 GUI 로직을 쉽게 구분하는 장점밖에 없음에도 불구하고 RAD 환경이 제공하는 쉬운 조작을 모두 허용한다. 데이터모듈은 모든 데이터세트 컴포넌트에 대해 상자(container) 역할을 하며, 라자루스 IDE에서 시각적 표현을 가지므로, 폼을 조작하면서 데이터모듈도 조작할 수 있겠다. 데이터모듈은 실행 중인 애플리케이션에서는 볼 수 없다 (애플리케이션 메모리에만 존재).

폼의 GUI 요소를 데이터모듈 상의 데이터소스나 데이터세트로 연결하기 위해서는 그것이 위치하는 유닛을 폼 유닛의 uses 절로 추가해야 한다. 그러면 컨트롤의 DataSource 프로퍼티의 드롭다운 리스트에서 하나를 선택하여 폼 위의 컨트롤들을 올바른 TDatasource 인스턴스로 쉽게 연결될 수 있다.


위의 4가지 클래스는 라자루스가 그 데이터 접근 메커니즘을 구현하는 방법에 속한다. 실제 연결은 TDataset 의 자손들이 구현한다. 종종 이 자손들은 데이터베이스로 연결을 나타내는 다른 컴포넌트를 참조하기도 한다. 데이터세트가 (예: 다른 테이블을 표시하는 데이터세트 또는 쿼리의 결과) 단일 데이터베이스 컴포넌트로 연결될지도 모른다. 하지만 이것이 데이터베이스 테이블로 연결하는 유일한 방법은 아니다. 데이터베이스 연결이 필요 없는 데이터 컴포넌트도 많기 때문이다. TDBF 컴포넌트와 같은 단일 파일 데이터세트는 데이터베이스라는 개념이 없고, 단층 파일 모델을 이용해 디스크 상에서 개별 파일만 처리한다. 따라서 기본 라자루스 데이터 아키텍처는 데이터베이스 추상화 개념을 제공하지 않는다.


데이터 검색하기

데이터세트 개념은 데이터세트의 레코드 세트에서 현재 레코드의 위치를 표시하는 커서를 활용한다. 현재 레코드의 데이터만 읽거나 조작할 수 있다. 데이터세트를 검색(navigate)한다는 것은 데이터세트 내 레코드에서 커서를 움직임을 의미한다. 테이블 레코드를 변경하려면 레코드의 내용을 편집하기 전에 해당 레코드를 검색해야 한다. TDataset 는 데이터세트 내에서 레코드를 검색하고 조사하는 메소드를 소개하며, 표 12.1에 열거된 프로퍼티들도 제공한다.

프로퍼티 설명
Active 부울(Boolean) 프로퍼티. True일 경우, 데이터세트가 개방되고, 브라우징과 편집에 데이터를 이용할 수 있다. False일 경우, 데이터세트가 닫히고, 데이터를 이용할 수 없다. 프로퍼티는 데이터스를 열거나 닫도록 True 혹은 False로 설정 가능한데, 둘다 IDE 혹은 코드에서 런타임 시 설정 가능하다. 데이터베이스를 개방 시 실제 데이터로 접근할 것이며, 이는 디스크상의 파일이 개방되거나 쿼리가 데이터베이스에서 실행됨을 의미한다.
이는 예외를 발생시킬 수 있음을 명심한다 (예: SQL 문이 오류를 포함하거나, 데이터를 읽어야 하는 파일을 이용할 수 없는 경우).
RecNo 데이터세트의 행에서 커서의 현재 위치를 나타낸다. 항상 이에 의존할 수는 없다. 일부 데이터세트는 단방향인데 (전방향 검색만 허용), 기반이 되는 데이터 접근 메커니즘이 행들 간 양방향 검색을 허용하지 않기 때문이다.
RecordCount 데이터세트 내 레코드의 수를 의미한다. 항상 이용 가능하거나 신뢰가 가는 수치는 아니다. 일부 데이터세트는 커서가 데이터를 이동할 때 추가 레코드를 인출하고, 더 많은 레코드를 인출하면서 레코드 계수를 업데이트한다.
BOF 커서가 세트의 시작에 위치한다. 이전 레코드로 이동 시 아무런 효과도 없을 것이다.
EOF 커서가 세트의 끝에 위치한다. 다음 레코드로 이동 시 아무런 효과도 없을 것이다.
Fields TField 인스턴스의 인덱서(indexed) 배열이다. 각 인스턴스는 현재 레코드 내 필드를 나타낸다. 해당 필드는 색인 또는 이름으로 (아래 참조) 접근할 수 있다.
Modified 버퍼 내 데이터가 수정되었는지를 나타낸다.
State 데이터베이스의 현재 상태를 나타내는 상태 프로퍼티로서, 읽기 전용 프로퍼티이다. 가장 일반적인 상태는, 데이터세트가 닫혔을 때 dsInactive; 데이터가 브라우징(browsed)될 때 dsBrowse; 현재 레코드를 편집할 때 dsEdit; 새 레코드를 추가 중일 때 dsInsert 가 있다.
FieldDefs TfieldDef 항목의 집합체(collection)이다. 각 항목은 행에 필드의 정의를 포함하고, 그 집합체 내 요소마다 Fields 배열에 해당하는 TField 인스턴스가 있다. 이 집합체는 자동으로 TDataset 자손으로 채워지므로 당신이 접근하고자 하는 데이터를 정확하게 표시한다. 데이터세트가 개방되면, TDataset 자손은 필요한 TField 인스턴스를 생성하기 위해 FieldDefs 집합체를 사용할 것이다. 이는 은밀히 진행되기 때문에 FieldDefs 집합체로 직접 접근할 필요가 없는 것이 보통이다. 인 메모리 데이터세트는 예외인데, 실제 데이터를 생성하기 위해선 FieldDefs 집합체를 적절한 정의로 채워야 한다.
Filter 데이터세트 내 레코드를 필터링하기 위한 필터 표현식(filter expression)이다. 구현 여부는 사용 중인 TDataset 엔진에 따라 좌우되는데, TDataset 자체가 필터의 구현을 포함하지는 않기 때문이다. 즉, 필터의 구문은 실제 사용 중인 TDataset 자손에 따라 좌우되지만 대부분 구현은 필터에 대한 SQL-compliant 구문을 준수한다.
Filtered 부울 프로퍼티로, Filter 프로퍼티의 필터 표현식을 적용해야 할지 여부를 나타낸다.
표 12.1: TDataset의 프로퍼티


데이터세트가 개방되고, EOF와 BOF가 모두 True인 경우, 데이터세트가 비어 있음을 의미한다 (포함된 레코드가 없음). 사용 중인 TDataset 자손에 대해 RecordCount=0은 구현되지 않을 수도 있으므로 해당 검사는 권장하지 않는다. 아래만이 유일하게 올바른 검사가 될 것이다:

  Var D : TDataset;
begin
  D.Active:=True;
  try
    if (D.EOF and D.BOF) then
      raise Exception.Create('No data available!');
    // Do something with D if there is data.
  finally
    D.Close;
  end;
end;


메소드

데이터의 검색에 이용할 수 있는 메소드를 표 12.2에 열거하였다.

메소드 설명
Open 데이터세트를 연다. Active 프로퍼티를 True로 설정하는 것과 동일하다. 데이터세트를 두 번 여는 것은 아무런 효과가 없다-두 번째 호출은 아무 것도 실행하지 않고 단순히 리턴할 것이다.
Close 데이터세트를 닫는다. Active 프로퍼티를 False로 설정하는 것과 동일하다. 데이터세트를 두 번 닫는 것은 아무런 효과가 없다-두 번째 호출은 아무 것도 실행하지 않고 단순히 리턴할 것이다.
Next 커서를 데이터세트의 다음 레코드로 이동한다. EOF가 True일 경우, 아무런 효과가 없다.
Prior 커서를 데이터세트의 이전 레코드로 이동한다. BOF가 True일 경우, 아무런 효과가 없다. 모든 TDataset 자손이 이 연산을 지원하는 것은 아니다. 일부 데이터세트는 한방향이라 전방향 검색만 허용한다.
First 커서를 데이터세트의 첫 번째 레코드로 이동한다. First를 호출하고 나면 BOF가 True가 된다. 다시 언급하지만, 모든 TDataset 자손이 이 연산을 지원하는 것은 아니다.
Last 커서를 데이터세트의 마지막 레코드로 이동한다. Last를 호출하고 나면 EOF가 True가 된다. 모든 데이터세트는 이 메소드를 지원한다.
MoveTo 이 메소드는 1을 기반으로 한 레코드 번호를 (RecNo) 파라미터로 수용한다. 번호가 매겨진 레코드로 점프할 것이다. 다시 언급하지만, 모든 TDataset 자손이 이 연산을 지원하는 것은 아니다.
Refresh 실제 소스로부터 데이터세트 내 데이터를 새로고침(refresh)하지만, 현재 레코드 위치는 (변경 가능 시) 변경하지 않는다. 파일 기반의 데이터세트의 경우, 디스크로부터 파일이 다시 읽힘을 의미한다.
표 12.2: navigation의 메소드


이러한 메소드와 프로퍼티를 이용해 데이터세트에서 읽기 전용 연산 대부분을 실행할 수 있다. 아래 예제 코드는 데이터세트 내에서 모든 레코드를 순회하고 각 레코드에 어떤 연산을 실행하도록 허용할 것이다:

  Var D : TDataset;
begin
  D.Open;
  Try
    While Not D.EOF do begin
      DoSomeOperation(D);
      D.Next;
    end;
  Finally
    D.Close;
  end;
end;


Try…Finally 블록 참고:

데이터의 조작 도중에 예외가 발생 시, 데이터세트는 어떻게 해서든 루틴의 끝에서 닫힐 것이다. 짧은 연산의 경우 실습하기 좋다.


필드 데이터 접근하기

현재 레코드의 필드 값은 Fields 프로퍼티를 통해 접근할 수 있다. 이는 TFields 클래스의 프로퍼티로, TField 자손에 대한 색인 배열을 기본 프로퍼티로 가진다. 배열은 0을 기반으로 하며 0부터 Count-1(Count라 함은 Fields의 프로퍼티) 범위까지 색인이 유효하다. 따라서 아래 코드는 색인 0으로 필드에 접근한다:

  Var F : TField;
begin
  F:= Dataset.Fields.Fields[0];
end;


각 요소는 항상 TField 클래스의 자손이다. 표 12.3은 TField 클래스의 가장 중요한 프로퍼티들을 소개하는데, 이는 필드 내용에 관한 정보를 제공한다.

프로퍼티 설명
FieldName 기반이 되는 데이터베이스에 알려진 필드 이름.
DataType 해당 필드 내 데이터의 데이터베이스 네이티브 타입.
FieldKind 데이터 필드, 색인(lookup) 필드, 계산된 필드 중 무엇에 해당하는지 표시.
Size 문자열과 BCD(2진화 10진수)의 경우, 가능한 문자 또는 BCD의 최대 값을 표시.
Precision BCD 값의 경우, 소수 자리 수를 의미.
Required 필드가 값을 필요로 하는 경우 해당 프로퍼티는 True가 될 것이며, 값은 데이터를 레코드로 포스팅하기 전에 설정되어야 한다. 데이터를 포스팅할 때 TDataset 는 Required가 True로 설정된 모든 필드들을 대상으로 비어 있지 않은지를 검사하고, 비어 있을 경우 예외를 발생시킨다. 기본 값으로는 기반이 되는 데이터베이스에 의해 공급된 값으로 설정될 것이지만 수정 가능하다. 예: 데이터베이스가 필요로 하지 않는다 하더라도 특정 상황에서 특정 필드를 설정하도록 요하는 경우.
ReadOnly 필드가 읽기 전용인지 나타낸다. 예를 들어, SQL 데이터베이스에서 계산(computed) 필드가 이에 해당할지도 모른다.
표 12.3: TField의 프로퍼티


표 12.3에 열거된 프로퍼티들은 필드가 포함할 데이터 유형을 (포함 방법도) 결정한다. 보통 필드는 하나의 데이터 유형만 포함하므로, 가령 정수 필드에 문자열을 저장하기란 불가능하다. 필드 내 데이터로 접근하기 위해 아래와 같은 읽기/쓰기 프로퍼티가 제공된다:

프로퍼티 설명
AsInteger 정수로서 데이터 값
AsString 문자열로서 데이터 값
AsDateTime TDateTime 값으로서 데이터 값
AsCurrency Currency 값으로서 데이터 값
AsFloat Double 값으로서 데이터 값
AsBoolean Boolean 값으로서 데이터 값
Value 변수(variant) 값으로서 데이터 값
표 12.4: TField 데이터로 접근 시 사용되는 프로퍼티


각 AsXXZ 프로퍼티는 요청된 타입으로 실제 데이터를 변환하고자 시도할 것이다. 가령 데이터베이스 필드가 문자열 필드일 경우, AsInteger 프로퍼티를 써서 값을 읽을 시 문자열 값을 Integer(정수)로 변환할 것이다. 데이터 내 문자열 값이 유효하지 않은 정수의 표현일 경우, EConvertError가 발생된다. 반대로 프로퍼티를 설정할 경우 데이터베이스가 예상하는 타입으로 값을 변환할 것이다.


데이터세트 내 첫 번째 필드는 정수 필드이고 현재 레코드에 필드 값이 123이라고 가정하면 아래와 같이 할당이 이루어진다:

 Var S : String;

begin
  S := Dataset.Fields[0].AsString;
end;


그 결과, 문자열 S는 '123'을 포함할 것이다ㅡ데이터는 올바른 타입으로 변환되었다. 다음 루틴은 데이터세트 D의 현재 레코드에 있는 모든 데이터를 콘솔로 덤프(dump)한다:

Procedure DoSomeOperation(D : TDataset);
  Var I : Integer;
      F : TField;
begin
  For I := 0 to D.Fields.Count - 1 do
    begin
      F := D.Fields[I];
      Write(F.FieldName:32,':');
      If (F.DataType = ftBlob) then
        Writeln('<BLOB>')
      else
        Writeln(F.AsString);
    end;
end;


위의 검색 예제와 함께 사용한다면 데이터세트 내 모든 데이터를 화면으로 덤프할 것이다.


필드의 이름이 알려진 경우 TDataset의 FieldByName 메소드로 접근할 수 있겠다. 위 예제를 계속하자면, 데이터세트 D는 아래와 같은 SQL 쿼리 결과를 포함한다:

SELECT FirstName, LastName, BirthDay From Persons;


그리고 DoSomeOperation은 아래와 같이 코드화할 수 있다:

Procedure DoSomeOperation(D : TDataset); Var BD : TDateTime;
begin
  Writeln('FirstName : ',D.FieldByname('FirstName').AsString);
  Writeln('LastName : ',D.FieldByname('LastName').AsString);
  BD := D.Fields.FieldByName('BirthDay').AsDateTime;
  Writeln('BirthDay : ',DateToStr(BD));
end;


변수 BD는 Fields 프로퍼티에서 검색됨을 명심한다. 데이터세트가 요청된 필드명으로 된 필드를 포함하지 않을 경우 FieldByName은 예외를 발생할 것이다. 이 행위를 원하지 않아 대안적 메소드인 FindField 를 이용할 경우 요청된 필드가 없을 시 단순히 NIL을 리턴시킬 것이다. 이를 이용해 아래와 같이 필드의 존재 유무를 검사할 수 있다:

Function HasField(D : TDataset; FieldName : String) : Boolean;

begin
  Result := D.FindField(FieldName) <> Nil;
end;


가변형 배열(variant array)을 이용해 필드 값을 검색(또는 설정)하는 대안적 방법도 있다. TDataset 클래스는 Variant 타입의 FieldValues 배열 프로퍼티를 가지는데, 아래와 같이 선언된다:

property FieldValues[FieldName : String] : Variant; default;


배열에서 색인은 필드명으로 된 문자열이며, 이 배열은 TDataset 클래스의 기본 프로퍼티이므로 아래와 같이 사용될 수 있다:

Procedure DoSomeOperation(D : TDataset);
  Var BD : TDateTime;
       S : String;
begin
  S := D['FirstName'];
  Writeln('FirstName : ',S);
  S := D['LastName'];
  Writeln('LastName : ',S);
  BD := D['BirthDay'];
  Writeln('BirthDay : ',DateToStr(BD));
end;


이러한 표기법은 비주얼 베이직에 사용되는 것과 유사하지만 사용을 권장하진 않는데, 가변형이 본래 충분하지 못하고 그 타입 변환 규칙이 예상치 못한 일을 야기할지 모르기 때문이다.


Fields 프로퍼티의 항목들이 모두 TField 타입은 아니라는 사실을 명심한다: 그보다는 TField의 자손들이다 (자손은 각 네이티브 데이터베이스 타입마다 제공된다). 데이터베이스로부터 데이터를 인출할 때는 각 TDataset 가 레코드세트 내 각 필드에 대한 네이티브 데이터베이스 타입을 TField 자손으로 매핑하고, DataType 프로퍼티는 데이터베이스에서 데이터의 타입을 가장 잘 설명하는 값으로 설정할 것이다.


표준 TField 의 자손을 몇 가지 이용할 수 있다:

자손 설명
TIntegerField 32 비트까지 부호 있는 정수 값. DataType은 ftInteger가 될 것이다.
TSmallIntField 16 비트까지 부호 있는 정수 값. DataType은 ftSmallInt가 될 것이다.
TWordField 16 비트 부호 없는 정수 값. DataType은 ftWord가 될 것이다.
TLargIntField 64 비트 정수 값. DataType은 ftLargeInt가 될 것이다.
TFloatField 부동 소수점 값. DataType은 ftDouble이 될 것이다.
TStringField 문자열 값. DataType은 ftString이 될 것이다.
TBCDField BCD 값. DataType은 ftBCD가 될 것이다.
TDateField 데이터 값. DataType은 ftDate가 될 것이다.
TTimeField 시간 값. DataType은 ftTime이 될 것이다.
TDateTimeField TDateTime 값. DataType은 ftDateTime이 될 것이다.
TBLOBField BLOB 데이터 (폼이 없는 바이너리 데이터). DataType은 ftBLOB가 될 것이다.
TMemoField 텍스트만 포함한 BLOB 데이터. DataType은 ftMemo가 될 것이다.
표 12.5: TField의 표준 자손들


어떤 필드가 사용되는지는 TDataset 자손에 따라 좌우된다. 좀 더 특수화된 TField 자손들도 존재하며, 매우 드물게 사용되긴 하지만 일부 TDataset 자손들은 특수화된 TField 자손들을 이용해 그들의 필드 타입에 대해 데이터베이스 특정적 행위를 캡슐화하기도 한다.


레코드 내 모든 필드가 데이터를 포함할 필요는 없다-비워 둘 수도 있다. 필드가 비어 있는지는 부울 IsNull 프로퍼티를 이용해 테스트할 수 있다. True일 경우 필드는 값을 가지지 않는다. 이는 SQL 의 값 NULL과 일치한다.


아래를 이용해 현재 레코드 내 모든 필더가 값을 가지는지 테스트한다:

With MyDataset do
 For i:=0 to Fields.Count-1 do
   if Fields[i].IsNull then
     Writeln('Field ',Fields[i].FieldName,' is empty');


레코드 위치시키기

때로는 특정 필드에 구체적인 값을 가진 레코드를 점프해야 하는 경우가 있다. 이때는 아래와 같이 정의된 Locate 메소드를 이용한다:

Type TLocateOption = (loCaseInsensitive, loPartialKey);
     TLocateOptions = set of TLocateOption;

function Locate( const keyfields: string;
                 const keyvalues: Variant;
                 Options: TLocateOptions  ) : boolean;


파라미터는 세미콜론으로 구분된 필드명 리스트이다; 단일 값 또는 값의 가변형 배열; 그리고 위치 옵션:

  • 검색은 대·소문자를 구분하지 않아도 되는가?
  • 부분적 키 일치, 즉 부분적으로 일치하는 문자열을 고려해야 하는가?


레코드가 일치하는 검색 기준을 찾을 경우 함수는 True를 리턴할 것이다. 이후 데이터세트는 첫 번째 일치하는 레코드로 위치될 것이다. 검색은 항상 데이터세트 내 첫 번째 레코드에서 시작된다. 즉, 아래 호출을 이용해 'FirstName' 필드에 AName 를 가진 레코드를 검색하게 됨을 의미한다:

Function FindFirstName(D : TDataset; AName : String) : boolean;

begin
  Result := D.Locate('FirstName',AName,[]);
end;


가변형 배열을 구성하기 위해 variants 유닛에서 VarArrayOf 함수를 아래 루틴에서와 같이 이용 가능하다:

Function FindPerson(D : TDataset;
  Const AFirstName,ALastName : String) : boolean;

begin
  Result := D.Locate('FirstName;LastName',
    VarArrayOf([AFirstName,ALastName]),[loCaseInsensitive]);
end;


이번 경우 검색은 대·소문자를 구분하지 않고 실행될 것이다.


북마크 사용하기

데이터세트 내 다른 레코드를 검색한 후에 특정 레코드로 돌아가길 원하는 경우가 있을 것이다. 이는 북마크를 검색하고 이후에 해당 북마크로 돌아가는 방식으로 이루어진다. TDataset의 Bookmark 프로퍼티가 이 목적에 사용되는데, 아래와 같이 선언될 수 있겠다:

propety Bookmark: TBookmarkStr read GetBookmarkStr
                               write SetBookmarkStr;


특정 레코드에서 북마크의 검색은 Bookmark 프로퍼티를 읽어옴으로써 실행된다. 북마크를 설정하면 데이터세트에서 북마크가 참조하는 위치로 점프할 것이다. 이것을 검색 루틴의 구현에 이용할 수 있는데, 아래에 예를 들어보겠다:

Function HasFirstName(D : TDataset; AName : String) : boolean;
  Var B : TBookMarkStr;
begin
  B := D.BookMark;
  Try
    Result := D.Locate('FirstName',AName,[loCaseInsensitive]);
  Finally
    D.BookMark := B;
  end;
end;


데이터세트가 첫 번째 이름 AName을 포함한 경우 위의 루틴은 True를 리턴하지만, 현재 위치에서 커서를 이동시키진 않을 것이다. 하나 이상의 북마크를 검색할 수 있다. 가령, 데이터세트에서 레코드의 선택을 유지할 때 이 방법을 사용할 수 있겠다.


데이터 조작하기

TDataset 는 데이터의 편집, 추가, 삭제를 위한 메소드도 포함하는데, 이를 표 12.6에서 소개하겠다. 데이터세트가 편집 모드에 있을 때는 어떤 검색 명령이든 검색 메소드 자체를 실행하기 전에 수정된 데이터의 포스팅(post)을 시도할 것이다.


데이터가 불완전하거나 유효하지 않은 경우, 예외가 발생하고 데이터세트는 편집 상태로 남을 것이다.


데이터세트가 현재 편집 모드 중 하나에 있는지 확인하기 위해 데이터세트의 State 프로퍼티가 검사될 것이다. 사실 특수 dsEditModes 상수는 모든 편집 모드를 포함하는 집합이다. 현재 데이터세트가 편집 모드들 중 하나에 해당한다면 아래 함수는 True를 리턴할 것이다.

메소드 설명
Edit 데이터세트를 편집 모드에 둔다. 이 명령 후 현재 레코드의 내용은 수정할 수 있다. (State 프로퍼티는 dsEdit과 동일해질 것이다.)
Insert 데이터세트를 현재 위치에서 삽입 모드에 둔다: 새 레코드가 추가된다. 이 명령 후 새 레코드의 내용은 수정할 수 있다. (State 프로퍼티는 dsInsert와 동일해질 것이다.)
Append Insert의 기능과 동일하나, 새 레코드가 레코드세트 끝에 추가된다는 점이 다르다. State 프로퍼티는 dsInsert 과 동일해질 것이다.
Post 데이터세트가 편집 또는 추가 모드에 있을 경우, Post 명령을 실행하면 레코드 버퍼에 내용이 변경되고, 데이터세트를 다시 브라우즈(browse) 모드로 되돌리는 동시 기반이 되는 데이터베이스에 변경 내용이 한 번에 적용될 것이다.
Cancel 데이터세트가 편집 또는 추가 모드에 있을 경우, Cancel 명령은 모든 변경사항을 취소하고 데이터세트를 Edit, Insert, 또는 Append 명령 이전 상태로 되돌린다.
Delete 이 명령은 레코드 버퍼로부터 그리고-사용되는 특정 TDataset 자손에 따라 좌우됨-기반이 되는 데이터베이스로부터 현재 레코드를 삭제한다. 데이터베이스로 이러한 액션을 취하는 데 어떤 Post도 필요하지 않다. 이 액션은 되돌릴 수 없으며, Cancel로도 실행취소할 수 없다.
표 12.6: 데이터의 편집, 추가, 삭제를 위한 메소드


Function CheckEditing(D : TDataset) : Boolean;
begin
  Result := D.State in dsEditModes;
end;


기존 데이터 수정하기

일반적으로, 기존 데이터의 수정은 데이터세트 내 올바른 레코드를 검색하여 데이터세트를 편집 모드로 둔 채로 이루어져야 State 프로퍼티가 dsEdit와 동일해진다. 그리고 나서 표 12.4에 설명된 AsXXZ 프로퍼티 중 하나를 통해 필드 값을 설정하여 데이터를 편집할 수 있다. 필드 값의 제거는 필드의 Clear 메소드를 호출하여 이루어진다.


모든 편집이 완료되면 Post 메소드를 이용해 편집 내용을 기반이 되는 데이터베이스로 적용시킬 수 있다. Post 메소드를 호출하기 전에는 모든 편집 내용이 메모리에만 존재한다: 실제 데이터는 아직 수정되지 않았다. Post 메소드를 호출하고 나면 데이터세트는 다시 브라우즈(browse) 모드가 된다.


아래 코드는 데이터세트 내 데이터를 수정하는 DoSomeOperation 구현의 예제이다:

Procedure DoSomeOperation(D : TDataset);
  Var I : Integer;
      F : TField;
begin
  If D.FieldByName('NeedsCheck').AsBoolean then
    begin
      Edit;
      I := FieldByName('CheckField').AsInteger;
      FieldByName('CheckField').AsInteger := I+1;
      FieldByName('NeedsCheck').AsBoolean := False;
      Post;
    end;
end;


위의 코드는 NeedsCheck 부울형(boolean) 필드를 검사할 것이다. True일 경우, CheckField 필드의 값을 1로 증가하고 NeedsCheck 필드는 False로 설정할 것이다. CheckTable이라 불리는 테이블의 모든 레코드에 대해 이 메소드를 호출할 경우, 아래 SQL 문과 연산이 같아질 것이다:

UPDATE CheckTable SET
  CheckField = CheckField + 1,
  NeedsCheck = False
WHERE
  NeedsCheck = True;


마찬가지로 아래와 같이 루프를 작성하면 전체 데이터세트에서 특정 필드(열)를 제거하는 것도 가능해진다:

Procedure ClearIfNeedsCheck(D : TDataset);
  Var F : TField;
begin
  With D do
    While not EOF do
    begin
      If FieldByName('NeedsCheck').AsBoolean then
      begin
        Edit;
        FieldByName('CheckField').Clear;
        Post;
      end;
      Next;
    end;
end;


확실히 'Checkfield' 필드가 필요한 필드가 아닐 경우에만 효과가 있을 것이다. 위의 코드는 아래 문을 이용해 SQL에서 실행할 수 있다:

UPDATE
  CheckTable
SET
  CheckField = Null
WHERE
  NeedsCheck = True;


새 데이터 삽입하기

Append 또는 Insert는 데이터세트에 새 레코드를 삽입하는 데에 사용할 수 있다. 두 메소드 모두 새 레코드를 생성한다. 전자는 데이터세트의 끝에 레코드를 추가하고, 후자는 현재 위치에 레코드의 삽입을 시도하거나 특정 위치에서 삽입을 지원하지 않는다면 Append로 돌아갈 것이다 (모든 데이터베이스가 테이블에서의 레코드 위치의 개념을 이해하는 것은 아니다). dBase 파일용 TDBF와 같이 파일 기반의 데이터세트는 현재 위치에서 레코드 삽입을 지원한다.


기존 데이터의 편집 시와 마찬가지로 새 레코드는 메모리에만 존재하며, 데이터세트의 Post 메소드가 호출될 때까지는 데이터베이스에 실제로 삽입되지 않는다. 아래 코드는 앞의 예제의 데이터세트에 새 레코드를 삽입할 것이다:

Procedure CreateNewRecord(D : TDataset);
begin
  With D do
  begin
    Append;
    FieldByName('CheckField').AsInteger := 0;
    FieldByName('NeedsCheck').AsBoolean := True;
    Post;
  end;
end;


SQL 문에서 해당 삽입 연산은 아래와 같이 작성된다:

INSERT INTO CheckTable
  (CheckField,NeedsCheck)
VALUES
  (0,True);


사실 일부 TDataset 자손들은 Post 연산 도중에 그러한 구문(statement)을 생성하고 기반이 되는 데이터베이스에서 실행할 것이다.


데이터 삭제하기

레코드를 삭제하려면 올바른 프로시저로 삭제해야 하는 레코드에 커서를 위치시킨 후 Delete 메소드를 호출한다. 현재 레코드는 삭제되고, 커서는 데이터세트의 다음 레코드로 위치할 것이다.


'Post' 메소드의 호출이 불필요함을 명심한다: delete 문은 즉시 효과가 나타나기 때문이다.


따라서 아래 코드를 사용하면 데이터세트 내 모든 레코드를 삭제할 것이다:

Procedure DelteAllRecords(D : TDataset);
begin
  D.First;
  While not D.EOF do
  D.Delete;
end;


데이터세트를 첫 번째 레코드로 위치시키는 것 외에 명시적 검색은 이루어지지 않음을 명심한다. 위의 코드는 SQL에서 아래와 같이 실행될 것이다.

DELETE FROM CheckTable;


다음은 데이터세트에서 모든 홀수 레코드를 삭제할 것이다:

Procedure DeleteEverySecondRecord(D : TDataset);
begin
  D.First;
  While not D.EOF do
  begin
    D.Delete;
    D.Next;
  end;
end;


편집내용 취소하기

새 레코드를 삽입하거나 기존 레코드를 편집하여 필드를 수정할 때는 연산을 취소해야 하는 경우가 발생한다. 예를 들어, 사용자가 고객의 주소를 편집하다가 고객을 애초에 잘못 선택했다는 사실을 뒤늦게 알아채는 경우가 있다. 편집내용을 취소하기 위해 Cancel 메소드를 호출할 수 있다. 이는 Edit 호출 이전의 상태로 레코드를 복구시킨다.


또 다른 시나리오는, Post 연산이 실패할 경우이다: 이런 경우 예외가 발생하며, 데이터세트는 편집 또는 삽입 모드로 유지될 것이다. 데이터세트를 다시 브라우즈 모드로 복원하기 위해서는 편집 모드를 취소해야 한다.


앞의 예제에서 'NeedsCheck' 필드가 필요한 필드라면, 아래 코드를 이용 시 Post 연산에 예외를 발생시킬 것이다:

Procedure TryInsert(D : TDataset; AValue : Integer);
begin
  Try
    D.Append;
    D.FieldByName('CheckField').AsInteger := AValue;
    D.Post;
  except
    On E : Exception do
    begin
      D.Cancel;
      Raise;
    end;
  end;
end;


예외 핸들러는 예외를 잡아내고 편집내용을 취소한 후 (데이터베이스를 브라우즈 모드로 복구), 예외를 재발생시킬 것이다.


데이터 조작 시 오류

데이터세트에서 데이터를 편집할 때는 다양한 오류들이 발생할 수 있음을 명심한다. Datset 클래스와 그 helper 클래스들은 수많은 검사를 실행하고 발견된 오류를 보고하므로 예외를 통해 오류가 보고된다.


오류 보고 시 자주 사용되는 클래스는 EDatabaseError 이다.


가장 먼저, 데이터세트가 편집 모드에 있지 않을 때 데이터 편집을 시도함으로써 자신이 발생시키게 될 오류의 타입을 고려해본다. 아래 코드는 EDatabaseError 예외를 야기할 것이다:

With MyDataset do
begin
  Open;
  FieldByName('MyField').AsString := 'Some sting';
  Post;
end;


데이터세트가 편집 모드에 있지 않을 때에는 필드로 쓰기가 허용되지 않으므로 Post 연산은 절대 도달할 수 없다.


그리고 나면 또 다른 흔한 타입의 오류로 인해 필드로 올바르지 않은 데이터 유형의 쓰기를 시도한다. SQL 타입 INT의 CUSTNO 필드를 가진다고 가정해보자 (이는 보통 DataType 값이 ftInteger 인 TIntegerField class에 일치할 것이다). 아래 코드는 EConvertError를 발생시킬 것이다:

With MyDataset do
begin
  Open;
  Append;
  FieldByName( 'CUSTNO' ).AsString := 'Some string';
  Post;
end;


오류가 발생되는 이유는 데이터세트가 'CUSTNO' 필드에 정수 값을 예상하는데 'Some String'은 유효하지 않기 때문이다.


또 다른 오류 유형으로, 레코드에 필요한 필드를 모두 공급하지 못하는 경우를 들 수 있겠다. 두 개의 문자열 필드, MyField와 MyRequiredField가 있는 데이터세트를 고려해보자. 아래 코드는 Post 명령이 실행될 때 예외를 발생할 것이다:

With MyDataset do
begin
  Open;
  Append;
  FieldByName( 'MyField').AsString := 'Some string';
  Post;
end;


이런 경우 EDatabaseError 오류가 발생하면서 유효한 값이 제공되지 않았으므로 MyRequiredField에 값이 필요하다는 표시가 뜰 것이다. 빈 문자열도 유효한 값이므로 빈 문자열을 제공해 이 문제를 해결할 수 있음을 명심한다. 아래 코드를 이용하면 오류가 발생되지 않을 것이다:

With MyDataset do
begin
  Open;
  Append;
  FieldByName('MyField').AsString := 'Some string';
  FieldByName('MyRequiredField').AsString := '';
  Post;
end;


데이터를 편집 시 필드 내용이 삭제되었을 때에도 동일한 오류가 발생할 수 있다:

With MyDataset do
begin
  Open;
  Edit;
  FieldByName('MyField').AsString := 'Some string';
  FieldByName('MyRequiredField').Clear;
  Post;
end;


TField의 Clear 메소드는 필드의 내용을 삭제한다. 필드가 필요하므로 Post 연산이 이를 허용하지 않는다. 데이터세트는 오류가 발생한 후 일관되지 않은 상태로 남겨질 것이고 상황을 바로 잡기 위한 액션이 필요할 것이므로 발생 가능한 오류를 인식하는 것이 중요하다. Post 연산에서 오류가 발생할 경우 (예: 필요한 필드에 어떤 값도 주어지지 않은 경우), 데이터세트는 편집 모드로 남을 것이다. 예를 들어, 아래 코드는 예외를 발생시킬 것이다:

With MyDataset do
  While Not EOF do
  begin
    Try
      Edit;
      FieldByName('MyRequiredField').Clear;
      Post;
    Except // Silently ignore any errors.
    end;
    Next;
  end;


코드가 오류를 해결하는 것처럼 보이지만 사실 그렇지 않다: 예외가 발생하면 예외는 Try...Except 블록에 의해 잡히지만 데이터세트는 여전히 편집 모드로 남겨진다. 이후 Next 문이 어쨌든 보류 변경의 포스팅을 시도할 것이며, 예외가 다시 발생되지만 이번에는 Try...Except 블록에 의해 잡히지 않는다. 이 오류를 올바르게 처리하는 방법은 앞절에서 소개한 바와 같이 보류 변경을 취소하는 방법이다:

With MyDataset do
  While Not EOF do
  begin
    Try
      Edit;
      FieldByName('MyRequiredField').Clear;
      Post;
    Except
     // Silently ignore any errors.
      Cancel;
    end;
    Next;
  end;


오류가 발생할 경우, Cancel은 변경내용을 취소하고 데이터세트를 다시 브라우즈 모드로 돌려놓을 것이다. Next 문은 단순히 다음 레코드로 이동시킬 것이다. 특정 오류를 무시할 수 있는지의 여부나 예외의 재발생 가능성 여부는 상황에 따라 달라진다. 중요한 건, Cancel 이 가능하도록 데이터세트를 일관된 상태로 놓는 것이다.


데이터세트 이벤트

앞 절에 설명한 오류 처리를 볼 때 어느 정도 오류 검사를 실행해야 함을 분명히 깨달았을 것이다. 예제에서와 같이 선형 부호(linear code)에서는 오류 검사를 쉽게 실행할 수 있지만, 데이터가 GUI에서 편집되어 사용자가 생성한 이벤트에 의해 액션이 실행되는 경우에는 (예: TDBNavigator 컨트롤의 버튼을 클릭하여) 오류 검사가 그다지 쉽지 않을지도 모른다.

고맙게도 TDataset는 이를 해결하는 다수의 이벤트를 제공한다. 앞 절에서 논한 대부분의 메소드의 경우 두 개의 이벤트가 있다. 이벤트에는 'Before'과 'After', 혹은 'On'이라는 접두사가 항상 붙는다. 이러한 이벤트는 TDataSetNotifyEvent 타입으로, 아래와 같이 선언된다:

TDataSetNotifyEvent = procedure(DataSet: TDataSet) of object;


인수는 일반 TNotifyEvent event handler 에서와 마찬가지로 TObject 대신 TDataset 타입이 된다.


'Before' 이벤트 핸들러에서 예외가 발생할 경우, 액션은 실행되지 않을 것이다: 따라서 호출되지 않았을 때와 동일한 상태일 것이다. 이를 이용해, 성공적인 메소드의 실행에 필요한 모든 조건을 충족시켰는지 검사할 수 있다.


표 12.7은 이용 가능한 Before 이벤트를 열거한다.

이벤트 설명
BeforeOpen 데이터세트가 실제로 열리기 전에 'Open' 메소드에 의해 실행된다. 주로 데이터세트의 수명에서 가장 먼저 실행되는 이벤트에 해당한다. 데이터세트는 여전히 dsInactive 상태에 있다.
BeforeScroll 커서의 방향이나 위치, 또는 커서 위치조정 메소드가 무엇이든 커서가 다른 레코드로 이동되기 전에 (예: 커서가 새 레코드로 위치될 때) 실행된다. 이 이벤트는 해당 이벤트 핸들러에서 시간이 소요되는 작업을 피해야 하는 경우 종종 트리거될 것이다.
BeforeEdit 'Edit' 메소드에 의해 실행된다. 데이터세트는 아직 dsEdit 상태가 아니다.
BeforeInsert 'Insert' 또는 'Append' 메소드에 의해 실행된다. 데이터세트는 아직 dsInsert 상태가 아니다.
BeforePost 'Post' 메소드에 의해 실행된다. 모든 필요한 필드에 값이 있는지 검사는 주로 이 이벤트가 트리거되기 전에 데이터세트에 의해 실행되며, 예외가 주어질 경우 이벤트가 트리거되지 않는다. 데이터세트는 여전히 dsEdit 또는 dsPost 상태에 있다.
BeforeDelete Delete 메소드에 의해 실행된다.
BeforeCancel Cancel 메소드 이전에 실행된다.
BeforeRefresh Refresh 메소드 이전에 실행된다.
BeforeClose Close 메소드에 의해 실행된다. 데이터세트는 여전히 dsActive 상태에 있다.
표 12.7: Before 이벤트


모든 이벤트에는 해당하는 'After' 이벤트가 있는데, 이는 메소드가 성공적으로 실행된 이후에 실행된다. 메소드의 실행 도중에 예외가 발생할 경우 'After' 이벤트 핸들러는 트리거되지 않을 것이다.


'Before' 또는 'After' 변형이 없는 특수 이벤트도 몇 가지가 있다:

이벤트 설명
OnNewRecord 새 레코드를 할당해야 하는 경우 발생한다. 특정 필드를 초기화하는 데에 사용할 수 있다. BeforeInsert 이후에, 하지만 AfterInsert 이전에 트리거된다.
OnPostError Post 메소드 도중에 오류가 감지될 때 발생한다. 예외 객체의 인스턴스는 이 이벤트 핸들러로 전달되고, post 연산이 다시 시도되도록 설정 가능한 변수도 전달된다. 오류를 수정 시 사용된다.
표 12.8: Before 또는 After variation가 없는 이벤트


지속성 필드 사용하기

데이터세트의 필드들은 내용의 표시를 제어하고, 필드 데이터를 표시하거나 편집할 때 데이터 인식 컨트롤이 행하는 행위를 제어하기 위한 추가 프로퍼티들을 갖고 있다. 일반적인 상황에서는 데이터세트가 활성화되지 않는 한 필드가 존재하지 않으므로 프로퍼티를 설정할 수 없다. 데이터세트를 열 때마다 다른 필드를 포함할 경우, 이것은 일반적이면서 예상되는 행위이다.


데이터세트가 리턴하는 실제 데이터에 대해 필드가 생성된다. 예를 들어, SQL 문을 입력하도록 해주는 폼을 생성하는 경우, DBGrid 내 결과가 되는 데이터세트, 쿼리가 열렸을 때 생성되는 필드의 타입과 번호는 당신이 입력한 SQL 문에 따라 좌우된다.


예를 들어, 데이터베이스 내 Customer 테이블에 'FirstName' 필드가 있다면, Customer 테이블의 모든 레코드는 격자로 표시되고, 해당 테이블에 대한 데이터세트가 열릴 때마다-무엇보다-필드명이 포함된 TField의 인스턴스가 생성된다. 이 데이터를 표시하는 격자는 생성된 필드마다 하나의 열을 생성할 것이다. 열의 제목은 TField 인스턴스의 DisplayLabel 프로퍼티로 설정될 것이다. 고객 이름 필드의 제목은 항상 'FirstName'이 되는데, 필드명으로 이용 가능한 값이 그것밖에 없으므로 DisplayLabel 프로퍼티가 기본 값으로 이 값을 리턴할 것이기 때문이다. 그 외 필드의 내용 표시를 제어하는 모든 TField 프로퍼티들도 기본 값으로 채워진다. 이로 인해 애플리케이션의 사용자는 혼란스러울 것이다.


Customers 테이블로 접근할 때마다 'FirstName' 필드가 리턴될 것을 이미 개발자가 알고 있다면 'FirstName' 필드에 (아니면 개발자가 정의하고자 하는 다른 필드) 관련된 정보에 플레이스 홀더로 된 지속성 필드(persistent field)를 정의할 수 있다. 해당 플레이스 홀더에서는 예를 들어 DisplayLabel 프로퍼티를 'Customer first name'으로 설정할 수 있다. 이후 격자는 이 값을 해당 필드 열의 제목으로 사용할 것이다. 데이터세트가 열려 있고 'FirstName' 필드를 만나면 이는 먼저 동일한 필드명으로 된 지속성 필드를 검색할 것이다. 일치하는 지속성 필드를 발견할 경우 사용할 것이다. 지속성 필드를 발견하지 못한다면 필드를 표현하기 위해 TField 인스턴스가 생성되고 기본 값으로 채워진다.


지속성 필드를 생성한다는 것은 폼 상의 컨트롤이 모두 필드로 선언되듯이 데이터세트 내 모든 필더가 폼 선언 내 필드로 정의됨을 의미한다. 사실 TField 는 TComponent 의 자손이다.


지속성 필드를 생성하면 예정보다 빨리 디스플레이 프로퍼티를 설정하도록 해주며, 이러한 디스플레이 프로퍼티는 나머지 폼과 함께 스트리밍되어(streamed) 런타임 시 폼에서 이용할 수 있을 것이다.


뿐만 아니라 지속성 필드는 자신의 필드로 쉬운 접근을 허용한다. 데이터세트에 ItemCount 필드가 있다고 가정하고, 지속성 필드를 생성한 후 필드 컴포넌트의 이름을 ItemCount로 재명명하면 (기본 이름은 'DatasetNameItemCount'가 될 것이다), 그 내용을 아래와 같이 변경할 수 있을 것이다:

Procedure TMyForm.MyMethod;
begin
  ItemCount.AsInteger := 0;
end;


데이터세트의 데이터 필드는 폼 클래스의 폼 'Field'로서 ('field'가 오브젝트 파스칼 의미로 사용되는) 이용할 수 있다.

그림 12.3: 데이터세트에 대한 컨텍스트 메뉴


그림 12.4: 필드 에디터와 그 컨텍스트 메뉴


그림 12.5: fielddefs 창


지속성 필드는 데이터세트를 오른쪽 마우스로 클릭하여 컨텍스트 메뉴를 표시한 후 (그림 12.3 참고) Edit Fields... 메뉴 항목을 선택하여 생성할 수 있다. 필드 에디터(Fields Editor)가 팝업되고 (그림 12.4 참고), 컨텍스트 메뉴에서 오른쪽 마우스 클릭을 통해 Add fields 메뉴 항목을 선택할 수 있다. 그러면 추가할 수 있는 필드의 리스트가 뜬다 (그림 12.5 참고). [Create] 버튼을 클릭하면 필드가 생성되어 폼으로 추가될 것이다. 이는 오브젝트 인스펙터에서도 선택 가능한데, 여기서는 표 12.9에 실린 프로퍼티들이 표시되고 설정 가능하다.

Fields 프로퍼티 설명
Alignment 필드 내용을 어떻게 표시할 것인지 제어한다. 예: 격자 열 내에 표시.
ConstraintErrorMessage 필드가 CustomConstraint 제약조건을 따르지 않을 때 어떤 메시지를 표시할 것인지 나타낸다.
CustomConstraint SQL 조건, SQL에서 Check Constraint와 유사하다. 일부 데이터세트 자손들은 레코드의 포스팅 전에 해당 제약조건으로 필드의 값을 검사한다. 필드가 제약조건을 충족하지 않을 경우 ConstraintError에 명시된 메시지와 함께 오류가 발생한다.
DefaultExpression 새 레코드가 생성될 때 삽입될 기본 필드 값.
DisplayFormat BCD와 float 필드의 경우, 포맷팅 문자열을 명시할 수 있고, 필드의 값은 명시된 마스크에 따라 포맷될 것이다.
DisplayLabel 해당 필드명을 표시해야 할 때 필드의 설명을 표시한다. 이를 DBGrid 열 헤더 또는 오류 메시지에 기본 값으로 이용할 수 있다.
DisplayWidth 필드가 표시될 때 문자열에서 사용될 너비.
EditFormat 편집 컨트롤이 입력될 수 있는 문자열을 포맷하거나 제약하도록 명시할 수 있는 편집 마스크(edit mask).
ReadOnly 필드를 읽기 전용으로 표시하여 편집할 수 없도록 만들 수 있다.
Required 필드를 필수로 표시하여 데이터를 포스팅하기 전에 사용자는 값을 입력해야 한다. 반대로, 삽입 쿼리가 기본 값을 제공할 경우 데이터베이스를 필요로 하는 필드는 필수가 아닌 값으로 표시할 수 있다.
Visible False로 설정하여 필드가 DBGrid에 표시되지 않도록 한다.
표 12.9: Fields Editor 프로퍼티


위의 프로퍼티 대다수는 필드 값을 사용자에게 표시할 것인지를 제어한다. 이는 GUI 컨트롤 수준으로도 실행할 수 있지만 필드 수준에서 설정 시 필드는 그것이 표시되는 모든 위치에 일관되게 표시될 것이다 (예: DBGrid와 DBEdit 컨트롤에서 일관되게 표시). 하나의 위치에서 필드 포맷을 설정할 경우 연결된 데이터 인식 컨트롤에 모두 동일하게 영향을 미친다.


프로퍼티뿐만 아니라 표 12.10에 열거된 이벤트도 이용할 수 있다.

이벤트 설명
OnChange 필드의 값이 변경된 후에 실행된다. 해당 이벤트는 필드 값이 설정된 방식과 상관없이 발생한다. (사용자 또는 코드를 통해)
OnGetText 해당 이벤트는 필드에 대한 대안적 디스플레이 또는 편집 텍스트를 명시하는 데에 사용할 수 있다.
OnSetText 해당 이벤트는 필드의 값이 설정되었을 때 대안 값을 명시하는 데에 사용할 수 있다.
표 12.10: Fields 이벤트


한 가지 기억해야 할 중요한 점은 지속성 필드가 생성될 때 이 필드들은 데이터 인식 컨트롤에 컴포넌트로서 이용할 수 있는 유일한 필드라는 점이다. 추후 데이터세트가 더 많은 필드를 검색할 경우 (가령 SQL 문에 추가되거나 BDF 파일이 더 많은 필드를 포함하도록 구성된 경우), 이러한 새 필드들은 지속성 필드로서 생성되지 않는 이상 데이터 인식 컨트롤에 이용할 수 없다.


다음 쿼리의 데이터를 리턴하는 데이터세트를 예로 들어보자:

SELECT FIRSTNAME,LASTNAME FROM PERSON;


두 개의 지속성 필드는 (FirstName과 LastName) 생성되어 격자에 표시된다. 이후에 쿼리는 변경되어 BirthDay 필드를 검색할 수 있게 된다:

SELECT FIRSTNAME,LASTNAME, BIRTHDAY FROM PERSON;


BirthDate 필드도 지속성 필드로 생성되지 않는 이상 격자에 표시되지 않을 것이다.


데이터 모듈

TDataset 과 TDatasource 의 인스턴스들은 폼에 바로 드롭할 수 있다. 이는 쉽고 빠르며, 곧 몇 개의 이벤트와 함께 폼이 준비된다. 이는 GUI와 데이터 이벤트 핸들러가 동일한 유닛에서 결합됨을 의미한다. 라자루스는 GUI와 데이터베이스 로직을 구분하는 방법을 제공한다: 데이터모듈이 바로 그것이다. 이것은 애플리케이션에 절대 표시되지 않는 비시각적 창으로 생각할 수 있지만 런타임 시에는 메모리에 유지된다. IDE에서 컴포넌트를 일반 폼에 드롭하듯 데이터모듈 위로 드롭할 수 있다. 이러한 경우 이벤트 핸들러는 폼이 아니라 데이터모듈에서 정의되므로, 모든 데이터 로직은 폼이 아닌 데이터모듈에 작성되어 결국 데이터와 GUI 로직이 분명하게 구분된다. 본래 창이 없는 서비스 애플리케이션들은 프로그램 데이터베이스 로직의 포함을 데이터모듈로 제한한다. 데이터모듈이 생성되면, 데이터 모듈 유닛이 애플리케이션의 uses 절에 추가될 경우 폼 위의 시각적 컨트롤은 데이터모듈 상의 어떤 데이터소스로든 연결될 수 있다:

  • File ▷ New 메뉴 항목으로 새 데이터모듈을 생성한다.
  • 새로 생성된 데이터모듈에 데이터베이스 연결을 생성하고 모든 파라미터를 설정한다.
  • 데이터모듈에 몇 개의 TDataset 자손을 드롭하고, 이를 데이터베이스 인스턴스로 연결한다. 주로 데이터세트를 데이터베이스로 연결하는 프로퍼티를 Database라고 부른다. 마지막으로, 데이터세트의 다양한 프로퍼티를 설정하여 적절한 데이터를 선택하도록 한다: 테이블 이름이나 SQL 문.
  • 폼을 데이터세트로 연결해야 하는 경우 TDatasource 인스턴스 또한 데이터모듈로 위치시켜야 하고, 그 Dataset 프로퍼티를 거쳐 올바른 TDataset 인스턴스로 가리켜야 한다.


위의 단계들을 완료하면 애플리케이션을 데이터베이스로 연결할 수 있다. Database 컴포넌트가 있다면 Connected 프로퍼티를 True로 설정함으로써 연결할 수 있다. 즉, IDE는 실제로 데이터베이스와 연결을 생성하도록 시도할 것을 뜻한다. 오류가 발생할 경우, 데이터베이스 컴포넌트의 프로퍼티가 올바르게 설정되지 않았음을 의미한다 (올바르지 않은 데이터베이스 이름이나 사용자 이름/비밀번호일 가능성이 높다). 기반이 되는 데이터베이스 인터페이스 라이브러리가 존재하지 않을 수도 있다. 데이터베이스 코드는 연결이 되었을 때에만 해당 라이브러리의 로딩을 시도할 것이다. 데이터베이스로의 연결이 구축되면 Active 프로퍼티를 True로 설정하여 데이터세트의 프로퍼티도 검사할 수 있다. 그러면 데이터베이스로부터 데이터의 인출을 시도할 것이다-어떤 데이터일지는 실제 데이터세트와 그 프로퍼티에 따라 좌우된다. 데이터세트의 프로퍼티가 올바르지 않거나 데이터베이스를 이용할 수 없는 경우 오류 메시지가 표시되고 무엇이 잘못되었는지 나타낼 것이다. 테스트 후 Active와 Connected 프로퍼티는 True 값으로 설정된 채 유지될 것이다. 애플리케이션이 실행되고 데이터모듈이 생성되면, 데이터베이스로의 연결이 다시 구축되고, 모든 활성화된 데이터세트에 대한 데이터가 데이터베이스로부터 인출될 것이다.


아마 데이터베이스 위치는 설치마다 달라질 것이므로 특정 런타임 구성이 필요할 것이다. 이러한 경우, 데이터모듈 정의를 저장하기 전에 Active와 Connected 프로퍼티를 False로 설정해야 한다. 이는 필요한 구성 단계를 적절한 이벤트 핸들러에서 런타임 시 실행하고 난 후 이를 True로 설정할 수 있다.


그림 12.6은 IDE에서 몇 가지 데이터 컴포넌트와 함께 데이터모듈을 보여준다.

그림 12.6: 설계모드의 데이터모듈 모습


데이터모듈이 생성되고 나면 TDBGrid를 이용해 하나 또는 이상의 폼을 생성할 수 있다. DataSource 프로퍼티를 데이터모듈 상에서 데이터소스 인스턴스로 설정할 수도 있다 (관련 내용은 12.2.3절을 참고). 매우 간단한 애플리케이션에서는 하나의 데이터세트만 이용하고 데이터베이스 컴포넌트 없이 메인 폼에서 간단히 실행할 수 있다: 이런 경우 TDatamodule을 사용하게 되면 불필요한 복잡성을 야기할지도 모른다.


데이터 인식 시각적 컨트롤

GUI에서 데이터를 표시하기 위해 라자루스는 TDatasource 인스턴스를 통해 통신하는 표준 GUI 컨트롤의 자손들, 즉 데이터를 인식하는 컨트롤 집합을 구현하였다. TDatasource 컴포넌트는 컴포넌트 팔레트의 Data Access 페이지에서 찾을 수 있으며, 데이터 인식 컨트롤은 그림 12.7과 같이 컴포넌트 팔레트의 Data Controls 페이지에서 찾을 수 있다.

그림 12.7: 데이터 인식 표준 컨트롤


데이터 인식 컨트롤은 주로 두 개의 프로퍼티를 가진다:

프로퍼티 설명
DataSource 데이터와 이벤트 통지(event notification)를 수락해야 하는 TDatasource 인스턴스.
DataField 컨트롤이 관심을 갖는 필드의 이름. 컨트롤은 현재 레코드에 대해 이 필드의 값만 표시하고 조작할 것이다. 일부 컨트롤은-특히 DBGrid와 DBNavigator 컨트롤-단일 필드가 아니라 데이터베이스에 전체로서 실행되기 때문에 DataField 프로퍼티가 없다.
표 12.11: 데이터 인식 컨트롤의 프로퍼티


데이터세트의 데이터를 표시하고 조작하기 위해서는 아래 단계를 순서대로 따라야 한다:

  • 폼에 하나 또는 이상의 TDatasource 컴포넌트를 드롭하고, 이를 TDataset 자손으로 연결한다.
  • 데이터 필드의 편집에 필요한 데이터 인식 컨트롤을 가능한 한 많이 폼에 드롭한 후 TDatasource 컴포넌트로 연결하고, 그에 따라 각 DataField 프로퍼티를 설정한다.


모두 올바로 설정되고 나면 TDataset 인스턴스의 Active 프로퍼티를 True로 설정 시 라자루스 IDE에서 설계되는 동안 폼에 데이터가 실제로 표시될 것이다. 이러한 액션 대부분은 코드를 단 한 줄도 쓰지 않고 실행할 수 있다.


라자루스 IDE에는 아래의 데이터 인식 컨트롤을 기본 값으로 이용할 수 있다:

컨트롤 설명
TDBLabel 어떤 필드의 텍스트든 표시하는 간단한 라벨 컨트롤.
TDBEdit 단일 필드의 내용을 편집하기 위한 간단한 편집 컨트롤.
TDBMemo 단일 필드의 내용을 다중행 텍스트로서 편집하기 위한 간단한 메모 컨트롤.
TDBImage 이미지 표시 컨트롤. BLOB 필드로부터 로딩하는 이미지라면 무엇이든 표시한다.
TDBListBox 리스트상자의 항목 리스트에서 필드 값을 선택하도록 해주는 선택 컨트롤.
TDBComboBox 콤보상자 리스트의 항목에서 필드 값을 선택하도록 해주는 선택 컨트롤.
TDBCheckBox 부울(boolean) 필드를 표시하고 설정하도록 해주는 체크상자. 값으로는 체크 표시와 체크해제 상태를 명시할 수 있다.
TDBRadioGroup 정수 필드의 내용을 매핑으로 표시하도록 해주는 라디오그룹 컨트롤. 기반이 되는 정수 값은 itemindex(항목색인)이 필드의 값과 일치하는 명명된(매핑된) 항목을 검사함으로써 라디오그룹에 표현된다.
TDBCalendar TDateTime 필드의 값을 표시하도록 설계된 달력 컨트롤.
TDBNavigator 일련의 버튼을 포함하는 특수화된 컨트롤. 각 버튼은 TDataset 검색 메소드 또는 그것의 데이터 조작 메소드와 일치한다. 적절한 버튼을 클릭하면 데이터세트에 해당하는 액션이 실행될 것이다. 컨트롤은 데이터세트의 상태를 추적하면서 데이터세트의 현재 상태에서 사용될 수 없는 버튼을 비활성화한다.
TDBGrid 데이터세트의 내용을 격자에 표시한다. 현재 레코드뿐만 아니라 격자에 일치하는 모든 레코드가 표시될 것이다. 격자 데이터는 편집이 가능하다ㅡ셀을 클릭하면 해당 셀의 내용을 편집할 수 있다.
표 12.12: 라자루스 IDE에서 이용 가능한 데이터 인식 컨트롤


데이터 비인식 조상과 비교 시 위의 컨트롤 중 대부분은 Datasource와 DataField 프로퍼티를 제외한 추가 프로퍼티가 없다.