web-dev-qa-db-ja.com

EF code first:ナビゲーションプロパティを初期化する必要がありますか?

私はいくつかの本を見ました(例:programming entity framework code first Julia Lerman

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Address { get; set; }
    public virtual License License { get; set; }
}

他のいくつかの書籍またはツール(例:Entity Framework Power Tools)POCOを生成すると、クラスのナビゲーションプロパティが初期化されます。

public class User
{
    public User()
    {
        this.Addresses = new IList<Address>();
        this.License = new License();
    }
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual License License { get; set; }
}

Q1:どちらが良いですか?どうして?長所と短所?

編集:

public class License
{
    public License()
    {
        this.User = new User();
    }
    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}

Q2:2番目のアプローチでは、 `License`クラスが` User`クラスへの参照も持っている場合、スタックオーバーフローが発生します。これは、一方向の参照が必要であることを意味します。(?)どのナビゲーションプロパティを削除するかを決定する方法

57

コレクション:問題ではありません。

コレクションとナビゲーションプロパティとしての参照には明確な違いがあります。参照エンティティです。コレクションは、エンティティを含みます。これは、ビジネスロジックの観点から、コレクションの初期化が無意味であることを意味します。つまり、エンティティ間の関連付けを定義しません。参照の設定は行います。

したがって、埋め込みリストを初期化するかどうか、またはどのように初期化するかは、純粋に好みの問題です。

「方法」については、一部の人々は遅延初期化を好む:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}

Null参照例外を防ぐため、ユニットテストとコレクションの操作を容易にしますが、不必要な初期化も防ぎます。後者は、クラスに比較的多くのコレクションがある場合に違いを生じる場合があります。欠点は、それが比較的多くの配管を要することです、特に。初期化なしの自動プロパティと比較した場合。また、C#でのnull伝播演算子の出現により、コレクションプロパティの初期化の緊急性が低下しました。

...明示的なロードが適用されない限り

唯一のことは、コレクションを初期化すると、Entity Frameworkによってコレクションがロードされたかどうかを確認するのが難しくなることです。コレクションが初期化されると、次のようなステートメントが...

var users = context.Users.ToList();

...空の、nullでないUserコレクションを持つAddressesオブジェクトを作成します(遅延ロードは別として)。コレクションがロードされているかどうかを確認するには、次のようなコードが必要です...

var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;

コレクションが初期化されていない場合、単純なnullチェックが実行されます。したがって、選択的明示的読み込みがコーディング慣行の重要な部分である場合、つまり...

if (/*check collection isn't loaded*/)
    context.Entry(user).Collection(c => c.Addresses).Load();

...コレクションプロパティを初期化しない方が便利な場合があります。

参照プロパティ:しない

参照プロパティはエンティティであるため、空のオブジェクトを割り当てるのは意味のあるです。

さらに悪いことに、コンストラクタでそれらを開始した場合、EFはオブジェクトの実体化または遅延読み込みによってそれらを上書きしません。それらは、積極的にそれらを置き換えるまで、常に初期値を持ちます。さらに悪いことに、空のエンティティをデータベースに保存してしまうこともあります!

また、別の効果があります:relationship fixupは発生しません。関係修正は、EFがナビゲーションプロパティによってコンテキスト内のすべてのエンティティを接続するプロセスです。 UserLicenceを別々にロードしても、User.Licenseが入力され、その逆も行われます。もちろん、Licenseがコンストラクターで初期化された場合を除きます。これは、1:nの関連付けにも当てはまります。 AddressがコンストラクターでUserを初期化する場合、User.Addressesは入力されません!

Entity Frameworkコア

Entity Frameworkコアの関係修正(執筆時点では2.1)は、コンストラクターで初期化された参照ナビゲーションプロパティの影響を受けません。つまり、ユーザーとアドレスがデータベースから個別に取得されると、ナビゲーションプロパティが設定されます。
ただし、遅延読み込みはnot初期化された参照ナビゲーションプロパティを上書きします。そのため、結論として、EFコアでも、コンストラクターで参照ナビゲーションプロパティを初期化すると問題が発生する場合があります。しないでください。とにかく意味をなさない、

70
Gert Arnold

すべてのプロジェクトで、「コレクションはnullであってはなりません。空または値を持っています。」

これらのエンティティの作成がサードパートコード(ORMなど)の責任であり、短時間のプロジェクトで作業している場合、最初の例があります。

2番目の例の方が優れています。

  • エンティティにはすべてのプロパティが設定されていると確信しています
  • 愚かなNullReferenceExceptionを避ける
  • コードの消費者をより幸せにする

ドメイン駆動設計を実践している人々は、コレクションを読み取り専用として公開し、それらのセッターを避けます。 ( NHibernateの読み取り専用リストのベストプラクティスは何ですか?

Q1:どちらが良いですか?どうして?長所と短所

コードで追加のチェックを避けるため(たとえば、Addresses)、null以外のコレクションを公開することをお勧めします。コードベースに含めることは良い契約です。しかし、私は単一のエンティティへのnullable参照を公開しても大丈夫です(例:License

Q2:2番目のアプローチでは、LicenseクラスにもUserクラスへの参照がある場合、スタックオーバーフローが発生します。これは、一方向の参照が必要であることを意味します。(?)どのナビゲーションプロパティを削除するかを決定する方法は?

data mapper pattern を自分で開発したとき、双方向参照を避けようとしましたが、子から親への参照はほとんどありませんでした。

ORMを使用すると、双方向参照を簡単に作成できます。

双方向参照セットを使用して単体テストのテストエンティティを構築する必要がある場合、次の手順に従います。

  1. parent entityをemty children collectionでビルドします。
  2. 次に、parent entityを参照してevey childchildren collectionに追加します。

License型にパラメーターなしのコンストラクターがあることに注意して、userプロパティを必須にします。

public class License
{
    public License(User user)
    {
        this.User = user;
    }

    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}
4
Ilya Palkin

POCOは遅延読み込みに依存しているため、newリストに対して冗長です。

遅延読み込みとは、エンティティを参照するプロパティに初めてアクセスしたときに、エンティティまたはエンティティのコレクションがデータベースから自動的に読み込まれるプロセスです。 POCOエンティティタイプを使用する場合、派生プロキシタイプのインスタンスを作成し、仮想プロパティをオーバーライドしてロードフックを追加することにより、遅延ロードが実現されます。

仮想修飾子を削除する場合は、遅延読み込みをオフにします。その場合、リストが初期化されないため、コードは機能しなくなります。

遅延ロードはエンティティフレームワークでサポートされている機能であり、DbContextのコンテキスト外でクラスを作成すると、依存するコードは明らかにNullReferenceExceptionの影響を受けることに注意してください。

HTH

3
bas

Q1:どちらが良いですか?どうして?長所と短所?

仮想プロパティがエンティティコンストラクター内で設定される場合の2番目のバリアントには、「 コンストラクターでの仮想メンバー呼び出し 」と呼ばれる明確な問題があります。

ナビゲーションプロパティの初期化を行わない最初のバリアントについては、オブジェクトの作成者/作成者に応じて2つの状況があります。

  1. エンティティフレームワークはオブジェクトを作成します
  2. コードコンシューマーはオブジェクトを作成します

最初のバリアントは、Entity Frameworkがオブジェクトを作成すると完全に有効ですが、コードコンシューマーがオブジェクトを作成すると失敗する可能性があります。

コードコンシューマが常に有効なオブジェクトを作成することを保証するソリューションは、 static factory method を使用することです。

  1. デフォルトのコンストラクタを保護します。 Entity Frameworkは、保護されたコンストラクターで動作します。

  2. 空のオブジェクトを作成する静的ファクトリーメソッドを追加します。 Userオブジェクト。すべてのプロパティを設定します。 AddressesおよびLicense、作成後、完全に構築されたUserオブジェクトを返します

このように、Entity Frameworkは保護されたデフォルトコンストラクターを使用して、一部のデータソースから取得したデータから有効なオブジェクトを作成し、コードコンシューマーは静的ファクトリメソッドを使用して有効なオブジェクトを作成します。

3
Lightman

私はこれからの答えを使用します なぜEntity Framework Code Firstプロキシコレクションがnullで、なぜ設定できないのですか?

コンストラクターの初期化に問題がありました。これを行う唯一の理由は、テストコードを簡単にするためです。コレクションがnullにならないようにすることで、テストなどで常に初期化することができます。

2
GraemeMiller

他の回答は完全に質問に答えますが、この質問はまだ関連性があり、Google検索で表示されるため、何かを追加したいと思います。

Visual Studioで「データベースの最初のモデルをコード化」ウィザードを使用すると、すべてのコレクションが次のように初期化されます。

public partial class SomeEntity
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public SomeEntity()
    {
        OtherEntities = new HashSet<OtherEntity>();
    }

    public int Id { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}

ウィザードの出力は、基本的にMicrosoftからの公式の推奨事項であるため、この5年前の質問に追加する理由を説明します。したがって、すべてのコレクションをHashSetsとして初期化します

そして個人的には、C#6.0の自動プロパティ初期化子を利用するために上記を微調整するのはかなり滑らかだと思います。

    public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();
1