Skip to main content

Command Palette

Search for a command to run...

[Uno] KeyEquality

Updated
2 min read

.NET 단일 코드로 크로스플랫폼 앱을 만들 수 있는 Uno Platform에서는 동일한 엔터티임을 비교하는 KeyEquality를 지원합니다. 사용방법은 다음과 같습니다.

public partial record Entity(string Id, ...)

속성명 Id는 암시적으로 키로 인식하며 다음 처럼 KeyEquals()로 키 동등을 비교할 수 있습니다.

entity.KeyEquals(...);

키 동등은 키 이외의 값이 다르더라도 동일한 엔터티라고 판단한다는 점에서 recordEquals와는 다르게 동작합니다.

public partial record Person([property: Key] string Name, int Age);

var john1 = new Person("John Doe", 20);
var john2 = john1 with { Age=21 };

Console.WriteLine("Are the same : " + john1.Equals(john2));
Console.WriteLine("Are the same person : " + john1.KeyEquals(john2));

Equals()false이지만 KeyEquals()의 결과는 true가 됩니다.

Are the same : false
Are the same person : true

그렇다면 왜 KeyEquals()가 필요할까요? record로 정의된 엔터티는 엔터티의 모든 값에 대한 동등성을 평가하기 보다는 키를 기준으로 동등함과 변경됨을 판단하는 것이 유용합니다. 예를 들어 데이터베이스의 레코드를 업데이트 하는 경우가 되겠습니다.

Uno Platform에서는 이러한 키 동등성을 소스 생성기를 이용해서 자동으로 생성합니다.

partial record Person : global::Uno.Extensions.Equality.IKeyEquatable<BibleVerseCounter.Business.Models.Person>, global::Uno.Extensions.Equality.IKeyed<string>
{

    /// <inheritdoc cref="{NS.Equality}.IKeyed{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    string global::Uno.Extensions.Equality.IKeyed<string>.Key
    {
        get
        {
            return Name;
        }
    }

    /// <inheritdoc cref="global::Uno.Extensions.Equality.IKeyEquatable{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    public virtual int GetKeyHashCode()
    {
        unchecked
        {
            var hash = global::System.Collections.Generic.EqualityComparer<global::System.Type>.Default.GetHashCode(EqualityContract) * -1521134295;


            hash += global::System.Collections.Generic.EqualityComparer<string>.Default.GetHashCode(Name);
            hash *= -1521134295;

            return hash;
        }
    }

    /// <inheritdoc cref="global::Uno.Extensions.Equality.IKeyEquatable{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    public bool KeyEquals(BibleVerseCounter.Business.Models.Person? other)
    {
        if (object.ReferenceEquals(this, other))
        {
            return true;
        }

        if (object.ReferenceEquals(null, other)
            || EqualityContract != other.EqualityContract)
        {
            return false;
        }

        return global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(Name, other.Name);
    }
}

자동 생성된 코드를 보면 IKeyEquatable<> 인터페이스와 IKeyed<string> 인터페이스가 구현 된 것을 볼 수 있습니다.

복합 키를 정의할 수도 있는데 사용 방법은 간단합니다. [property:Key] 특성을 키 속성에 다음처럼 복수로 장식하면,

public partial record CompositeKeyEntity(
    [property:Key] string Key1,
    [property:Key] string Key2,
    string Value1
    );

다음처럼 Uno의 자동 생성 기능으로 코드가 생성 됩니다.

partial record CompositeKeyEntity : global::Uno.Extensions.Equality.IKeyEquatable<BibleVerseCounter.Business.Models.CompositeKeyEntity>, global::Uno.Extensions.Equality.IKeyed<(string, string)>
{

    /// <inheritdoc cref="{NS.Equality}.IKeyed{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    (string, string) global::Uno.Extensions.Equality.IKeyed<(string, string)>.Key
    {
        get
        {
            return (Key1, Key2);
        }
    }

    /// <inheritdoc cref="global::Uno.Extensions.Equality.IKeyEquatable{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    public virtual int GetKeyHashCode()
    {
        unchecked
        {
            var hash = global::System.Collections.Generic.EqualityComparer<global::System.Type>.Default.GetHashCode(EqualityContract) * -1521134295;


            hash += global::System.Collections.Generic.EqualityComparer<string>.Default.GetHashCode(Key1);
            hash *= -1521134295;

            hash += global::System.Collections.Generic.EqualityComparer<string>.Default.GetHashCode(Key2);
            hash *= -1521134295;

            return hash;
        }
    }

    /// <inheritdoc cref="global::Uno.Extensions.Equality.IKeyEquatable{T}" />
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("KeyEqualityGenerationTool", "1")]
    public bool KeyEquals(BibleVerseCounter.Business.Models.CompositeKeyEntity? other)
    {
        if (object.ReferenceEquals(this, other))
        {
            return true;
        }

        if (object.ReferenceEquals(null, other)
            || EqualityContract != other.EqualityContract)
        {
            return false;
        }

        return global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(Key1, other.Key1)
            && global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(Key2, other.Key2);
    }
}

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read15

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read19

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.