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が...それをマップしたくなかったときのタイプの、けれども残念ながら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(ドメインと他のValueObjectsへの参照は、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を使用してプリミティブな執着を回避する-パート4https : //andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ids-to-avoid-primitive-obsession-part-4 /

TL; DR / Andrewの概要 この投稿では、値コンバーターとカスタムIValueConverterSelectorを使用して、EF Coreエンティティで厳密に型指定されたIDを使用するソリューションについて説明します。EF Coreフレームワークの基本ValueConverterSelectorは、プリミティブ型間のすべての組み込み値変換を登録するために使用されます。このクラスから派生することにより、強く型付けされたIDコンバーターをこのリストに追加し、EF Coreクエリ全体でシームレスな変換を取得できます


2

あなたは運が悪いと思います。ユースケースは非常にまれです。そして、EF Core 3.1.1は、ほとんどの基本的なケースを除いて何も壊れていないSQLをデータベースに配置することにまだ苦労しています。

したがって、LINQツリーを通過する何かを記述する必要があり、これはおそらく膨大な量の作業であり、EF Coreのバグに遭遇した場合-チケットでそれを説明するのが楽しいでしょう。


ユースケースはまれであることに同意しますが、その背後にあるアイデアは私が望むかもしれないまったく愚かではありません...?もしそうなら、私に知らせてください。それが愚かである場合(強く型付けされたIDはドメインでプログラミングするのが簡単なので、これまでのところ確信はありません)、またはすぐに答えが見つからない場合は、David Browne-Micrososftによって提案されたエイリアスを使用する可能性があります(stackoverflow .com / a / 60155275/1155847)。これまでのところ、他のユースケース、EF Coreのコレクションと隠しフィールドで問題はなく、バグはないので、奇妙だと思いました。
Yves Schelpe

それ自体は愚かではありませんが、私がこれまでに見たことのないORMがそれをサポートすることはまれであり、EfCoreが非常に悪いため、現在、出荷する必要があるため、削除してEf(非コア)に戻ることに取り組んでいます。私にとってEfCore 2.2の方がうまくいきました-3.1は100%使用不可能です。私が使用するすべての予測は、悪いSQLまたは「クライアント側をもう評価しません」という結果になります-2.2はサーバーで完全に評価しました。したがって、私は彼らがそのようなものに時間を費やすことを期待しません-彼らのコア機能は壊れていますが。詳細については、github.com / dotnet / efcore / issues / 19830#issuecomment
TomTom

EfCore 3.1が壊れました。EfCoreチームがクライアント側をもう評価しないことに決めた理由があり、2.2でそれについて警告を発して、次の変更に備えることができます。それについては、その特定のものが壊れているとは思いません。コメントできない他のことについては、私は問題を見てきましたが、パフォーマンスのコストなしでそれらを解決することができました。一方、私が制作のために行った最後の3つのプロジェクトでは、そのうちの2つはDapperベース、もう1つはEfベースでした...多分私はこのプロジェクトのためにdapperルートを目指すべきですが、新しい開発者の簡単なエントリの目的を打ち負かしています:-)...わかります。
Yves Schelpe

問題は、サーバー側の評価の定義です。彼らは完璧に機能した非常にシンプルなものまで吹き飛ばします。それがuselesになるまで機能を取り除きました。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.