Skip to content

트랜잭션 베이스 클래스

Fox Transactions 는 트랜잭션 지원을 위한 다양한 기능을 제공합니다. 이 기능들을 사용하기 위해서는 FoxComponentBase 클래스에서 파생되어야 합니다. 또한 FoxBizBase 클래스와 FoxDacBase 클래스는 트랜잭션을 위한 추가적인 기능을 제공합니다.

Note

FoxComponentBase 클래스는 Fox Transactions 을 사용하기 위한 최상위 베이스 클래스입니다. 이는 .net framework 의 ContextBoundObject 가 제공하는 기능을 사용하기 위해 요구되었던 베이스 클래스 입니다. .net 5 이상 버전에서는 더 이상 ContextBoundObject 를 사용하지 않기 때문에 반드시 FoxComponentBase 클래스 대신 새로운 인터페이스 타입을 도입할 수도 있습니다만, 하위 호환성과 전통 때문에 여전히 FoxComponentBase 클래스에서 파생되기를 요구합니다.

이 문서와 관련된 예제 코드는 다음을 참조 하십시요.

FoxComponentBase 추상 베이스 클래스

FoxComponentBase 클래스는 Fox Transactions 기능을 사용하기 위해 클래스가 반드시 상속(inherit)해야할 추상 베이스 클래스 입니다. Fox Transactions 이 요구하는 수행 프록시 생성이나 전처리/후처리가 정상적으로 수행되기 위해서는 FoxComponentBase 에서 파생된 클래스가 필요합니다. FoxComponentBase 클래스의 선언은 다음과 같습니다.

1
2
3
4
5
[FoxAutoComplete(true)]
public abstract class FoxComponentBase : IDisposable
{
    ......
}

FoxAutoCompleteAttribute 가 명시되어 있음에 주목하십시요. FoxAutoCompleteAttribute 특성은 트랜잭션 메서드들이 자동 트랜잭션을 사용함을 의미합니다. 그리고 이 특성은 파생 클래스에 상속되기 때문에 명시적으로 FoxAutoComplete(false) 특성을 명시적을 사용하지 않는 한 모든 메서드는 자동 트랜잭션이 적용됩니다.

Activate/Deactivate 메서드

Fox Transactions 은 전처리/후처리를 통해 트랜잭션과 같은 다양한 기능을 제공합니다. 전처리에 의해 처리되는 과정을 활성화(activation)라고 부르며 후처리에 의해 처리되는 과정을 비활성화(deactivation)이라고 부릅니다. Fox Transactions 에 의해 활성화가 시작됨을 알리는 콜백 메서드가 Activate 메서드이며 비활성화가 시작됨을 알리는 콜백 메서드가 Deactivate 메서드입니다.

Info

활성화/비활성화 이름과 Activate/Deactivate 메서드 이름의 기원은 COM+ 입니다. COM+ 는 객체를 호출하기 전에 활성화하는 과정을 반드시 요구했으며 JIT(Just In Time) 활성화와 같은 기능도 제공했었습니다. Fox Transactions 은 호환성과 전통적인 이유에서 여전히 이러한 용어와 메서드 이름을 사용할 뿐입니다.

FoxComponentBase 메서드는 이들 두 메서드를 protected virtual 메서드로 제공하며 파생 클래스는 이 메서드를 오버라이드 하여 메서드 호출 전에 전처리나 메서드 호출 후에 후처리를 수행할 수 있습니다. 다음 예제 코드는 Activate 메서드와 Deactivate 메서드를 오버라이드하여 메시지를 콘솔에 출력하며 수행 문맥 객체(FoxExecutionContext)를 사용하여 호출 대상 메서드 이름, 예외 발생 여부를 확인하는 예를 보여 줍니다.

protected override void Activate()
{
    base.Activate();
    FoxExecutionContext ctx = this.Context;
    Console.WriteLine($">> {ctx.MethodName} activated...");
}

protected override void Deactivate()
{
    Console.WriteLine($">> {this.Context.MethodName} deactivated...");
    if (this.Context.Exception != null)
    {
        Console.WriteLine($">>  method failed with exception: {this.Context.Exception.Message}");
    }
    base.Deactivate();
}

Activate/Deactivate 메서드를 오버라이드 할 때 주의할 점은 베이스 클래스의 Activate/Deactivate 메서드를 호출해야 한다는 점입니다. 베이스 클래스 메서드를 호출함으로써 베이스 클래스가 활성화/비활성화 시 자동으로 수행하는 작업이 계속될 수 있도록 해야 합니다.

Note

FoxComponentBase 클래스의 Activate/Deactivate 메서드는 아무런 작업도 수행하지 않습니다. 하지만 FoxDacBase 클래스와 같은 일부 베이스 클래스들은 이들 메서드를 통해 데이베이스 연결을 자동으로 열거나 닫는 작업을 수행합니다.

성능 측정

FoxComponentBase 클래스에서 파생된 클래스의 메서드들은 수행 프록시를 통해 호출되는 경우 자동으로 성능 측정이 수행됩니다. 메서드가 호출되기 직전 전처리 과정에서 성능 활동(Performance Activity) 혹은 성능 문맥(Performance Context)이라 부르는 NeoDEEX 의 성능 측정 기능을 사용하여 성능 측정을 시작하고 메서드 호출이 종료된 직후 후저리 과정에서 성능 측정을 종료하고 메서드를 수행하는데 소요된 시간을 기록합니다.

Info

성능 측정으로 인한 오버헤드를 최소화하기 위해 스톱워치(FoxStopWatch) 기반의 단순한 시간 측정 기법이 사용됩니다. 간단한 성능 측정을 통해 개략적인 성능 병목이 관측된다면 고급 프로파일링 도구를 사용하여 성능 저하의 원인을 심도 있는 분석을 수행할 수 있습니다.

측정된 성능 정보는 간단하게 특정 이름을 가진 로거를 구성하여 로그에 기록할 수 있습니다. 로거 이름은 NeoDEEX.Transactions.Performance 이며 이 이름을 가진 로거가 존재하는 경우 FoxComponentBase 클래스의 후처리 모듈은 수집된 성능 정보를 기록하게 됩니다. 예를 들어 다음과 같이 이름이 NeoDEEX.Transactions.Performance인 콘솔 로거를 구성하고,

{
  "$schema": "https://neodeex.github.io/doc/neodeex.config.schema.json",
  "logging": {
    "filter": "Verbose",
    "loggers": {
      "NeoDEEX.Transactions.Performance": {
        "providerType": "NeoDEEX.Diagnostics.Loggers.FoxConsoleLoggerProvider"
      }
    }
  }
}

다음과 같이 FoxComponentBase 에서 파생된 TxComp 클래스의 TxMethod 메서드를 호출하면,

public class TxComp : FoxComponentBase, ITxComp
{
    ......

    [FoxTransaction(FoxTransactionOption.Required)]
    public void TxMethod()
    {
        Console.WriteLine("[white]TxComp.TxMethod invoked...[/]");
    }

}

성능 정보가 콘솔에 다음과 같이 출력됩니다. 이 로그에서 TxMethod 메서드를 호출하는데 약 0.82 msec 가 소요됨을 알 수 있습니다.

1
2
3
TxComp.TxMethod invoked...
I 2026-02-06 21:10:01.66933 [NeoDEEX.Transactions.Performance] Performance Activity [NEOWORK2:23804:component_base_demo.TxComp::TxMethod;Generic] elapsed=0.82 time=21:10:01:6684 id=49f06a24-ef78-47b6-b9cd-cc4136c64641
    component_base_demo.TxComp::TxMethod inclusive=0.82 exclusive=0.82 time=21:10:01:6684 id=2 parent=-1

성능 측정 결과를 로그에 기록하지 않고 데이터로서 수집하거나 사용하고자 한다면 성능 활동 혹은 성능 문맥을 먼저 구동한 후에 FoxComponentBase 클래스의 메서드를 호출하면 됩니다. 대표적으로 Fox Biz Service 가 이와 같은 작업을 수행합니다. 다음은 코드를 통해 수동으로 성능 활동 객체(FoxPerformanceActivity)를 생성하고 성능 측정을 시작한 후에 TxComp 클래스의 TxMethod 메서드를 호출하는 예를 보여 줍니다.

1
2
3
4
5
6
7
8
using TxComp comp = new();
ITxComp itf = comp.CreateExecution<ITxComp>();
FoxPerformanceActivity activity = new("MyTrace");
using (activity.Enter())
{
    itf.TxMethod();
}
Console.WriteLine(activity.ActivityInfo.ToString());

이 경우 FoxPerformanceActivity 클래스의 ActivityInfo 속성을 사용하여 측정된 성능 정보를 활용할 수 있습니다. NeoDEEX 의 성능 측정 기능에 대한 상세한 내용은 성능 측정 문서를 참고 하십시요.

사용자 정보

FoxComponentBase 클래스는 호출자가 사용자 정보(인증 정보)를 포함하는 경우이 이 정보를 메서드 내에서 사용할 수 있도록 UserId, UserInfo 속성을 제공합니다.

전형적인 NeoDEEX 기반 앱은 웹 앱이나 데스크톱 앱에서 사용자 인증을 수행하고 인증된 사용자 정보를 FoxUserInfoContext 객체에 기록합니다. 이 사용자 정보는 Fox Web API 호출 시 서비스에 전달되며 서비스는 수신된 FoxUserInfoContext 객체가 서버 측에서 사용될 수 있도록 SetCallContext 메서드를 호출합니다.

1
2
3
4
5
6
7
// Web API 는 다양한 인증 방식을 통해 클라이언트가 전송한 FoxUserInfoContext 객체를 수신함.
FoxUserInfoContext userInfo = AuthenticateUser(request);
// 유효한 인증된 사용자임을 확인하고 SetCallContext 메서드를 호출
if (userInfo != null && ValidateUser(userInfo))
{
    userInfo.SetCallContext();
}

이렇게 설정된 사용자 정보는 FoxComponentBase 클래스의 UserInfo 속성을 통해 접근할 수 있습니다. UserId 속성은 UserInfo.UserId 속성에 대한 숏컷 속성입니다.

public void TxMethod()
{
    if (this.UserInfo != null)
    {
        Console.WriteLine($">>  authenticated user id: {this.UserId}");
    }
    else
    {
        Console.WriteLine(">>  anonnymous user");
    }
    ......
}

사용자 정보는 특히 데이터액세스 메서드에서 유용할 때가 많습니다. 데이터를 갱신할 때 최종 갱신을 요청한 사용자 아이디를 기록하는 컬럼이 존재하는 경우가 많은데, 이 때 이 사용자 정보를 사용하면 굳이 매개변수를 사용하여 사용자 아이디를 전달할 필요가 없기 때문입니다.

public void UpdateProduct(Product product)
{
    string query = "UPDATE Product SET ProductName = @name, ... , ModifiedBy = @uid, ModifiedAt = sysdatetime() WHERE ProductId = @Id";
    var parameters = this.DbAccess.CreateParmCollection();
    parameters["name"] = product.ProductName;
    ......
    parameters["uid"] = this.UserId;
    parameters["Id"] = product.Id;
    this.DbAccess.ExecuteSqlNonQuery(query, parameters);
}

예외 처리

Fox Transactions 에서 트랜잭션을 처리하는 권장되는 방법은 예외 발생 여부에 따라 트랜잭션을 커밋하거나 롤백하는 것입니다. 따라서 예외 발생시 트랜잭션이 롤백되도록 try~catch 문장을 사용하지 않는 것이 좋습니다. 하지만 오류 로그를 남겨야 하는 등의 예외 처리가 필요한 경우, OnError 메서드를 사용할 수 있습니다.

메서드가 예외와 함께 종료되면 OnError 메서드가 호출되며 이 메서드의 매개변수로 전달되는 예외 객체를 사용하여 로깅과 같은 필요한 작업을 수행할 수 있습니다.

1
2
3
4
5
protected override void OnError(Exception ex)
{
    IFoxLog log = FoxLogManager.GetLogger(this.GetType());
    log.Error(ex);
}

어떤 메서드에서 예외가 발생하건 OnError 메서드가 호출되기 때문에 어떤 메서드 호출도중 예외가 발생했는가를 알아내기 위해서는 수행 문맥(execution context)을 나타내는 Context 속성을 사용해야 할 수도 있습니다.

OnError 메서드를 사용할 때는 주의해야 할 점이 있습니다. 예를 들어 ASP.NET 코드가 Fox Transactions 의 비즈니스 로직 클래스의 메서드를 호출하고 이 메서드가 다시 DAC 클래스의 메서드를 호출하는 경우를 생각해 봅시다. 만약 비즈니스 로직 클래스와 DAC 클래스 모두 OnError 메서드에서 예외를 로깅하는 경우 DAC 클래스에서 발생한 예외는 로그에 2회 기록될 수 있습니다. 예외가 DAC 클래스 메서드를 호출한 비즈니스 로직 클래스 메서드로 전파되기 때문입니다.

HandleChildException 속성은 호출한 수행 문맥에서 발생한 예외를 OnError 에서 처리할지를 나타내는 속성입니다. 기본값은 true 이며, 이 속성을 false 로 설정하면 현재 수행 문맥에서 발생한 예외에 대해서만 OnError 메서드를 호출합니다.

1
2
3
4
5
6
7
8
public class BizClass : FoxBizBase, IBizClass
{
    public BizClass()
    {
        this.HandleChildException = false;
    }
    ......
}

Warning

로깅과 같은 예외 처리는 OnError 메서드를 사용하는 것 보다는 Fox Transactions 를 호출하는 계층, 즉 ASP.NET 계층에서 중앙집중적으로 처리하는 것이 권장됩니다. 예를 들어 ASP.NET 은 예외 필터와 같은 기능을 사용하여 중앙 집중적인 예외 처리가 가능합니다.

Dynamic Invoke

트랜잭션 처리와 같은 Fox Transactions 가 제공하는 기능을 사용하기 위해서는 수행 프록시를 통해 FoxComponentBase 클래스에서 파생된 클래스의 메서드를 호출해야 합니다. 수행 프록시는 전처리/후처리를 수행하여 Fox Transactions의 기능을 제공합니다. 수행 프록시를 생성하기 위해 FoxComponentBase 클래스는 CreateExecution<T> 메서드를 제공하며 이 메서드는 수행 프록시에 적용할 인터페이스를 요구합니다.

하지만 항상 수행 프록시와 인터페이스를 사용해야만 하는 것은 아닙니다. FoxComponentBase 클래스는 Invoke 메서드를 제공하는데 이 메서드는 내부적으로 수행 프록시 없이 전처리/후처리를 수행하여 Fox Transactions 의 기능을 제공할 수 있습니다. Invoke 메서드는 리플렉션과 비슷하게 메서드 이름과 매개변수들을 사용하여 메서드 호출을 수행합니다. 따라서 다음과 같이 CreateExecution<T> 메서드를 사용하지 않고 호출할 수 있습니다.

using TxComp comp = new();
comp.Invoke("TxMethod");

수행 프록시와 인터페이스를 요구하지 않는 반면 리플렉션과 비슷하게 인터페이스의 강력한 타입 체크를 사용할 수 없습니다. 위 코드에서 메서드 이름에 오타가 있거나 매개변수 타입이 맞지 않더라도 컴파일 타임 오류를 발생하지 않고 런타임에서나 예외가 발생하게 됩니다. 따라서 Invoke 메서드를 사용하는 것보다 수행 프록시와 인터페이스를 적용할 것을 권장합니다.

FoxDacBase 추상 베이스 클래스

FoxDacBase 클래스는 데이터 액세스 컴포넌트(DAC; Data Access Component)를 위한 베이스 클래스 입니다. FoxDacBase 클래스는 일반적인 DAC 클래스들이 사용하는 Supported 트랜잭션 옵션이 적용되어 있으며 트랜잭션 컨트롤러 종류 값으로 RootContext 이 명시되어 있습니다. 따라서 FoxDacBase 에서 파생된 클래스는 Supported 옵션이 기본적으로 사용되며 트랜잭션 루트와 동일한 트랜잭션 컨트롤러를 사용하게 됩니다.

1
2
3
4
5
6
[FoxTransaction(FoxTransactionOption.Supported)]
[FoxTransactionController(FoxTransactionControllerKind.RootContext)]
public abstract class FoxDacBase : FoxComponentBase
{
    ......
}

DbAccess 속성

FoxDacBase 클래스는 DbAccess 속성을 통해 데이터베이스 액세스를 좀 더 편리하게 해줍니다. DbAccess 속성은 메서드 호출 전에 FoxDbAccess 인스턴스로 채워지므로 객체 생성에 신경쓸 필요 없이 그냥 사용(just-use)하면 됩니다.

public class DacClass : FoxDacBase, IDacClass
{
    ......
    public int InsertMemo(Memo memo)
    {
        object? result = this.DbAccess.ExecuteQueryScalar("memo.insert", memo)
            ?? throw new InvalidOperationException("InsertMemo failed: result is null.");
        int newId = Convert.ToInt32(result);
        return newId;
    }
}

기본적으로 DbAccess 속성은 트랜잭션 메서드 호출 전에 CreateDbInstance 가상 메서드가 반환하는 값으로 초기화 됩니다. 그리고 CreateDbInstance 가상 메서드는 디폴트 연결 문자열을 사용하여 FoxDbAccess 인스턴스를 생성하도록 기본 구현되어 있습니다.

1
2
3
4
protected virtual FoxDbAccess CreateDbInstance()
{
    return FoxDbAccess.CreateDbAccess();
}

특정 이름의 연결 문자열을 사용해야 하거나 동적으로 연결 문자열을 바꾸어야 한다면 CreateDbInstance 가상 메서드를 오버라이드할 수 있습니다. 다음 코드는 디폴트 연결 문자열이 아닌 경우 생성자를 통해 연결 문자열 이름을 전달 받아 사용하는 예를 보여 줍니다.

public class DacClass(string? connName = null) : FoxDacBase, IDacClass
{
    public string? ConnectionStringName { get; set; } = connName;

    protected override FoxDbAccess CreateDbInstance()
    {
        return FoxDbAccess.CreateDbAccess(ConnectionStringName);
    }
    ......
}

DbAccess 해제(dispose)

CreateDbInstance 메서드에 생성된 FoxDbAccess 인스턴스는 메서드 내에서 DbAccess 속성을 통해 사용되고 메서드 종료 이후에는 반드시 Dispose 됩니다. 이는 FoxDacBase 에서 파생된 DAC 클래스 메서드에서 데이터베이스 연결과 관련되어 try~finally 문장을 사용할 필요가 없음을 의미합니다. 예를 들어, 2회의 데이터베이스 액세스를 위해 Open 메서드를 호출하는 다음 코드를 생각해 봅시다.

1
2
3
4
5
6
7
public void InsertTwoMemo(Memo memo1, Memo memo2)
{
    this.DbAccess.Open();
    dbAccess.ExecuteQueryNonQuery("memo.insert", memo1);
    dbAccess.ExecuteQueryNonQuery("memo.insert", memo2);
    this.DbAccess.Close();
}

첫번째 데이터 액세스에서 오류가 발생하는 경우를 대비하여 try~finally 문장을 사용하여 Close 메서드를 사용하는 것이 일반적입니다. 하지만 FoxDacBase 에서 파생된 DAC 클래스의 메서드들은 메서드 종료 후 DbAccess 속성은 Dispose 될 것이므로 try~finally 문장을 사용할 필요가 없습니다.

로컬 트랜잭션 지원

FoxDacBase 클래스는 로컬 트랜잭션 컨트롤러를 위한 IFoxLocalTransactionObject 인터페이스를 구현합니다. 따라서 FoxDacBase 클래스에서 파생된 DAC 클래스는 로컬 트랜잭션 컨트롤러와 함께 트랜잭션 처리가 가능합니다. 다음 코드는 로컬 트랜잭션 컨트롤러를 사용하는 비즈니스 로직 클래스와 DAC 클래스 예를 보여 줍니다.

[FoxTransactionController(FoxTransactionControllerKind.LocalTransaction)]
public class BizClass : FoxBizBase, IBizClass
{
    public List<int> InsertMany(IEnumerable<Memo> memos)
    {
        using DacClass dac = new();
        IDacClass itf = dac.CreateExecution<IDacClass>();
        List<int> newIds = [];
        foreach (Memo memo in memos)
        {
            int id = itf.InsertMemo(memo);
            newIds.Add(id);
        }
        return newIds;
    }
}

public class DacClass : FoxDacBase, IDacClass
{
    public int InsertMemo(Memo memo)
    {
        ......
    }
}

DacClass 클래스는 굳이 FoxTransactionControllerAttribute 특성을 명시할 필요가 없습니다. 베이스 클래스인 FoxDacBase 클래스에서 트랜잭션 루트의 컨트롤러를 사용하도록 FoxTransactionControllerKind.RootContext 값이 사용되고 FoxTransactionControllerAttribute 특성은 상속되기 때문입니다.

1
2
3
4
5
[FoxTransactionController(FoxTransactionControllerKind.RootContext)]
public abstract class FoxDacBase : FoxComponentBase
{
    ......
}

FoxBizBase 추상 베이스 클래스

FoxBizBase 클래스는 비즈니스 로직 클래스를 위한 베이스 클래스 입니다. FoxBizBase 클래스는 일반적인 비즈니스 로직 클래스들이 사용하는 Required 트랜잭션 옵션이 적용되어 있습니다. 트랜잭션 컨트롤러는 별도로 명시되어 있지 않기 때문에 디폴트 트랜잭션 컨트롤러인 패스트 트랜잭션(FastTranssaction) 컨트롤러가 사용됩니다.

1
2
3
4
5
[FoxTransaction(FoxTransactionOption.Required)]
public abstract class FoxBizBase : FoxComponentBase
{
    ......
}

FoxDacBase 와 달리 FoxBizBase 클래스는 비즈니스 로직을 위한 특별한 기능을 제공하지 않습니다만 어플리케이션에서 트랜잭션을 제어하는 계층이 비즈니스 로직이므로 FoxBizBase 에서 파생된 비즈니즈 로직 클래스를 사용하는 것을 권장합니다.

베이스 클래스 권장 코딩 패턴

Fox Transactions 기능을 사용할 때 비즈니스 로직 클래스나 DAC 클래스를 작성할 때 FoxComponentBase 클래스에서 직접 파생하는 것은 매우 좋지 않습니다. DAC 클래스는 FoxDacBase 클래스에서 파생하고 비즈니스 로직 클래스는 FoxBizBase 클래스에서 파생하는 것이 권장됩니다.

앱 규모가 큰 경우 DAC 클래스들을 위한 베이스 클래스와 비즈니스 로직 클래스들을 위한 베이스 클래스를 각각 정의하고 이들이 FoxDacBase, FoxBizBase 에서 파생하는 것이 권장됩니다.

// 앱 수준의 비즈니스 로직 베이스 클래스
[FoxTransaction(FoxTransactionOption.Required)]
public class MyBizBase : FoxBizBase
{
}

// 앱 수준의 DAC 베이스 클래스
[FoxTransaction(FoxTransactionOption.Supported)]
public class MyDacBase : FoxDacBase
{
}

// 개별 비즈니스 로직 클래스는 앱 수준의 베이스 클래스에서 파생
public class BizClass : MyBizBase, IBizClass
{
    ......
}

// 개별 DAC 클래스는 앱 수준의 베이스 클래스에서 파생
public class DacClass : MyDacBase, IDacClass
{
    ......
}

처음에는 이들 앱 수준의 베이스 클래스들(MyBizBase, MyDacBase 클래스)이 아무런 기능이 없더라도 상관없습니다. 이들 베이스 클래스는 추후에 발생할지도 모르는 전역적인 변경 사항을 적용할 수 있기 때문입니다. 예를 들어, 디폴트 컨트롤러 대신 로컬 트랜잭션 컨트롤러를 사용해야 한다면 모든 비즈니스 로직 클래스에 FoxTransactionControllerAttribute 특성울 추가할 필요 없이 앱 수준의 베이스 클래스에만 FoxTransactionControllerAttribute 특성을 추가하면 됩니다.

1
2
3
4
5
[FoxTransactionController(FoxTransactionControllerKind.LocalTransaction)]
public class MyBizBase : FoxBizBase
{
    ......
}

Summary

Fox Transactions의 트랜잭션 기능을 활용하기 위해서는 모든 컴포넌트가 FoxComponentBase 추상 클래스에서 파생되어야 합니다. 이 베이스 클래스는 전처리/후처리(Activate/Deactivate), 성능 측정, 사용자 정보 전달, 예외 처리 등 트랜잭션 실행에 필요한 핵심 인프라를 제공합니다.

데이터 액세스 계층은 FoxDacBase, 비즈니스 로직 계층은 FoxBizBase를 상속하는 것이 권장되며, 이를 통해 각 계층에 적합한 기본 트랜잭션 옵션과 실행 컨텍스트가 자동으로 적용됩니다. 특히 FoxDacBase 클래스는 DbAccess 속성을 통해 데이터베이스 연결 준비 및 해제 자동화, 로컬 트랜잭션 지원 등 DAC에 특화된 기능을 제공합니다.

또한, 앱 규모가 커질수록 FoxDacBase/FoxBizBase 위에 애플리케이션 전용 베이스 클래스를 두어 전역 정책 변경(예: 트랜잭션 컨트롤러 교체)을 일괄적으로 적용할 수 있는 구조가 바람직합니다.