3

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

...我们被告知:

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

course.DepartmentID = null;

注意:如果引用处于添加状态(在本例中为课程对象),则在调用 SaveChanges 之前,引用导航属性不会与新对象的键值同步。不会发生同步,因为对象上下文不包含添加对象的永久键,直到它们被保存。如果您必须在设置关系后立即使新对象完全同步,请使用以下方法之一。

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

course.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?我在这里缺少什么重要技巧?

4

2 回答 2

3

我找到了我要找的东西!(一路上学到了不少东西)

认为EF 在其动态代理中生成的是“更改跟踪代理”。这些代理类的行为更像是从 ADO.Net 实体数据模型中派生的旧 EntityObject 部分类。

通过对动态生成的代理类进行一些反思(感谢我在这篇文章中找到的信息:http: //davedewinter.com/2010/04/08/viewing-generated-proxy-code-in-the-entity-framework / ),我看到我的关系属性的“get”被覆盖以执行延迟加载,但“set”根本没有被覆盖,所以当然在调用 DetectChanges 之前什么都没有发生,并且 DetectChanges 正在使用“与快照比较”检测更改的方法。

进一步挖掘最终将我引向了这对内容丰富的帖子,我向使用 EF 的任何人推荐它们:http: //blog.oneunicorn.com/2011/12/05/entity-types-supported-by-the-entity-框架/

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

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

  • 您的类必须遵循以启用更改跟踪代理的规则非常严格和限制性。这限制了您定义实体的方式,并阻止使用私有属性甚至私有 setter 之类的东西。规则是: 类必须是公开的而不是密封的。所有属性都必须具有公共/受保护的虚拟 getter 和 setter。集合导航属性必须声明为 ICollection<T>。它们不能是 IList<T>、List<T>、HashSet<T> 等。

  • 因为规则非常严格,很容易出错,结果是您不会获得更改跟踪代理。例如,缺少一个虚拟,或者将一个 setter 设置为内部。

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

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

通过在我的原始示例中将我的所有属性设置为虚拟,我确实得到了 IEntityWithChangeTracker 类型的代理类,但我感觉有点......我不知道......“脏”......因为使用它们,所以我认为我只需要接受它,并记住在做作业时始终设置我的关系的双方。

无论如何,感谢您的帮助!

干杯,克里斯

于 2013-01-07T19:24:40.860 回答
1

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

db.SalesOrderLines.Add(sol);

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

所以,不,您不需要执行SaveChanges(),但必须将对象添加到上下文中并且必须已触发关系修复。

于 2013-01-06T19:06:56.957 回答