1

問題の概要:マスター エンティティと詳細エンティティがあります。Master (myMaster) を初期化すると、Details (myMaster.Detail) のインスタンスが作成され、myMaster が追加されたときに両方がデータベースに保持されているように見えます。ただし、コンテキストをリロードして myMasterReloaded.detail にアクセスすると、そのプロパティは初期化されません。ただし、コンテキストから直接詳細を取得すると、これは魔法のように myMasterReloaded.detail を初期化するように見えます。以下の最小限の単体テストの例を使用して、これを要約しました。これは「機能」ですか、それとも重要な概念の詳細が欠けていますか?

//DECLARE CLASSES
public class Master
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid MasterId { get; set; }
    public Detail Detail { get; set; }
    public Master() { Detail = new Detail(); }
}

public class Detail
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid DetailId { get; set; }
    public Master MyMaster{ get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Master> Masters { get; set; }
    public DbSet<Detail> Details { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithOptionalPrincipal(x => x.MyMaster)
            .WillCascadeOnDelete(true);
    }
}

//PERFORM UNIT TEST
[TestMethod]
public void UnitTestMethod()
{
    //Start with fresh DB
    var context = new MyDbContext();
    context.Database.Delete();
    context.Database.CreateIfNotExists();

    //Create and save entities
    var master = context.Masters.Create();            
    context.Masters.Add(master);
    context.SaveChanges();

    //Reload entity
    var contextReloaded = new MyDbContext();
    var masterReloaded = contextReloaded.Masters.First();

    //This should NOT Pass but it does..
    Assert.AreNotEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);

    //Let's say 'hi' to the instance of details in the db without using it.
    contextReloaded.Details.First();

    //By simply referencing the instance above, THIS now passes, contracting the first Assert....WTF??
    Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);
}

(これは、より洗練されたエンティティ セットの難点です。詳細を単純に複雑な型に置き換えることができない最も単純なケースにこれを単純に要約しました)。

乾杯、ロブ

4

2 に答える 2

2

最初にMasterをリロードしたときにDetailを熱心にロードしていないため、Detailエンティティが Entity Framework の「グラフ」(内部メモリ) にないためだと思います。グラフ内の唯一のものは、単一のマスターエンティティになります。

しかし、Detail をクエリすると( " Let's say hi")、それがグラフに読み込まれ、FK 関連付けに基づいて参照が解決されます。したがって、MasterDetailに関連付けられるようになったため、最終的なテストに合格します。

私は間違っているかもしれませんが、それはそのように聞こえます.

遅延読み込みを有効にしていますか? そうでない場合は、必要な関係を熱心に読み込む必要があります。

これの代わりに:

var masterReloaded = contextReloaded.Masters.First();

これを試して:

var masterReloaded = contextReloaded.Masters.Include(x => x.Detail).First();
于 2011-02-10T23:47:18.000 に答える
0

Matt Hamilton は正しかった (上記参照)。問題は次のとおりです。

  1. Detail プロパティは、コンストラクター内でインスタンス化することも、バッキング メンバーを介してゲッター/セッターを介してインスタンス化することもできません。新しいインスタンスでプロパティを含むエンティティをインスタンス化するのが便利な場合は、データベースからオブジェクトを再構築するときにエンティティ フレームワークによって自動的に実行されない個別の初期化メソッドを用意すると役立つ場合があります。
  2. これが正しく機能するためには、Master クラスでDetail プロパティをvirtualとして宣言する必要があります。

以下はパスします (期待どおり/希望)

  public class Master
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid MasterId { get; set; }

    //Key new step: Detail MUST be declared VIRTUAL
    public virtual Detail Detail { get; set; }
}

public class Detail
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid DetailId { get; set; }
    //Set this to be VIRTUAL as well
    public virtual Master MyMaster { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Master> Masters { get; set; }
    public DbSet<Detail> Details { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //This sets up a BI-DIRECTIONAL relationship
        modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithOptionalPrincipal(x => x.MyMaster)
            .WillCascadeOnDelete(true);
    }
}


[TestMethod]
public void UnitTestMethod()
{

   var context = new MyDbContext();
   context.Database.Delete();
   context.Database.CreateIfNotExists();

   //Create and save entities
   var master = context.Masters.Create();

   //Key new step: Detail must be instantiated and set OUTSIDE of the constructor
   master.Detail = new Detail();
   context.Masters.Add(master);
   context.SaveChanges();

   //Reload entity
   var contextReloaded = new MyDbContext();
   var masterReloaded = contextReloaded.Masters.First();

   //This NOW Passes, as it should
   Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);

   //This line is NO LONGER necessary
   contextReloaded.Details.First();

   //This shows that there is no change from accessing the Detail from the context
   Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);
   }

最後に、双方向の関係を持つ必要もありません。「MyMaster」への参照は Detail クラスから安全に削除でき、代わりに次のマッピングを使用できます。

modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithMany()
            .IsIndependent();

上記で context.Details.Remove(master.Detail) を実行すると、 master.Detail == null が true になりました (期待/希望どおり)。

エンティティ自体をインスタンス化していないため、コンストラクターでエンティティの仮想リストを初期化できる X-to-many マッピング (たとえば、 myDetails = new List(); の呼び出し) から混乱の一部が生じたと思います。

ちなみに、マスターから詳細のリストへの1対多の一方向マップで誰かが問題を抱えている場合、次のことがうまくいきました。

 modelBuilder.Entity<Master>()
             .HasMany(x => x.Details)
             .WithMany()
             .Map((x) =>
                      {
                           x.MapLeftKey(m => m.MasterId, "MasterId");
                           x.MapRightKey(d => d.DetailId, "DetailId");
                       });

乾杯、 ロブ

于 2011-02-12T22:12:09.340 に答える