使用实体框架保存问题(需要概念上的帮助)

时间:2011-02-10 23:41:21

标签: entity-framework code-first ado.net-entity-data-model entity-framework-ctp5

问题摘要:我有一个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);
}

(这是更复杂的实体集的关键点。我简单地将其简化为最简单的情况,我不能简单地用复杂类型替换细节)。

干杯, 罗布

2 个答案:

答案 0 :(得分:2)

认为是因为当你第一次重新加载 Master 时,你没有急于加载细节,所以细节实体不会在实体框架“图形”(内部存储器)中。图表中唯一的内容是单个 Master 实体。

但是,当您查询 明细(“让我们打个招呼”)时,会将其加载到图表中并根据FK关联解析参考,因此您的最终测试通过, Master 现在与详细信息相关。

我可能错了 - 但这听起来像是这样。

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

而不是:

var masterReloaded = contextReloaded.Masters.First();

试试这个:

var masterReloaded = contextReloaded.Masters.Include(x => x.Detail).First();

答案 1 :(得分:0)

Matt Hamilton是对的(见上文)。问题是:

  1. Detail属性应在构造函数中实例化,也不应通过支持成员通过getters / setter实例化。如果在新实例中实例化包含Properties的实体很方便,那么在从数据库重构对象时,实体框架不会自动执行单独的初始化方法可能会有所帮助。
  2. 需要在Master类中声明Detail属性 virtual 才能使其正常工作。
  3. 以下遗嘱通过(如预期/希望)

      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为true(正如您所期望的那样)。

    我认为一些混淆来自于X-to-many映射,您可以在构造函数中初始化实体的虚拟列表(例如,调用myDetails = new List();),因为您没有实例化实体本身。

    顺便提一下,如果任何人在使用从Master到 LIST 的一对多单向地图遇到一些困难,以下内容对我有用:

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

    干杯,罗布