Id fortemente tipizzati in Entity Framework Core


12

Sto cercando di avere una Idclasse fortemente tipizzata , che ora detiene "lunghi" internamente. Implementazione di seguito. Il problema che sto riscontrando nell'utilizzare questo nelle mie entità è che Entity Framework mi dà un messaggio che l' ID proprietà è già mappato su di esso. Vedi il mio IEntityTypeConfigurationsotto.

Nota: non intendo realizzare un'implementazione DDD rigida. Quindi tienilo a mente quando commenti o rispondi . L'intero ID dietro il tipizzato Idè per gli sviluppatori che arrivano al progetto sono fortemente tipizzati per usare Id in tutte le loro entità, ovviamente tradotto in long(o BIGINT) - ma è chiaro quindi per gli altri.

Di seguito la classe e la configurazione, che non funzionano. Il repository è disponibile all'indirizzo https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31 ,

Idimplementazione di classe (ora contrassegnata come obsoleta, perché ho abbandonato l'idea fino a quando non ho trovato una soluzione per questo)

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

EntityTypeConfigurationStavo usando quando ID non contrassegnato come obsoleto per l'entitàPerson Purtroppo però, quando di tipo Id, EfCore non voleva mapparlo ... quando di tipo lungo non era un problema ... Altri tipi di proprietà, come vedi (con Name) funziona bene.

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 base (quando stavo ancora usando Id, quindi quando non era contrassegnato come obsoleto)

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(il dominio e i riferimenti agli altri oggetti oggetto valore sono disponibili all'indirizzo 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);
    }
}

Risposte:


3

Non sto mirando ad avere una rigida implementazione DDD. Quindi tienilo a mente quando commenti o rispondi. L'intero id dietro l'ID digitato è per gli sviluppatori che arrivano al progetto sono fortemente tipizzati per usare l'ID in tutte le loro entità

Quindi perché non aggiungere semplicemente un alias di tipo:

using Id = System.Int64;

Certo, mi piace l'idea. Ma ogni volta che utilizzerai "Id" in un file .cs, non dovresti assicurarti di mettere questa istruzione usando lì sopra - mentre con una classe che viene passata in giro, non è necessario? Inoltre perderei altre funzionalità di classe base come Id.Empty..., o dovrei implementarlo altrimenti in un metodo di estensione quindi ... Mi piace l'idea, grazie per averci pensato. Se non ci fosse altra soluzione, mi accontenterei di questo, poiché ciò indica chiaramente l'intenzione.
Yves Schelpe,

3

Quindi dopo aver cercato a lungo e aver cercato di ottenere qualche risposta in più, l'ho trovato, eccolo qui. Grazie ad Andrew Lock.

ID fortemente tipizzati in EF Core: utilizzo di ID entità fortemente tipizzati per evitare l'ossessione primitiva - Parte 4 : https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ids-to-evitare-primitivo-obsession-part-4 /

TL; DR / Riepilogo di Andrew In questo post descrivo una soluzione all'utilizzo di ID fortemente tipizzati nelle entità EF Core utilizzando i convertitori di valore e un IValueConverterSelector personalizzato. Il ValueConverterSelector di base nel framework EF Core viene utilizzato per registrare tutte le conversioni di valore integrate tra tipi primitivi. Derivando da questa classe, possiamo aggiungere i nostri convertitori di ID fortemente tipizzati a questo elenco e ottenere una conversione senza interruzioni durante le nostre query EF Core


2

Penso che tu sia sfortunato. Il tuo caso d'uso è estremamente raro. E EF Core 3.1.1 sta ancora lottando con l'inserimento di SQL nel database che non è rotto in nulla tranne che nella maggior parte dei casi di base.

Quindi, dovresti scrivere qualcosa che attraversi l'albero di LINQ e questo probabilmente è un'enorme quantità di lavoro, e se ti imbatti in bug su EF Core - che ti piacerà - ti divertirai a spiegarlo nei tuoi biglietti.


Sono d'accordo che il caso d'uso sia raro, ma l'idea alla base non è del tutto stupida, potrei sperare ...? In tal caso, per favore fatemi sapere. Se è stupido (finora convinto di no, dato che gli ID fortemente tipizzati sono così facili da programmare nel dominio), o se non trovo una risposta rapidamente potrei usare un alias come suggerito da David Browne - Micrososft di seguito ( StackOverflow .com / a / 60155275/1155847 ). Fin qui tutto bene su altri casi d'uso, raccolte e campi nascosti in EF Core, nessun bug, quindi ho pensato che sarebbe stato strano, altrimenti ho una solida esperienza con il prodotto.
Yves Schelpe,

Non è stupido di per sé, ma è raro che NO orm che io abbia mai visto lo supporti ed EfCore è così grave che in questo momento sto lavorando per rimuoverlo e tornare a Ef (non core) perché devo spedire. Per me EfCore 2.2 ha funzionato meglio - 3.1 è inattuabile al 100% poiché qualsiasi proiezione che utilizzo risulta in sql errato o "non valutiamo più il lato client" anche se - 2.2 ha valutato perfettamente sul server. Quindi, non mi aspetto che trascorrano del tempo su cose del genere, mentre le loro funzioni principali sono rotte. github.com/dotnet/efcore/issues/19830#issuecomment-584234667 per maggiori dettagli
TomTom

EfCore 3.1 non funzionante, ci sono ragioni per cui il team di EfCore ha deciso di non valutare più il lato client, ha anche emesso avvisi al riguardo in 2.2 per prepararti alle modifiche imminenti. Per quanto riguarda quello, non vedo che quella cosa particolare è rotta. Per quanto riguarda altre cose che non posso commentare, ho visto problemi, ma sono stato in grado di risolverli senza alcun costo perfetto. D'altra parte, negli ultimi 3 progetti che ho realizzato per la produzione, 2 di questi erano basati su Dapper, uno basato su Ef ... Forse dovrei mirare a seguire il percorso dapper per questo, ma sconfigge lo scopo di entrare facilmente per i nuovi sviluppatori :-)... Vedremo.
Yves Schelpe,

Il problema è la definizione di cosa sia la valutazione lato server. Soffiano anche su cose molto semplici che hanno funzionato perfettamente. Funzionalità strappata fino a quando non è stata utilizzata. Rimuoviamo EfCore e torniamo a EF. EF + terze parti per lfiltering globale = funzionante. Il problema con dapper è che consento a tutti gli utenti complessi deciso LINQ - DEVO tradurlo dal bo in una query sul lato server. Ha funzionato in Ef 2.2, ora completamente potenziato.
TomTom

Ok, ora ho letto questo github.com/dotnet/efcore/issues/19679#issuecomment-583650245 ... Capisco cosa intendi Quale lib di terze parti stai usando allora? Potresti riformulare quello che hai detto su Dapper, poiché non capivo cosa volevi dire. Per me ha funzionato, ma erano progetti scuri con solo 2 sviluppatori nel team - e un sacco di piatti manuali da scrivere per farlo funzionare in modo efficiente ovviamente ...
Yves Schelpe,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.