web-dev-qa-db-fra.com

Ids fortement typés dans Entity Framework Core

J'essaie d'avoir une classe Id fortement typée, qui détient désormais "long" en interne. Mise en œuvre ci-dessous. Le problème que j'ai en utilisant ceci dans mes entités est que Entity Framework me donne un message indiquant que la propriété Id y est déjà mappée. Voir mon IEntityTypeConfiguration ci-dessous.

Remarque: Je ne vise pas à avoir une implémentation DDD rigide. Alors s'il vous plaît gardez cela à l'esprit lorsque vous commentez ou répondez. L'identifiant entier derrière le Id tapé est pour les développeurs venant au projet, ils sont fortement typés pour utiliser l'ID dans toutes leurs entités, bien sûr traduit en long (ou BIGINT) - mais c'est clair alors pour les autres.

Ci-dessous la classe et la configuration, ce qui ne fonctionne pas. Le dépôt peut être trouvé à https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31 ,

Id implémentation de classe (marqué comme obsolète maintenant, car j'ai abandonné l'idée jusqu'à ce que j'aie trouvé une solution pour cela)

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 J'utilisais lorsque l'identifiant n'était pas marqué comme obsolète pour l'entité Person Malheureusement, quand il était de type Id, EfCore n'a pas Je ne veux pas le mapper ... quand de type long ce n'était pas un problème ... D'autres types possédés, comme vous le voyez (avec Name) fonctionnent bien.

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 classe de base (quand j'utilisais encore Id, donc quand il n'était pas marqué comme obsolète)

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 (le domaine et les références aux autres ValueObjects peuvent être trouvés à 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);
    }
}
12
Yves Schelpe

Je pense que vous n’avez pas de chance. Votre cas d'utilisation est extrêmement rare. Et EF Core 3.1.1 a toujours du mal à mettre SQL sur la base de données qui n'est cassé dans rien, sauf dans la plupart des cas de base.

Donc, vous devriez écrire quelque chose qui passe par l'arborescence LINQ et cela représente probablement une énorme quantité de travail, et si vous tombez sur des bogues sur EF Core - ce que vous ferez -, amusez-vous à l'expliquer dans vos tickets.

2
TomTom