Entity Framework Core에서 강력한 형식의 ID


12

Id내부적으로 'long'을 유지 하는 강력한 형식의 클래스를 만들려고합니다 . 아래 구현. 내 엔터티에서 이것을 사용하는 데있어 문제는 Entity Framework 에서 속성 ID 가 이미 매핑되어 있다는 메시지를 표시한다는 것입니다. IEntityTypeConfiguration아래를 참조하십시오 .

참고 : 엄격한 DDD 구현을 목표로하지 않습니다. 따라서 의견을 말하거나 대답 할 때이 점을 명심 하십시오 . 유형이 지정된 뒤 전체 ID는 Id그들이 강력 코스 번역의 그 모든 엔티티에서 ID를 사용하는 입력하고 프로젝트에 오는 개발자를위한 것입니다 long(또는 BIGINT) - 그러나 다른 사람을 위해 다음 분명하다.

클래스 및 구성 아래에서 작동하지 않습니다. 리포지는 https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31 에서 찾을 수 있습니다 .

Id클래스 구현 (지금은 해결책을 찾을 때까지 아이디어를 포기했기 때문에 더 이상 사용되지 않음으로 표시됨)

namespace Kf.CANetCore31.DomainDrivenDesign
{
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    [Obsolete]
    public sealed class Id : ValueObject
    {
        public static implicit operator Id(long value)
            => new Id(value);
        public static implicit operator long(Id value)
            => value.Value;
        public static implicit operator Id(ulong value)
            => new Id((long)value);
        public static implicit operator ulong(Id value)
            => (ulong)value.Value;
        public static implicit operator Id(int value)
            => new Id(value);


        public static Id Empty
            => new Id();

        public static Id Create(long value)
            => new Id(value);

        private Id(long id)
            => Value = id;
        private Id()
            : this(0)
        { }

        public long Value { get; }

        public override string DebuggerDisplayString
            => this.CreateDebugString(x => x.Value);

        public override string ToString()
            => DebuggerDisplayString;

        protected override IEnumerable<object> EquatableValues
            => new object[] { Value };
    }
}

EntityTypeConfiguration아이디 엔티티에 대한 오래된 것으로 표시하지 않을 때 내가 사용했다Person 불행하게도하지만, EfCore가 오랫동안 아무 문제 ... 다른 소유 유형 없을 때 유형의 당신이 (함께 보는 바와 같이, ... 매핑하지 않은 경우 유형 ID의 Name) 잘 작동합니다.

public sealed class PersonEntityTypeConfiguration
        : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            // this would be wrapped in either a base class or an extenion method on
            // EntityTypeBuilder<TEntity> where TEntity : Entity
            // to not repeated the code over each EntityTypeConfiguration
            // but expanded here for clarity
            builder
                .HasKey(e => e.Id);
            builder
                .OwnsOne(
                e => e.Id,
                id => {
                   id.Property(e => e.Id)
                     .HasColumnName("firstName")
                     .UseIdentityColumn(1, 1)
                     .HasColumnType(SqlServerColumnTypes.Int64_BIGINT);
                }

            builder.OwnsOne(
                e => e.Name,
                name =>
                {
                    name.Property(p => p.FirstName)
                        .HasColumnName("firstName")
                        .HasMaxLength(150);
                    name.Property(p => p.LastName)
                        .HasColumnName("lastName")
                        .HasMaxLength(150);
                }
            );

            builder.Ignore(e => e.Number);
        }
    }

Entity 기본 클래스 (아직 ID를 사용하고있을 때 사용되지 않는 것으로 표시되지 않은 경우)

namespace Kf.CANetCore31.DomainDrivenDesign
{
    /// <summary>
    /// Defines an entity.
    /// </summary>
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    public abstract class Entity
        : IDebuggerDisplayString,
          IEquatable<Entity>
    {
        public static bool operator ==(Entity a, Entity b)
        {
            if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
                return true;

            if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
                return false;

            return a.Equals(b);
        }

        public static bool operator !=(Entity a, Entity b)
            => !(a == b);

        protected Entity(Id id)
            => Id = id;

        public Id Id { get; }

        public override bool Equals(object @object)
        {
            if (@object == null) return false;
            if (@object is Entity entity) return Equals(entity);
            return false;
        }

        public bool Equals(Entity other)
        {
            if (other == null) return false;
            if (ReferenceEquals(this, other)) return true;
            if (GetType() != other.GetType()) return false;
            return Id == other.Id;
        }

        public override int GetHashCode()
            => $"{GetType()}{Id}".GetHashCode();

        public virtual string DebuggerDisplayString
            => this.CreateDebugString(x => x.Id);

        public override string ToString()
            => DebuggerDisplayString;
    }
}

Person(도메인과 다른 ValueObject에 대한 참조는 https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31/tree/master/Source/Core/Domain/Kf.CANetCore31.Core.Domain/People 에서 찾을 수 있습니다 )

namespace Kf.CANetCore31.Core.Domain.People
{
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    public sealed class Person : Entity
    {
        public static Person Empty
            => new Person();

        public static Person Create(Name name)
            => new Person(name);

        public static Person Create(Id id, Name name)
            => new Person(id, name);

        private Person(Id id, Name name)
            : base(id)
            => Name = name;
        private Person(Name name)
            : this(Id.Empty, name)
        { }
        private Person()
            : this(Name.Empty)
        { }

        public Number Number
            => Number.For(this);
        public Name Name { get; }

        public override string DebuggerDisplayString
            => this.CreateDebugString(x => x.Number.Value, x => x.Name);
    }
}

답변:


3

엄격한 DDD 구현을 목표로하지 않습니다. 따라서 의견을 말하거나 대답 할 때이 점을 명심하십시오. 형식화 된 ID 뒤에있는 전체 ID는 개발자가 모든 엔터티에서 ID를 사용하도록 강력하게 형식화 된 프로젝트에 오는 개발자를위한 것입니다.

그렇다면 왜 타입 별칭을 추가하지 않겠습니까?

using Id = System.Int64;

물론, 나는 그 아이디어를 좋아한다. 그러나 .cs 파일에서 "Id"를 사용할 때마다 클래스를 전달할 때이 using 문을 맨 위에 배치해야 할 필요는 없습니까? 또한 Id.Empty... 와 같은 다른 기본 클래스 기능을 잃 거나 확장 방법으로 다른 방법으로 구현해야합니다 ... 아이디어를 좋아합니다. 다른 해결책이 없다면 의도를 분명히 밝히기 때문에 이에 대해 해결하려고합니다.
— Yves Schelpe

3

그래서 오랜 시간 동안 검색하고 더 많은 답변을 얻으려고 노력한 후에 나는 그것을 찾았습니다. Andrew Lock에게 감사합니다.

EF Core의 강력한 유형의 ID : 강력한 유형의 엔티티 ID를 사용하여 원시적 인 집착을 피 하십시오 -4 부 : https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ids-to-avoid-primitive-obsession-part-4 /

TL; DR / 앤드류 요약 이 글에서는 값 변환기와 사용자 정의 IValueConverterSelector를 사용하여 EF Core 엔티티에서 강력한 유형의 ID를 사용하는 솔루션에 대해 설명합니다. EF Core 프레임 워크의 기본 ValueConverterSelector는 기본 유형 사이의 모든 내장 값 변환을 등록하는 데 사용됩니다. 이 클래스를 통해 강력한 형식의 ID 변환기를이 목록에 추가하고 EF Core 쿼리 전체에서 원활하게 변환 할 수 있습니다.


2

운이 나쁘다고 생각합니다. 사용 사례는 매우 드 rare니다. 그리고 EF Core 3.1.1은 여전히 ​​가장 기본적인 경우를 제외하고는 손상되지 않은 데이터베이스에 SQL을 배치하는 데 어려움을 겪고 있습니다.

따라서 LINQ 트리를 통과하는 무언가를 작성해야 할 것입니다.이 작업은 엄청난 양의 작업 일 것입니다 .EF Core의 버그에 걸려 넘어지면 티켓에서 설명하는 것이 재미 있습니다.


유스 케이스가 드물다는 데 동의하지만 그 아이디어는 완전히 어리석은 것이 아니라고 생각합니다 ...? 그렇다면 알려주세요. 어리석은 경우 (강력한 유형의 ID가 도메인에서 프로그래밍하기가 쉽기 때문에 지금까지 확신하지 못했거나) 대답을 빨리 찾지 못하면 David Browne-Micrososft가 제안한 별칭을 아래에서 사용할 수 있습니다 ( stackoverflow .com / a / 60155275 / 1155847 ). 지금까지 다른 유스 케이스와 EF Core의 컬렉션 및 숨겨진 필드에 버그가 없었으므로 버그가 없으므로 이상하게 생각했습니다. 그렇지 않으면 제품에 대한 좋은 경험이 있습니다.
— Yves Schelpe

그것은 그 자체로 어리석지 않지만, 내가 본 적이있는 NO orm이 그것을 지원하고 EfCore가 너무 나빠서 지금 그것을 제거하고 선적해야하기 때문에 Ef (비 코어)로 돌아가는 것은 드문 일입니다. 나를 위해 EfCore 2.2가 더 잘 작동했습니다. 3.1은 서버에서 완벽하게 평가되었지만-나쁜 SQL을 초래하거나 "더 이상 클라이언트 측을 평가하지 않습니다"라는 결과로 100 % 사용할 수 없습니다. 그래서 나는 그들이 핵심 기능이 깨지는 동안 그런 것들에 시간을 할애하지 않을 것입니다. 자세한 내용은 github.com/dotnet/efcore/issues/19830#issuecomment-584234667
— TomTom

EfCore 3.1이 깨졌습니다. EfCore 팀이 더 이상 클라이언트 측을 평가하지 않기로 결정한 이유가 있습니다. 2.2 버전에서 이에 대한 경고를 표시하여 향후 변경을 준비 할 수도 있습니다. 그에 관해서는, 나는 그 특정한 것이 깨지는 것을 보지 못했습니다. 내가 말할 수없는 다른 것들에 관해서는, 나는 문제를 보았지만 성능 비용없이 문제를 해결할 수있었습니다. 다른 한편으로, 내가 생산을 위해 한 마지막 3 개의 프로젝트에서 그들 중 2 개는 Dapper 기반, 하나는 Ef 기반이었습니다 ... 어쩌면 나는 이것에 대한 Dapper 경로를 목표로 삼아야하지만 새로운 개발자를위한 쉬운 진입의 목적을 상실합니다. :-) ... 봅시다.
— Yves Schelpe

문제는 서버 측 평가의 정의입니다. 그들은 완벽하게 작동하는 매우 간단한 것들을 날려 버립니다. 쓸모 없을 때까지 기능을 제거했습니다. EfCore를 제거하고 EF로 돌아갑니다. EF + 글로벌 lfiltering에 대한 타사 = 작동. dapper의 문제점은 모든 복잡한 사용자가 LINQ를 결정하도록 허용한다는 것입니다. 나는 그것을 bo에서 서버 측 쿼리로 변환해야합니다. Ef 2.2에서 일했으며 지금은 완전히 붕어가났습니다.
— TomTom

좋아, 이제이 github.com/dotnet/efcore/issues/19679#issuecomment-583650245를 읽었습니다. 무슨 의미인지 알겠습니다 . 당신이 의미하는 바를 이해하지 못했기 때문에 Dapper에 대해 말한 내용을 다시 말씀해 주시겠습니까? 저에게는 효과가 있었지만 팀에 2 명의 개발자 만있는 키가 적은 프로젝트였습니다. 물론 효율적으로 작동하도록 많은 수동 상용구가 작성되었습니다.
— Yves Schelpe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.