问题摘要:我有一个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);
}
(这是更复杂的实体集的关键点。我简单地将其简化为最简单的情况,我不能简单地用复杂类型替换细节)。
干杯, 罗布
答案 0 :(得分:2)
我认为是因为当你第一次重新加载 Master 时,你没有急于加载细节,所以细节实体不会在实体框架“图形”(内部存储器)中。图表中唯一的内容是单个 Master 实体。
但是,当您查询 明细(“让我们打个招呼”)时,会将其加载到图表中并根据FK关联解析参考,因此您的最终测试通过, Master 现在与详细信息相关。
我可能错了 - 但这听起来像是这样。
您是否启用了延迟加载?如果没有,您需要急切加载所需的关系。
而不是:
var masterReloaded = contextReloaded.Masters.First();
试试这个:
var masterReloaded = contextReloaded.Masters.Include(x => x.Detail).First();
答案 1 :(得分:0)
Matt Hamilton是对的(见上文)。问题是:
以下遗嘱通过(如预期/希望)
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");
});
干杯,罗布