1

问题摘要:我有一个 Master 和 Detail 实体。当我初始化一个 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实体不会在实体框架“图”(内部存储器)中。图中唯一的将是一个实体。

但是,当您查询Detail “让我们打个招呼”)时,它会加载到图中,并且基于 FK 关联解析了引用,因此您的最终测试通过了,因为Master现在与Detail相关。

不过我可能是错的——但这就是听起来的样子。

您是否启用了延迟加载?如果没有,您需要预先加载所需的关系。

而不是这个:

var masterReloaded = contextReloaded.Masters.First();

试试这个:

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

马特·汉密尔顿是对的(见上文)。问题是:

  1. Detail 属性不应构造函数中实例化,也不应通过支持成员通过 getter/setter 实例化。如果在新实例中实例化包含属性的实体很方便,那么拥有一个单独的初始化方法可能会有所帮助,该方法不会由实体框架在从数据库中重建对象时自动执行。
  2. Detail 属性需要在 Master 类中声明为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);
   }

最后,也不必具有双向关系。可以从 Detail 类中安全地删除对“MyMaster”的引用,并且可以使用以下映射代替:

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

使用上述方法,执行 context.Details.Remove(master.Detail) 会导致 master.Detail == null 为真(如您所料/希望的那样)。

我认为 X 对多映射出现了一些混乱,您可以在构造函数中初始化实体的虚拟列表(例如,调用 myDetails = new List(); ),因为您没有实例化实体本身。

顺便说一句,如果有人在使用从 Master 到LIST of Details的一对多单向映射时遇到一些困难,以下对我有用:

 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 回答