> For the complete documentation index, see [llms.txt](https://docs.axuslab.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.axuslab.com/3.-application-layer/entity_history.md).

# 엔티티 기록 - Entity History

Entity History

#### 설명

Axs는 모든 엔티티 및 속성 변경 사항을 자동으로 기록하는 기능을 제공합니다.

엔터티 변경을 위해 저장된 필드는 다음과 같습니다. 관련 **테넌트 ID**, **엔터티 변경 집합 ID**, **엔터티 ID**, **엔터티 유형 이름**, **변경 시간** 및 **변경 유형**.

엔터티 기록 추적 시스템은 [**IAxsSession**](https://github.com/axuslab/docs.axs/blob/main/Developments/Session/README.md)을 사용하여 현재 UserId 및 TenantId를 가져옵니다.

기본적으로 엔티티는 자동으로 추적되지 않습니다. 시작 구성을 사용하거나 속성을 통해 엔티티를 구성 해야 합니다.

### IEntityHistoryStore

엔티티 히스토리 추적 시스템은 **IEntityHistoryStore**를 사용하여 변경 정보를 저장합니다.

### 환경 구성

엔터티 기록을 구성하려면 모듈([module](https://github.com/axuslab/docs.axs/blob/main/Developments/Module_System/README.md))의 PreInitialize 메서드에서 **Configuration.EntityHistory** 속성을 사용할 수 있습니다. 엔티티 히스토리는 **기본적으로 활성화**됩니다.

```csharp
public class MyModule : AxsModule
{
    public override void PreInitialize()
    {
        Configuration.EntityHistory.IsEnabled = AppConsts.EntityHistoryEnabled;
    }

    //...
}
```

엔터티 기록 구성 속성은 다음과 같습니다 :

* **IsEnabled**: 추적 시스템을 완전히 활성화 / 비활성화하는 데 사용됩니다. 기본값 : **true**.
* **IsEnabledForAnonymousUsers**: true로 설정하면 응용 프로그램에 로그인하지 않은 사용자에 대한 변경 로그가 저장됩니다. 기본값 : **false**.
* **Selectors**: 변경 로그를 저장할 엔티티를 선택하는데 사용됩니다.
* **IgnoredTypes**: 변경 로그를 저장할 때 엔티티를 건너 뛰는 데 사용됩니다.

**Selectors**는 변경 로그를 저장할 엔티티를 선택하기 다른 유형의 클래스를 선택하기 위한 기능 입니다.

```csharp
Configuration.EntityHistory.Selectors.Add(
    new NamedTypeSelector(
        "Axs.FullAuditedEntities",
        type => typeof (IFullAudited).IsAssignableFrom(type)
    )
);
```

모듈의 PreInitialize 메서드에 선택기를 추가 할 수 있습니다.

**IgnoredTypes**는 변경 로그를 저장할 때 무시할 엔티티 유형 목록입니다. 예를 들어 **ISetting**을 구현하는 모든 엔터티를 엔터티 기록 시스템에서 추적하도록 구성했습니다. **ISetting**을 구현하는 **SecretSetting**의 엔티티 히스토리를 건너 뛰기 위해 아래와 같이 정의 할 수 있습니다 :

```csharp
Configuration.EntityHistory.IgnoredTypes.AddIfNotContains(typeof(SecretSetting));
```

모듈의 PreInitialize 메서드에 무시 된 유형을 추가 할 수 있습니다.

#### 어트리뷰트를 이용한 활성/비 활성화

구성별로 추적 된 엔터티를 선택할 수 있지만 단일 **엔터티** 또는 개별 **속성**에 대해 **Audited** 및 **DisableAuditing** 특성을 사용할 수 있습니다. 예:

```csharp
[Audited]
public class MyEntity : Entity
{
    public string MyProperty1 { get; set; }

    [DisableAuditing]
    public int MyProperty2 { get; set; }

    public long MyProperty3 { get; set; }
}
```

MyProperty2는 명시 적으로 비활성화되어 있으므로 제외하고 MyEntity의 모든 속성이 추적됩니다. Audited 속성은 원하는 속성에 대한 변경 로그를 저장하는 데 사용할 수 있습니다.

**DisableAuditing**은 엔터티 또는 엔터티의 단일 속성에 사용할 수 있습니다. 따라서 예를 들어 비밀번호와 같은 변경 로그에서 **민감한 데이터를 숨길 수 있습니다**.

#### Reason Property

엔터티 변경 집합에는 변경 집합이 발생한 **이유**, 즉 이러한 변경을 초래 한 사용 사례를 이해하는 데 사용할 수있는 Reason 속성이 있습니다.

예를 들어, 개인 A가 계정 A에서 계정 B로 돈을 이체합니다. 두 계정 잔액이 모두 변경되고 "송금"이 변경 세트의 이유로 기록됩니다. 잔액 변경은 다른 이유로 인해 발생할 수 있으므로 Reason 속성은 이러한 변경이 이루어진 이유를 설명합니다.

#### UseCase 어트리뷰트

**UseCase** 어트리뷰트를 사용하여 이유에 대해서 핸들링 할 수 있다.

```csharp
[UseCase(Description = "사용자 등록")]
public virtual async Task CreateUserAsync(CreateUserDto input)
{
    // ...

    await _unitOfWorkManager.Current.SaveChangesAsync();
}
```

#### UseCase 어트리뷰트 제한 사항

* `public virtual` 메서드로 정의
* 메서드 내에서 `await _unitOfWorkManager.Current.SaveChangesAsync()` 호출

#### 확인

해당하는 프로젝트의 모듈에서 세팅이 되어야 합니다.

**Entity History를 활성화 하기**

각 모듈에서 아래와 같은 코드로 정의를 해야 Entity History가 정상적으로 동작을 합니다.

```csharp
public override void PreInitialize()
{
    // Entity history 사용여부
    Configuration.EntityHistory.IsEnabled = true;
    // 로그인을 하지 안았어도 Entity history 사용여부
    Configuration.EntityHistory.IsEnabledForAnonymousUsers = true;
    // Entity History 대상이 되는 클래스 선언
    Configuration.EntityHistory.Selectors.Add("SharedEntities1", typeof(Sample), typeof(SampleProduct), typeof(SampleProductAddInfo), typeof(SampleProductDetail));

    // 또는 아래 코드와 같이 인터페이스 기준으로 선택할 수 있음
    //Configuration.EntityHistory.Selectors.Add(
    //    new NamedTypeSelector(
    //        "SharedEntities2",
    //        type => typeof (IFullAudited).IsAssignableFrom(type)
    //    )
    //);
}
```

**코드에서 확인**

SampleAppController에서 아래 코드처럼 엔티티를 수정하는 코드를 확인합니다.

```csharp
public async Task<SampleDto> UpdateSampleEntityHistoryAsync()
{
    // 파라메터를 받는것 대신 테스트를 위해 내부에서 DTO 선언해서 사용
    UpdateSampleDto sampleDto = new UpdateSampleDto() { Id = 2, Name = $"UpdateSample : {DateTime.Now}", Age = DateTime.Now.Millisecond };
    // DB에서 원본 데이터를 가져온다.
    var sample = await this.GetEntityByIdAsync(sampleDto.Id);
    // 사용자단에서 넘어온 Dto 객체를 엔티티로 매핑
    this.ObjectMapper.Map(sampleDto, sample);
    // 엔티티 객체를 다시 Dto 객체로 다시 매핑하여 리턴한다.
    return this.MapToEntityDto(sample);
}
```

**스웨거에서 확인**

**/api/services/app/Sample/UpdateSampleEntityHistory** 주소를 호출해서 테스트를 할 수 있습니다.

![picture 4](/files/F0zuc3o4HRY1eMZMDsn0)

리턴데이터 확인

![picture 5](/files/tQt0Di50zX0BLFeXlKTl)

위와 같이 확인이 되면 데이터의 히스토리에서 변경내역을 확인할 수 있습니다.

**데이터 베이스에서 확인**

아래는 데이터베이스에서 확인한 데이터 값입니다.

정상적으로 호출이 되어 Sample의 엔티티의 값이 변경이 되면 아래와 같은 유형의 데이터를 확인할 수 있습니다.

EntityChanges 테이블 ![picture 3](/files/DIsj79jyAOlCUJ0fLE2T)

EntityPropertyChanges 테이블

![picture 1](/files/ATctDLooPFZo4NerPPz0)

EntityChangeSets 테이

![picture 2](/files/n2UdDSj6gYOzEX8kxyhs)
