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.