当我在一个分离的场景中,从客户端获取一个dto,并将其映射到一个实体中以保存它时,我会这样做:
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
那么,什么是DbSet.Attach(entity)或者,当EntityState.Modified已经附加了实体时,我为什么要使用.Attach方法?
发布于 2015-06-23 03:24:42
当你这样做的时候context.Entry(entity).State = EntityState.Modified;
另一方面, DbSet.Attach(entity) 将实体附加到上下文而不将其标记为脏。相当于做了context.Entry(entity).State = EntityState.Unchanged;以这种方式附加时,除非您随后继续更新实体上的属性,否则下次您调用 context.SaveChanges() 时,EF 将不会为此实体生成数据库更新。即使您打算更新实体,如果实体有很多属性(db 列)但您只想更新其中的几个,您可能会发现执行 DbSet.Attach(entity) 是有利的,并且然后只更新需要更新的几个属性。这样做会从 EF 生成更有效的更新语句。EF 只会更新您修改的属性(与 context.Entry(entity).State = EntityState.Modified; 相反,这将导致所有属性/列被更新)
相关文档:Add/Attach and Entity States.
添加/附加和实体状态
代码示例
假设您有以下实体:
public class Person
{
public int Id { get; set; } // primary key
public string FirstName { get; set; }
public string LastName { get; set; }
}
如果您的代码如下所示:
context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();
生成的SQL将如下所示:
UPDATE person
SET FirstName = 'whatever first name is',
LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.
请注意上面的update语句将如何更新所有列,无论您是否实际更改了这些值。
相反,如果您的代码使用“正常”附加,如下所示:
context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();
那么生成的update语句就不同了:
UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.
如您所见,update语句仅限更新在将实体附加到上下文后实际更改的值。根据表的结构,这可能会产生积极的性能影响。
现在,哪个选项对你更好完全取决于你想做什么。
发布于 2020-05-02 08:50:35
除了(标记的答案)之外, context.Entry(entity).State = EntityState.Unchanged 和 context.Attach(entity) (在 EF Core 中)之间还有一个重要区别:我自己做了一些测试以更深入地了解它(因此这也包括一些通用的参考测试),所以这是我的测试场景:我使用了 EF Core 3.1.3
我使用了QueryTrackingBehavior.NoTracking
我只使用属性进行映射(见下文)
我使用不同的上下文来获取订单并更新订单
我每次测试都擦掉了整个数据库
以下是模型:
public class Order
{
public int Id { get; set; }
public string Comment { get; set; }
public string ShippingAddress { get; set; }
public DateTime? OrderDate { get; set; }
public List OrderPositions { get; set; }
[ForeignKey("OrderedByUserId")]
public User OrderedByUser { get; set; }
public int? OrderedByUserId { get; set; }
}
public class OrderPos
{
public int Id { get; set; }
public string ArticleNo { get; set; }
public int Quantity { get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
public int? OrderId { get; set; }
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
这是数据库中的(原始)测试数据:
要获取订单,请执行以下操作:
order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();
现在测试:
简单更新
EntityState
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
简单更新
附加
db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1
使用更改子Ids进行更新
EntityState
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.Id = 3; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
使用更改子Ids进行更新
附加
db.Attach(order);
order.ShippingAddress = "Germany"; // would be UPDATED
order.OrderedByUser.Id = 3; // will throw EXCEPTION
order.OrderedByUser.FirstName = "William (CHANGED)"; // would be UPDATED
order.OrderPositions[0].Id = 3; // will throw EXCEPTION
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // would be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // would be INSERTED
db.SaveChanges();
// Throws Exception: The property 'Id' on entity type 'User' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.)
注意:这抛出异常,无论Id是否被更改或被设置为原始值,看起来Id的状态被设置为" changed“,这是不允许的(因为它是主键)
使用将子Ids更改为新的方式进行更新( EntityState和Attach之间没有区别):
db.Attach(order); // or db.Entry(order).State = EntityState.Unchanged;
order.OrderedByUser = new User();
order.OrderedByUser.Id = 3; // // Reference will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on User 3)
db.SaveChanges();
// Will generate SQL in 2 Calls:
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 3
注:查看与使用EntityState进行更新而不使用new的区别(如上)。这一次,由于新的用户实例,名称将被更新。
通过更改引用Ids进行更新
EntityState
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.Id = 2; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
通过更改引用Ids进行更新
附加
db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on FIRST User!)
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1
注意:引用将更改为User 3,但是还有order.OrderedByUser.Id用户%1将被更新,我猜这是因为
是不变的(它仍然是1)。
结论
使用EntityState,您拥有更多的控制权,但您必须自己更新子属性(第二级)。使用Attach可以更新所有内容(我猜是所有级别的属性),但您必须关注引用。例如:如果User (OrderedByUser)是一个dropDown,那么通过dropDown更改该值可能会覆盖整个User-object。在这种情况下,原始的dropDown-Value将被覆盖,而不是引用。
对我来说,最好的情况是将像OrderedByUser这样的对象设置为null,如果我只想更改引用(无论是EntityState还是Attach),则只将order.OrderedByUserId设置为新值。
希望这能有所帮助,我知道这是一大堆文本:D
发布于 2017-09-15 15:27:39
当您使用 DbSet.Update 方法时,实体框架将您的实体的所有属性标记为 EntityState.Modified,因此会对其进行跟踪。如果您只想更改某些属性,而不是所有属性,请使用 DbSet.Attach。此方法使您的所有属性都为 EntityState.Unchanged,因此您必须使您要更新的属性为 EntityState.Modified。因此,当应用程序命中 DbContext.SaveChanges 时,它只会操作修改后的属性。
https://stackoverflow.com/questions/30987806
复制相似问题