实体框架 4 - AddObject 与 Attach

Posted

技术标签:

【中文标题】实体框架 4 - AddObject 与 Attach【英文标题】:Entity Framework 4 - AddObject vs Attach 【发布时间】:2011-04-24 14:56:14 【问题描述】:

我最近一直在使用 Entity Framework 4,对于何时使用 ObjectSet.Attach 和 ObjectSet.AddObject 有点困惑。

据我了解:

当系统中已存在实体时使用“附加” 创建全新实体时使用“AddObject”

所以,如果我要创建一个新人,我会这样做。

var ctx = new MyEntities();
var newPerson = new Person  Name = "Joe Bloggs" ;
ctx.Persons.AddObject(newPerson);
ctx.SaveChanges();

如果我要修改现有的 Person,我会这样做:

var ctx = new MyEntities();
var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" ;
existingPerson.Name = "Joe Briggs";
ctx.SaveChanges();

请记住,这是一个非常简单的示例。实际上,我使用的是纯 POCO(无代码生成)、存储库模式(不处理 ctx.Persons)和工作单元(不处理 ctx.SaveChanges)。但是“在幕后”,以上是我的实现中发生的事情。

现在,我的问题 - 我还没有找到我必须使用 Attach 的场景。

我在这里缺少什么?我们什么时候需要使用 Attach?

编辑

澄清一下,我正在寻找何时使用 Attach over AddObject(反之亦然)的示例

编辑 2

下面的答案是正确的(我接受了),但我想我会添加另一个 Attach 有用的例子。

在上述修改现有人员的示例中,实际上执行了两个查询。

一个用于检索 Person (.SingleOrDefault),另一个用于执行 UPDATE (.SaveChanges)。

如果(出于某种原因),我已经知道系统中存在“Joe Bloggs”,为什么还要额外查询以先获取他?我可以这样做:

var ctx = new MyEntities();
var existingPerson = new Person  Name = "Joe Bloggs" ;
ctx.Persons.Attach(existingPerson);
ctx.SaveChanges();

这将导致只执行一条 UPDATE 语句。

【问题讨论】:

Attach 也用于 MVC 现在将模型直接放回 EF 的日子。效果很好,节省了大量的代码行。 【参考方案1】:

ObjectContext.AddObjectObjectSet.AddObjectAddObject 方法用于添加在数据库中 存在的新创建的对象。实体将获得一个自动生成的临时 EntityKey 及其 EntityState 将设置为已添加。当调用 SaveChanges 时,EF 将清楚该实体需要插入到数据库中。

ObjectContext.AttachObjectSet.Attach: 另一方面,Attach 用于数据库中已经存在 的实体。而不是设置 EntityState to added, Attach 会产生一个Unchanged EntityState,这意味着它自从附加到上下文后就没有改变。假定您附加的对象存在于数据库中。如果您在附加对象后对其进行修改,则当您调用 SaveChanges 时,EntityKey 的值用于通过在 db 表中查找其匹配的 ID 来更新(或删除)相应的行。 此外,使用 Attach 方法,您可以定义 ObjectContext 中已存在但自动连接的实体之间的关系。基本上 Attach 的主要目的是连接已经附加到 ObjectContext 并且 不是 新的实体,因此您不能使用 Attach 来附加添加了 EntityState 的实体。在这种情况下,您必须使用 Add()。 例如,假设您的 Person 实体有一个名为 Addresses 的导航属性,它是 Address 实体的集合。假设您已经从上下文中读取了两个对象,但它们彼此不相关,并且您希望这样做:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" ;
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.PersonReference.Attach(existingPerson)
ctx.SaveChanges();

【讨论】:

感谢您的回答,我确实了解两者的定义(也就是前两段)。但我不明白我需要使用 Attach 的场景。你的最后一段对我来说真的没有意义(基本上就像前两段的组合),你能给我一个例子,说明我在上面的场景中会在哪里使用“附加”吗?这真的是我要寻找的——例子,而不是定义。不过真的很感谢你的时间。 :) 没问题,我添加了一个代码 sn-p 来澄清最后一段,你可以看到我们有 2 个不相关的对象,并且 Attach 帮助我们将它们相互关联。另一个示例是使用 Attach() 方法将“分离的实体”附加回上下文(有多种原因您可能希望将分离的实体附加回上下文) 是的,我现在明白了。我刚刚观看了一个关于 EF4 的 TechEd 视频(由 Julie Lerman 制作),其中展示了一个示例。您可能有一个您没有从查询中检索到的实体(即它是断开的),但您知道它存在,因此您使用 Attach 对该实体执行更新。有道理,尽管我仍然在努力设想一个您将拥有“断开连接”实体的场景。感谢您的帮助。 太棒了。能否请您将视频的链接分享给可能碰巧看到这篇文章的其他开发者? 上面RPM1984分享的链接坏了,现在重定向到channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DEV205。享受【参考方案2】:

这是一个较晚的响应,但它可能会帮助其他发现此问题的人。

基本上,当您在“使用”范围之外操作实体时,可能会发生“断开连接”的实体。

Employee e = null;

using (var ctx = new MyModelContainer())

     e = ctx.Employees.SingleOrDefault(emp => emp .....);


using (var ctx2 = new MyModelContainer())

     e; // This entity instance is disconnected from ctx2

如果您进入另一个“using”范围,则“e”变量将断开连接,因为它属于之前的“using”范围,并且由于之前的“using”范围被破坏,因此“e”断开连接。

我是这么理解的。

【讨论】:

Tchi 的例子是一个优秀而简单的例子——是的,Employee 变量应该在外面声明。在范围外尝试 e.Address.Street 并看到一个空引用异常弹出。如果您附加,那么应用程序将不必为第二个范围内的员工返回数据库。【参考方案3】:

这是来自Programming Entity Framework: DbContext的引用

对一个实体调用 Remove 不被上下文跟踪将导致 InvalidOperationException 被抛出。这 Entity Framework 抛出这个异常是因为不清楚你的实体是否 正在尝试删除的是应标记为删除的现有实体或新的实体 应该忽略的实体。因此,我们不能只使用 Remove 来标记 断开连接的实体为已删除;我们需要先附加它

private static void TestDeleteDestination()

    Destination canyon;
    using (var context = new BreakAwayContext())
    
        canyon = (from d in context.Destinations
        where d.Name == "Grand Canyon"
        select d).Single();
    
    DeleteDestination(canyon);

private static void DeleteDestination(Destination destination)

    using (var context = new BreakAwayContext())
    
        context.Destinations.Attach(destination);
        context.Destinations.Remove(destination);
        context.SaveChanges();
    

TestDeleteDestination 方法模拟客户端应用程序获取现有的 来自服务器的目标,然后将其传递给 DeleteDestination 方法 服务器。 DeleteDestination 方法使用 Attach 方法让上下文 知道这是一个现有的目的地。然后使用 Remove 方法注册 现有的删除目的地

【讨论】:

【参考方案4】:

我用过这个方法

var user = _context.Users.Attach(new User
     
     Name = "Fahimeh",
     Email = "text@mail.com",
);
 _context.SaveChanges();

 return View(user);

【讨论】:

【参考方案5】:

如果只引用主键而不是附加键呢?

即:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" ;
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.AddressId = myAddress.Id // not -> existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.Person.Id = existingPerson.Id // not -> myAddress.PersonReference.Attach(existingPerson);
ctx.SaveChanges();

【讨论】:

以上是关于实体框架 4 - AddObject 与 Attach的主要内容,如果未能解决你的问题,请参考以下文章

WCF 数据服务 AddObject

将 WCF 服务与实体框架 4 和...DTO 一起使用?

如何自定义与 EDMX(模型优先)实体框架 4.1 一起使用的表名?

使用 MVC 4 将实体框架与 Oracle 11g 连接时面临的问题

实体框架 4:为所有实体创建单个图表是不是有意义?

从 Edmx 构建的预编译实体框架 4 面向 SQL Server Safe 与 SQL CE 一起使用?