Stark typisierte IDs im Entity Framework Core

12

Ich versuche, eine stark typisierte IdKlasse zu haben, die jetzt intern 'lang' ist. Implementierung unten. Das Problem, das ich in meinen Entitäten habe, ist, dass Entity Framework mir eine Nachricht gibt, dass die Eigenschafts- ID bereits darauf abgebildet ist. Siehe meine IEntityTypeConfigurationunten.

Hinweis: Ich strebe keine starre DDD-Implementierung an. Bitte beachten Sie dies beim Kommentieren oder Beantworten . Die gesamte ID hinter der Eingabe Idist für Entwickler gedacht, die zu dem Projekt kommen. Sie sind stark typisiert, um die ID in all ihren Entitäten zu verwenden, natürlich übersetzt in long(oder BIGINT) - aber dann ist es für andere klar.

Unterhalb der Klasse & Konfiguration, was nicht funktioniert. Das Repo finden Sie unter https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31 ,

IdKlassenimplementierung (jetzt als veraltet markiert, da ich die Idee aufgegeben habe, bis ich eine Lösung dafür gefunden habe)

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 };
    }
}

EntityTypeConfigurationIch habe verwendetPerson , wenn die ID für die Entität nicht als veraltet markiert ist. Leider wollte EfCore sie nicht zuordnen, wenn sie vom Typ ID war. Wenn sie vom Typ lang war, war dies kein Problem. Andere Typen im Besitz, wie Sie sehen (mit Name) funktioniert gut.

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 Basisklasse (als ich noch Id verwendete, also als es nicht als veraltet markiert war)

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(Die Domain und Verweise auf die anderen ValueObjects finden Sie unter 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);
    }
}
Yves Schelpe
quelle

Antworten:

3

Ich strebe keine starre DDD-Implementierung an. Bitte beachten Sie dies beim Kommentieren oder Beantworten. Die gesamte ID hinter der eingegebenen ID ist für Entwickler gedacht, die zu dem Projekt kommen. Sie sind stark typisiert, um die ID in allen ihren Entitäten zu verwenden

Fügen Sie dann einfach einen Typalias hinzu:

using Id = System.Int64;
David Browne - Microsoft
quelle
Klar, ich mag die Idee. Aber jedes Mal, wenn Sie die "ID" in einer CS-Datei verwenden, müssten Sie nicht sicherstellen, dass diese using-Anweisung dort oben platziert wird - während eine Klasse herumgereicht wird, muss man das nicht tun? Außerdem würde ich andere Basisklassenfunktionen wie Id.Empty... verlieren oder müsste sie dann anderweitig in einer Erweiterungsmethode implementieren ... Ich mag die Idee, danke zum Mitdenken. Wenn keine andere Lösung gefunden wird, würde ich mich damit zufrieden geben, da dies eindeutig die Absicht besagt.
Yves Schelpe
3

Nachdem ich lange gesucht und versucht hatte, eine weitere Antwort zu erhalten, fand ich sie, hier ist sie dann. Vielen Dank an Andrew Lock.

Stark typisierte IDs in EF Core: Verwenden stark typisierter Entitäts-IDs, um primitive Besessenheit zu vermeiden - Teil 4 : https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- IDs-um-primitive-Besessenheit-zu-vermeiden-Teil-4 /

TL; DR / Zusammenfassung von Andrew In diesem Beitrag beschreibe ich eine Lösung für die Verwendung stark typisierter IDs in Ihren EF Core-Entitäten mithilfe von Wertkonvertern und einem benutzerdefinierten IValueConverterSelector. Der BasiswertConverterSelector im EF Core-Framework wird verwendet, um alle integrierten Wertkonvertierungen zwischen primitiven Typen zu registrieren. Durch Ableiten von dieser Klasse können wir unsere stark typisierten ID-Konverter zu dieser Liste hinzufügen und eine nahtlose Konvertierung in allen EF Core-Abfragen erzielen

Yves Schelpe
quelle
2

Ich denke du hast kein Glück. Ihr Anwendungsfall ist äußerst selten. Und EF Core 3.1.1 hat immer noch Probleme damit, SQL in die Datenbank zu integrieren, die nur in den meisten Basisfällen fehlerhaft ist.

Sie müssten also etwas schreiben, das durch den LINQ-Baum geht, und dies ist wahrscheinlich eine enorme Menge an Arbeit, und wenn Sie auf Fehler in EF Core stoßen - was Sie werden -, haben Sie Spaß daran, dies in Ihren Tickets zu erklären.

TomTom
quelle
Ich bin damit einverstanden, dass der Anwendungsfall selten ist, aber die Idee dahinter ist nicht ganz dumm, könnte ich hoffen ...? Wenn ja, lassen Sie es mich bitte wissen. Wenn es dumm ist (bisher nicht überzeugt, da stark typisierte IDs in der Domäne so einfach zu programmieren sind) oder wenn ich keine schnelle Antwort finde, kann ich einen Alias ​​verwenden, wie von David Browne - Micrososft unten vorgeschlagen ( Stackoverflow) .com / a / 60155275/1155847 ). So weit so gut in anderen Anwendungsfällen und Sammlungen und versteckten Feldern in EF Core, keine Fehler, also fand ich es seltsam, da ich sonst eine solide gute Erfahrung mit dem Produkt habe.
Yves Schelpe
Es ist an sich nicht dumm, aber es ist selten genug, dass KEIN Orm, den ich jemals gesehen habe, es unterstützt und EfCore so schlecht ist, dass ich gerade daran arbeite, es zu entfernen und zu Ef (nicht Kern) zurückzukehren, weil ich versenden muss. Für mich hat EfCore 2.2 besser funktioniert - 3.1 ist zu 100% unveränderlich, da jede Projektion, die ich verwende, zu schlechtem SQL führt oder "wir bewerten die Clientseite nicht mehr", selbst wenn - 2.2 auf dem Server perfekt bewertet wurde. Ich würde also nicht erwarten, dass sie Zeit mit solchen Dingen verbringen - während ihre Kernfunktionen kaputt sind. github.com/dotnet/efcore/issues/19830#issuecomment-584234667 für weitere Details
TomTom
EfCore 3.1 ist defekt. Es gibt Gründe, warum das EfCore-Team beschlossen hat, die Clientseite nicht mehr zu bewerten. In 2.2 werden sogar Warnungen ausgegeben, um Sie auf bevorstehende Änderungen vorzubereiten. Was das betrifft, sehe ich nicht, dass diese bestimmte Sache kaputt ist. Was andere Dinge betrifft, die ich nicht kommentieren kann, habe ich Probleme gesehen, konnte sie aber ohne Perf-Kosten lösen. Auf der anderen Seite waren bei den letzten 3 Projekten, die ich für die Produktion gemacht habe, 2 davon Dapper-basiert, ein Ef-basiert ... Vielleicht sollte ich versuchen, den adretten Weg für dieses zu gehen, aber den Zweck des einfachen Einstiegs für neue Entwickler zunichte machen :-)... Wir werden sehen.
Yves Schelpe
Das Problem ist die Definition der serverseitigen Auswertung. Sie blasen sogar auf sehr einfache Sachen, die einwandfrei funktionierten. Funktionalität herausgerissen, bis sie verwendet wurde. Wir entfernen einfach EfCore und kehren zu EF zurück. EF + Drittanbieter für globales Filtern = funktioniert. Das Problem mit dapper ist, dass ich jedem komplexen Benutzer die Entscheidung über LINQ erlaube - ich MUSS das aus dem bo in eine serverseitige Abfrage übersetzen. Arbeitete in Ef 2.2, jetzt total kaputt.
TomTom
Ok, ich habe jetzt diesen Artikel gelesen: github.com/dotnet/efcore/issues/19679#issuecomment-583650245 ... Ich verstehe, was Sie meinen. Welche Drittanbieter-Bibliothek verwenden Sie dann? Könnten Sie umformulieren, was Sie über Dapper gesagt haben, da ich nicht verstanden habe, was Sie meinten? Für mich hat es funktioniert, aber es waren Projekte, die mit nur 2 Entwicklern im Team zurückhaltend waren - und natürlich viel manuelles Boilerplate, damit es effizient funktioniert ...
Yves Schelpe