Сильно типизированные идентификаторы в ядре Entity Framework - PullRequest
12 голосов
/ 10 февраля 2020

Я пытаюсь создать строго типизированный класс Id, который теперь содержит внутреннее значение 'long'. Реализация ниже. Проблема, которую я использую в своих объектах, заключается в том, что Entity Framework дает мне сообщение о том, что свойство Id уже сопоставлено с ним. См. Мой IEntityTypeConfiguration ниже.

Примечание: Я не собираюсь иметь жесткую DDD реализацию. Поэтому, пожалуйста, помните об этом , комментируя или отвечая на . Весь идентификатор за напечатанным 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 Я использовал, когда Id не помечен как устаревший для сущности Person К сожалению, однако, когда для типа Id EfCore не хотел отображать его ... когда типа long это не было проблемой ... Другие принадлежащие типы, как вы видите (с 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 ]

3 голосов
/ 16 февраля 2020

Так что после долгого поиска и попытки получить ответ, я нашел его, вот оно. Спасибо Эндрю Локу.

Строго набранные идентификаторы в EF Core: Использование стипендиальных идентификаторов сущностей для избежания примитивной одержимости - Часть 4 : https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/

TL; DR / Сводка Эндрю В этой статье я опишу решение использования строго типизированных идентификаторов в ваших сущностях EF Core с помощью преобразователей значений и пользовательского IValueConverterSelector. Базовый ValueConverterSelector в платформе EF Core используется для регистрации всех встроенных преобразований значений между примитивными типами. Исходя из этого класса, мы можем добавить наши строго типизированные преобразователи идентификаторов в этот список и получить плавное преобразование во всех наших запросах EF Core

3 голосов
/ 10 февраля 2020

Я не собираюсь иметь жесткую DDD реализацию. Поэтому, пожалуйста, имейте это в виду при комментировании или ответе. Весь идентификатор за напечатанным идентификатором предназначен для разработчиков, приходящих в проект, для которых они строго типизированы, чтобы использовать идентификатор во всех своих сущностях

Тогда почему бы просто не добавить псевдоним типа:

using Id = System.Int64;
2 голосов
/ 10 февраля 2020

Я думаю, вам не повезло. Ваш случай использования крайне редок. И EF Core 3.1.1 все еще пытается поставить SQL в базу данных, которая не сломана ни в чем, кроме самых базовых случаев.

Итак, вам придется написать что-то, проходящее через дерево LINQ и это, вероятно, огромная работа, и если вы наткнетесь на ошибки в EF Core - что вы и будете - с удовольствием объясните это в своих билетах.

...