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

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다.

...
[MaxLength(32)]
public string? 제목 { get; set; }

엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다.

속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다.

그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 관리를 하면 편리합니다.

다음 코드 예시는 유형의 길이(MaxLength) 등을 정의해서 활용하는 방법입니다.

public interface IHaveValue<TModel, TProvider>
{
    public abstract static TModel Create(TProvider Value);

    TProvider Value { get; }
}

public interface IHaveValueWithLength<TModel, TProvider> : IHaveValue<TModel, TProvider>
{
    public abstract static int MaxLength { get; }
}

public record Title(string Value) : IHaveValueWithLength<Title, string>
{
    public static int MaxLength => 64;

    public static Title Create(string Value) => new(Value);
}

| ValueWithLengthConverter.cs

public class ValueWithLengthConverter<TModel, TProvider> : ValueConverter<TModel, TProvider>
    where TModel : IHaveValueWithLength<TModel, TProvider>
{
    public ValueWithLengthConverter() : base(
        v => v.Value,
        v => Convert(v),
        new ConverterMappingHints(size: TModel.MaxLength)
    )
    {
    }

    private static TModel Convert(TProvider value) => TModel.Create(value);
}

다음처럼 적용하고 사용할 수 있습니다.

| DbContext

...
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder
            .Properties<Title>()
            .HaveConversion<ValueWithLengthConverter<Title, string>>();
    }
...

| 엔터티

...
    public Title? 제목 { get; set; }
...

이제 제목의 경우 Title 유형을 사용하는 것으로 일관되게 유형 길이가 적용이 됩니다.

4 Likes

uuid를 적용할때도 활용할 수 있습니다. 저는 nanoid-net를 쓸 것인데요, 다음처럼 사용할 수 있습니다.

public record Uid(string Value) : IHaveValueWithLength<Uid, string>
{
    public static int MaxLength => 21;

    public static Uid Create(string Value) => new(Value);
    public static Uid New() => new(Nanoid.Generate(size: MaxLength));
}

public record Suid(string Value) : IHaveValueWithLength<Suid, string>
{
    public static int MaxLength => 10;

    public static Suid Create(string Value) => new(Value);

    public static Uid New() => new(Nanoid.Generate(size: MaxLength));
}

| DbContext

...
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder
            .Properties<Uid>()
            .HaveConversion<ValueWithLengthConverter<Uid, string>>();

        configurationBuilder
            .Properties<Suid>()
            .HaveConversion<ValueWithLengthConverter<Suid, string>>();
    }
...

| 엔터티

    [Key]
    public Uid Id { get; init; } = Uid.New();

잘 적용됨을 알 수 있습니다.

4 Likes