EF 5.0新对象:assign外键属性不设置外键id,或者添加到集合中

时间:2013-01-06 15:09:35

标签: entity-framework-5

EF 5.0,在现有数据库工作流上使用代码优先。 数据库在SalesOrderLine上具有基本的SalesOrder和SalesOrderLine表以及必需的外键,如下所示;

public class SalesOrder
{
    public SalesOrder()
    {
        this.SalesOrderLines = new List<SalesOrderLine>();
    }

    public int SalesOrderID { get; set; }
    public int CustomerID { get; set; }
    public virtual Customer Customer { get; set; }

    public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}

public class SalesOrderLine
{
    public SalesOrderLine()
    {
    }
    public int SalesOrderLineID { get; set; }
    public int SalesOrderID { get; set; }

    public virtual SalesOrder SalesOrder { get; set; }
}
public SalesOrderLineMap()
{
    // Primary Key
    this.HasKey(t => t.SalesOrderLineID);
    // Table & Column Mappings
    this.ToTable("SalesOrderLine");
    this.Property(t => t.SalesOrderLineID).HasColumnName("SalesOrderLineID");
    this.Property(t => t.SalesOrderID).HasColumnName("SalesOrderID");

    // Relationships
    this.HasRequired(t => t.SalesOrder)
        .WithMany(t => t.SalesOrderLines)
        .HasForeignKey(d => d.SalesOrderID);
}

现在根据这个页面: http://msdn.microsoft.com/en-us/data/jj713564

......我们被告知:

  

以下代码通过设置外键来删除关系   为空。请注意,外键属性必须可以为空。

     

course.DepartmentID = null;

     

注意:如果引用处于添加状态(在本例中,为   当然对象),参考导航属性不会   与SaveChanges之前的新对象的键值同步   调用。由于对象上下文不会发生同步   在保存之前,不包含添加对象的永久键。如果   设置完成后,必须将新对象完全同步   关系,请使用以下方法之一。

     

通过将新对象分配给导航属性。以下代码   在课程和部门之间创建一种关系。如果   对象附加到上下文中,课程也被添加到   department.Courses集合,以及相应的外键   课程对象上的属性设置为关键属性值   部。

     

course.Department = department;

......听起来不错!

现在我的问题: 我有以下代码,但两个断言都失败了 - 为什么?

    using (MyContext db = new MyContext ())
    {
        SalesOrder so = db.SalesOrders.First();
        SalesOrderLine sol = db.SalesOrderLines.Create();
        sol.SalesOrder = so;

        Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
        Trace.Assert(so.SalesOrderLines.Contains(sol));
    }

两个对象都附在上下文中 - 不是吗?在此之前我需要做一个SaveChanges()吗?如果是这样,这似乎有点傻,而且当将新对象添加到外键集合时,我需要手动设置对象的所有引用,这是相当烦人的。

- 更新 -

我应该把格特的答案标记为正确,但我对此并不高兴,所以我会等一两天。 ......这就是原因:

以下代码也不起作用:

SalesOrder so = db.SalesOrders.First();
SalesOrderLine sol = db.SalesOrderLines.Create();
db.SalesOrderLines.Add(sol);

sol.SalesOrder = so;
Trace.Assert(so.SalesOrderLines.Contains(sol));

的唯一代码是:

SalesOrder so = db.SalesOrders.First();
SalesOrderLine sol = db.SalesOrderLines.Create();

sol.SalesOrder = so;
db.SalesOrderLines.Add(sol);

Trace.Assert(so.SalesOrderLines.Contains(sol));

...换句话说,您必须先设置所有外键关系,然后调用TYPE.Add(newObjectOfTYPE) 在任何关系和外键字段连接之前。这意味着从完成Create到执行Add()的时间,对象基本上处于半成品状态。我(错误地)认为,因为我使用了Create(),并且因为Create()返回一个子类动态对象(而不是使用返回POCO对象的“new”),所以将处理关系连线我。对我来说也很奇怪,你可以在用 new 运算符创建的对象上调用Add(),它会工作,即使该对象不是一个子类型...

换句话说,这将有效:

    SalesOrder so = db.SalesOrders.First();
    SalesOrderLine sol = new SalesOrderLine();

    sol.SalesOrder = so;
    db.SalesOrderLines.Add(sol);

    Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
    Trace.Assert(so.SalesOrderLines.Contains(sol));

......我的意思是,这很酷,但是这让我很奇怪;使用“Create()”而不是new的重点是什么,如果你想要正确地连接它,你总是要在任何一种情况下添加()对象?

对我来说最烦人的是以下失败;

SalesOrder so = db.SalesOrders.OrderBy(p => p.SalesOrderID).First();
SalesOrderLine sol = db.SalesOrderLines.Create();

sol.SalesOrder = so;
db.SalesOrderLines.Add(sol);

 // NOTE: at this point in time, the SalesOrderId field has indeed been set to the SalesOrderId of the SalesOrder, and the Asserts will pass...
Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
Trace.Assert(so.SalesOrderLines.Contains(sol));

sol.SalesOrder = db.SalesOrders.OrderBy(p => p.SalesOrderID).Skip(5).First();

 // NOTE: at this point in time, the SalesOrderId field is ***STILL*** set to the SalesOrderId of the original SO, so the relationships are not being maintained! 
// The Exception will be thrown!
if (so.SalesOrderID == sol.SalesOrderID)
    throw new Exception("salesorderid not changed");

...这对我来说似乎完全废话,让我感觉像EntityFramework,即使在版本5中,就像是一个米纸桥上的雷区。为什么上面的代码无法在SalesOrder属性的第二个赋值上同步SalesOrderId?我在这里错过了什么基本技巧?

2 个答案:

答案 0 :(得分:3)

我找到了我要找的东西! (并且一路上学到了很多东西)

想到 EF在其动态代理中产生的是“变更追踪代理”。这些代理类的行为更像ADO.Net实体数据模型中旧的EntityObject派生的部分类。

通过对动态生成的代理类进行一些反思(感谢我在本文中发现的信息:http://davedewinter.com/2010/04/08/viewing-generated-proxy-code-in-the-entity-framework/),我看到我的关系属性的“获取”被覆盖以进行延迟加载,但是“set”根本没有被覆盖,所以当然在调用DetectChanges之前没有发生任何事情,而且DetectChanges正在使用“比较快照”方法来检测变化。

进一步的挖掘最终将我带到了这对非常有用的帖子,我推荐给任何使用EF的人: http://blog.oneunicorn.com/2011/12/05/entity-types-supported-by-the-entity-framework/

http://blog.oneunicorn.com/2011/12/05/should-you-use-entity-framework-change-tracking-proxies/

不幸的是,为了让EF生成更改跟踪代理,必须执行以下操作(引自上文):

  
      
  • 您的课程必须遵循的规则才能启用更改跟踪   代理是非常严格和限制性的。这限制了你的方式   定义您的实体并阻止使用私有等内容   物业甚至私人二手。规则是:班级必须是   公开而非密封。 所有媒体资源必须具有公开/受保护   虚拟getter和setter。集合导航属性必须是   声明为ICollection&lt; T&gt;。它们不能是IList&lt; T&gt;,List&lt; T&gt;,   HashSet&lt; T&gt;,依此类推。

  •   
  • 由于规则限制性很强,因此很容易出错,结果就是您无法获得更改跟踪代理。例如,   错过虚拟或内部制定者。

  •   

...他继续提到有关变更跟踪代理的其他信息以及为什么它们可能表现出更好或更差的性能。

在我看来,改变跟踪代理类会很好,因为我来自ADO.Net实体模型世界,而且我已经习惯了这样的工作,但我也有一些相当丰富的东西课程,我不确定我是否能够满足所有标准。另外,第二个要点使我相当紧张(虽然我想我可以创建一个循环遍历我所有实体的单元测试,执行Create(每个都为0然后测试IEntityWithChangeTracker接口的结果对象)。

通过在我的原始示例中将所有属性设置为虚拟,我确实得到了IEntityWithChangeTracker类型的代理类,但我感觉有点......我不知道......“脏”......使用它们,所以我想我只需要把它搞砸并记得在做作业时总是设置我的人际关系的两个方面。

无论如何,谢谢你的帮助!

干杯, 克里斯

答案 1 :(得分:1)

不,SalesOrderLine sol未附加到上下文(尽管它是由DbSet创建的)。你必须

db.SalesOrderLines.Add(sol);

ChangeTracker执行DetectChanges() DbSet.Add()sol.SalesOrderID是触发此方法的方法之一)的方式将其附加到上下文中,因此也执行 relationship fixup ,它设置so.SalesOrderLines并确保SaveChanges()包含新对象。

所以,不,你不需要执行{{1}},但必须将对象添加到上下文中,并且必须触发关系修正