实体框架5更新记录


870

我一直在探索在ASP.NET MVC3环境中在Entity Framework 5中编辑/更新记录的不同方法,但是到目前为止,它们都没有打勾我需要的所有框。我会解释原因。

我发现了三种方法的优缺点:

方法1-加载原始记录,更新每个属性

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

优点

  • 可以指定要更改的属性
  • 视图不需要包含每个属性

缺点

  • 在数据库上进行2次查询以加载原始文件,然后对其进行更新

方法2-加载原始记录,设置更改的值

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

优点

  • 仅将修改后的属性发送到数据库

缺点

  • 视图需要包含每个属性
  • 在数据库上进行2次查询以加载原始文件,然后对其进行更新

方法3-附加更新的记录并将状态设置为EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

优点

  • 1 x查询数据库以更新

缺点

  • 无法指定要更改的属性
  • 视图必须包含每个属性

我对你们的问题;有没有一种干净的方法可以实现这组目标?

  • 可以指定要更改的属性
  • 视图不需要包含每个属性(例如密码!)。
  • 1 x查询数据库以更新

我知道这是一件很不重要的事情,但是我可能缺少一个简单的解决方案。如果不是,则以一种方法为准;-)


13
使用ViewModels和良好的映射引擎?您只能获得“要更新的属性”来填充视图(然后进行更新)。仍然会有2个更新查询(获取原始+更新),但是我不会将其称为“骗局”。如果那是您唯一的表现问题,那么您就是个快乐的人;)
RaphaëlAlthaus

感谢@RaphaëlAlthaus,非常有效的观点。我可以这样做,但是我必须为多个表创建CRUD操作,因此我正在寻找一种可以直接与模型一起使用的方法,以免我为每个模型创建n-1个ViewModel。
Stokedout,2013年

3
好吧,在我当前的项目(也包括许多实体)中,我们开始研究模型,认为我们将浪费时间使用ViewModels。现在,我们要使用ViewModels,并且在开始时(不容忽视)基础架构的工作,现在它变得越来越远,越来越清晰,更容易维护。更安全(无需担心会有恶意的“隐藏域”或类似的东西)
拉斐尔奥尔索斯

1
并没有更多的(可怕)ViewBags来填充DropDownLists(我们至少有一个DropDownList中的几乎所有的CRU(d)的意见...)
拉斐尔奥尔索斯

我认为您是对的,这对尝试忽略ViewModels不利。是的,有时ViewBag似乎有点脏。通常,按照Dino Esposito的博客,我会更进一步,并且也创建InputModels,有点细的皮带和牙套,但是效果很好。只是意味着每个模型额外增加2个模型
-doh

Answers:


681

您正在寻找:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();

59
嗨@Ladislav Mrnka,如果我想一次更新所有属性,可以使用下面的代码吗?db.Departments.Attach(部门); db.Entry(department).State = EntityState.Modified; db.SaveChanges();
Foyzul Karim 2013年

23
@Foysal:是的,可以。
Ladislav Mrnka

5
这种方法的问题之一是您无法模拟db.Entry(),这是一个严重的PITA。EF在其他地方有一个相当不错的嘲讽故事-很烦人的(据我所知)他们在这里没有一个。
肯·史密斯,

23
@Foysal做context.Entry(entity).State = EntityState。单独修改就足够了,不需要进行附加。它将作为修改后的附件自动附加...
HelloWorld 2013年

4
@ Sandman4,这意味着所有其他属性都必须存在并设置为当前值。在某些应用程序设计中,这是不可行的。
Dan Esparza 2014年

176

我真的很喜欢这个公认的答案。我相信还有另一种方法可以解决此问题。假设您有一个很短的属性列表,这些属性是您永远不想包含在View中的,因此在更新实体时,这些属性将被省略。假设这两个字段是“密码”和“ SSN”。

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

此示例使您在向Users表和View添加新字段后,基本上无需考虑业务逻辑。


如果我没有为SSN属性指定值,即使我将IsModified设置为false,仍然会根据模型规则验证该属性,但仍然会收到错误消息。因此,如果将该属性标记为NOT NULL,那么如果我不设置任何不同于null的值,它将失败。
RolandoCC 2014年

您不会收到错误消息,因为这些字段将不在您的表单中。您省去了绝对不会更新的字段,使用附加的传递回的表单从数据库中获取条目,并告诉条目这些字段没有被修改。模型验证是在ModelState中而不是在上下文中控制的。本示例引用现有用户,因此引用“ updatedUser”。如果您的SSN是必填字段,那么它在首次创建时就已经存在。
smd 2014年

4
如果我正确理解,则“ updatedUser”是已经填充有FirstOrDefault()或类似内容的对象的实例,因此我仅更新更改的属性,并将其他属性设置为ISModified = false。这很好。但是,我想做的是在不先填充对象的情况下更新对象,而不在更新之前进行任何FirstOrDefault()的操作。如果我没有为所有必填字段指定值,即使我在这些属性上将ISModified = false设置为,也将收到错误消息。entry.Property(e => e.columnA).IsModified = false; 没有这一行,ColumnA将失败。
RolandoCC

您正在描述的是创建一个新实体。这仅适用于更新。
2014年

1
RolandoCC,将db.Configuration.ValidateOnSaveEnabled = false; 在db.SaveChanges()之前;
威尔基

28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();

这似乎是一个非常不错的解决方案-无需大惊小怪。您不必手动指定属性,它会考虑所有OP的项目符号-是否有任何理由没有更多的选票?
nocarrier

虽然没有。它具有最大的“缺点”之一,对数据库的影响不只一个。您仍然必须使用此答案加载原件。
smd

1
@smd为什么您说它多次击中数据库?我不认为会发生这种情况,除非使用SetValues()具有这种效果,但这似乎并不正确。
议会

@议会我想我写那本书的时候一定已经睡着了。道歉。实际的问题是覆盖预期的空值。如果更新后的用户不再引用某项内容,那么如果要清除它,则用原始值替换它是不合适的。
2015年

22

我在存储库基类中添加了一个额外的update方法,该方法类似于Scaffolding生成的update方法。而不是将整个对象设置为“修改”,而是设置了一组单独的属性。(T是类的通用参数。)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

然后打电话,例如:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

我喜欢一趟数据库。不过,最好使用视图模型来执行此操作,以避免重复属性集。我还没有这样做,因为我不知道如何避免将视图模型验证器上的验证消息带入我的域项目中。


啊...用于视图模型的单独项目和用于与视图模型一起使用的存储库的单独项目。
伊恩·沃伯顿

11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}

为什么不只是DbContext.Attach(obj); DbContext.Entry(obj).State = EntityState.Modified;
nelsontruran

这控制了set更新语句的一部分。
Tanveer Badar


3

根据您的用例,以上所有解决方案均适用。这是我通常这样做的方式:

对于服务器端代码(例如批处理),我通常加载实体并使用动态代理。通常,在批处理过程中,您需要在服务运行时始终加载数据。我尝试批量加载数据,而不是使用find方法来节省一些时间。根据不同的过程,我使用乐观或悲观的并发控制(除了并行执行方案(我需要使用普通sql语句锁定某些记录的情况之外,我总是使用乐观的并发控制,尽管这种情况很少见))。根据代码和方案的不同,影响可以减少到几乎为零。

对于客户端方案,您有几种选择

  1. 使用视图模型。这些模型应具有属性UpdateStatus(unmodified-inserted-updated-deleted)。客户有责任根据用户操作(插入-更新-删除)在此列中设置正确的值。服务器可以向数据库查询原始值,或者客户端应将原始值和更改的行一起发送到服务器。服务器应附加原始值,并在每一行中使用UpdateStatus列来决定如何处理新值。在这种情况下,我总是使用乐观并发。这只会执行插入-更新-删除语句,而不执行任何选择,但是可能需要一些聪明的代码来遍历图并更新实体(取决于您的方案-应用程序)。映射器可以帮助但不能处理CRUD逻辑

  2. 使用像breeze.js这样的库来隐藏大多数这种复杂性(如1中所述),并尝试使其适合您的用例。

希望能帮助到你

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.