Entity Framework Core 복합 속성 업데이트 버전 차이

  • 4 minutes to read

소개

Entity Framework Core는 .NET 애플리케이션에서 데이터베이스 작업을 간편하게 수행할 수 있도록 도와주는 ORM(Object-Relational Mapping) 프레임워크입니다. .NET 5.0과 .NET 8.0에서는 엔터티 업데이트를 처리하는 방식에 약간의 차이가 있습니다. 이 아티클에서는 두 버전의 차이점을 간단한 예제와 함께 설명합니다.

.NET 5.0 예제

.NET 5.0에서는 복합 속성(예: PhysicalAddress.Street)을 처리하는 업데이트 메서드는 다음과 같이 작성할 수 있습니다:

public void Update(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    var entry = dbContext.Attach(entity);
    var originalValues = entry.GetDatabaseValues();
    string userName = httpContextAccessor.HttpContext.User.Identity.Name;
    DateTimeOffset currentTime = DateTimeOffset.Now;
    long auditTrailId = 0;

    foreach (var property in properties)
    {
        string propertyName = GetPropertyName(property);

        if (propertyName.Contains(".") && propertyName.Split(".").Length == 2)
        {
            var referenceEntry = entry.Reference(propertyName.Split(".").First()).TargetEntry;
            var originalValuesReference = referenceEntry.GetDatabaseValues();
            var oldValue = originalValuesReference[propertyName.Split(".").Last()]?.ToString();
            var newValue = referenceEntry.Property(propertyName.Split(".").Last()).CurrentValue?.ToString();

            referenceEntry.Property(propertyName.Split(".").Last()).IsModified = oldValue != newValue;
        }
        else
        {
            var oldValue = originalValues[propertyName]?.ToString();
            var newValue = entry.Property(propertyName).CurrentValue?.ToString();

            entry.Property(propertyName).IsModified = oldValue != newValue;
        }

        // 감사 로직 생략
    }

    dbContext.SaveChanges();
}

private string GetPropertyName(Expression<Func<TEntity, object>> property)
{
    return (property.Body as MemberExpression ?? ((UnaryExpression)property.Body).Operand as MemberExpression).Member.Name;
}

.NET 8.0 예제

.NET 8.0에서는 동일한 작업을 수행하기 위해 몇 가지 수정이 필요합니다. 복합 속성의 업데이트를 처리할 때 새로운 메서드를 사용하여 엔터티 속성 이름을 보다 안전하게 가져오고 처리해야 합니다.

public void Update(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    var entry = dbContext.Attach(entity);
    var originalValues = entry.GetDatabaseValues();
    string userName = httpContextAccessor.HttpContext.User.Identity.Name;
    DateTimeOffset currentTime = DateTimeOffset.Now;
    long auditTrailId = 0;

    foreach (var property in properties)
    {
        string propertyName = GetPropertyName(property);

        if (propertyName.Contains("."))
        {
            var propertyNames = propertyName.Split('.');
            var referenceEntry = entry.Reference(propertyNames.First()).TargetEntry;
            var originalValuesReference = referenceEntry.GetDatabaseValues();
            var oldValue = originalValuesReference != null && originalValuesReference[propertyNames.Last()] != null
                ? originalValuesReference[propertyNames.Last()].ToString()
                : string.Empty;
            var newValue = referenceEntry.Property(propertyNames.Last()).CurrentValue?.ToString() ?? string.Empty;

            referenceEntry.Property(propertyNames.Last()).IsModified = !newValue.Equals(oldValue);

            if (!newValue.Equals(oldValue))
            {
                HandleAuditTrail(entity, userName, currentTime, ref auditTrailId, propertyName, oldValue, newValue);
            }
        }
        else
        {
            var oldValue = originalValues != null && originalValues[propertyName] != null
                ? originalValues[propertyName].ToString()
                : string.Empty;
            var newValue = entry.Property(propertyName).CurrentValue?.ToString() ?? string.Empty;

            entry.Property(propertyName).IsModified = !newValue.Equals(oldValue);

            if (!newValue.Equals(oldValue))
            {
                HandleAuditTrail(entity, userName, currentTime, ref auditTrailId, propertyName, oldValue, newValue);
            }
        }
    }

    dbContext.SaveChanges();
}

private string GetPropertyName(Expression<Func<TEntity, object>> property)
{
    var memberExpression = property.Body as MemberExpression ?? ((UnaryExpression)property.Body).Operand as MemberExpression;
    return memberExpression.Member.Name;
}

private void HandleAuditTrail(TEntity entity, string userName, DateTimeOffset currentTime, ref long auditTrailId, string propertyName, string oldValue, string newValue)
{
    try
    {
        if (auditTrailId == 0)
        {
            var entityDetails = GetEntityDetails(entity, entity.GetType().Name, entity.ID);
            auditTrailId = AddAuditTrailRecord(entity.GetType().Name, entity, userName, currentTime, entityDetails, "Update");
        }

        if (auditTrailId == -1)
        {
            return;
        }

        var auditTrailChange = new AuditTrailChange
        {
            PropertyName = propertyName,
            OldValue = oldValue,
            NewValue = newValue,
            AuditTrailRecordID = auditTrailId,
            CreatedBy = userName,
            CreatedAt = currentTime,
            Active = true // Ensure the record is active
        };

        dbContext.Add(auditTrailChange);
    }
    catch (Exception ex)
    {
        // Log the exception or handle it as needed
        Console.WriteLine($"Error in HandleAuditTrail: {ex.Message}");
    }
}

주요 차이점

  1. 속성 이름 추출: .NET 5.0에서는 GetPropertyName 메서드를 사용하여 속성 이름을 추출하는 데 문제가 없었지만, .NET 8.0에서는 복합 속성 이름을 보다 안전하게 처리하기 위해 로직을 개선해야 합니다.
  2. 복합 속성 처리: .NET 8.0에서는 복합 속성을 보다 명확하게 처리하고 TargetEntry를 활용하여 참조 엔터티의 값을 올바르게 가져오고 설정합니다.
  3. 감사 로직 처리: 새로운 HandleAuditTrail 메서드를 도입하여 감사 로직을 분리하고, 감사 기록을 보다 명확하게 관리합니다.

결론

.NET 5.0과 .NET 8.0 모두 Entity Framework Core를 사용하여 엔터티를 업데이트할 수 있지만, 버전 간의 미세한 차이로 인해 코드의 일부를 수정해야 할 수 있습니다. 이 아티클에서는 이러한 차이점을 이해하고 최신 버전에서 발생할 수 있는 문제를 해결하는 방법을 배웠습니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com