Entity Framework 5 更新记录

Posted

技术标签:

【中文标题】Entity Framework 5 更新记录【英文标题】:Entity Framework 5 Updating a Record 【发布时间】:2013-02-26 11:47:53 【问题描述】:

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

我找到了三种方法,我会提到它们的优缺点:

方法一——加载原始记录,更新每个属性

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 x 数据库查询以加载原始然后更新它

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

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

if (original != null)

    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();

优点

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

缺点

视图需要包含所有属性 2 x 数据库查询以加载原始然后更新它

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

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

优点

1 x 查询要更新的数据库

缺点

无法指定更改哪些属性 视图必须包含所有属性

问题

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

可以指定更改哪些属性 视图不需要包含所有属性(例如密码!) 1 x 查询要更新的数据库

我知道要指出这是一件很小的事情,但我可能缺少一个简单的解决方案。如果不是方法一将占上风;-)

【问题讨论】:

使用 ViewModels 和一个好的映射引擎?您只会获得“要更新的属性”来填充您的视图(然后进行更新)。仍然会有 2 个更新查询(获取原始信息 + 更新它),但我不会称其为“骗局”。如果这是您唯一的性能问题,那么您就是一个快乐的人;) 感谢@RaphaëlAlthaus,非常有效的观点。我可以这样做,但我必须为许多表创建 CRUD 操作,所以我正在寻找一种可以直接与模型一起使用的方法,以节省我为每个模型创建 n-1 个 ViewModel。 嗯,在我当前的项目(也包括许多实体)中,我们开始使用模型,认为我们会浪费时间使用 ViewModel。我们现在将使用 ViewModels,并且在开始时(不可忽略)基础设施工作,现在它变得更加清晰且易于维护。而且更安全(无需担心恶意的“隐藏字段”或类似的东西) 不再有(糟糕的)ViewBags 来填充您的 DropDownLists(我们几乎所有的 CRU(D) 视图中都至少有一个 DropDownList...) 我认为你是对的,我试图忽略 ViewModel 很糟糕。是的,ViewBag 有时看起来有点脏。我通常会根据Dino Esposito's 博客更进一步,并创建 InputModels,带一点皮带和大括号,但效果很好。只是意味着每个模型有 2 个额外的模型 - doh ;-) 【参考方案1】:

已经给出了一些非常好的答案,但我想投入两分钱。这是将视图对象转换为实体的一种非常简单的方法。简单的想法是只有存在于视图模型中的属性才会被写入实体。这类似于@Anik Islam Abhi 的答案,但传播为零。

public static T MapVMUpdate<T>(object updatedVM, T original)

    PropertyInfo[] originalProps = original.GetType().GetProperties();
    PropertyInfo[] vmProps = updatedVM.GetType().GetProperties();
    foreach (PropertyInfo prop in vmProps)
    
        PropertyInfo projectProp = originalProps.FirstOrDefault(x => x.Name == prop.Name);
        if (projectProp != null)
        
            projectProp.SetValue(original, prop.GetValue(updatedVM));
        
    
    return original;

优点

视图不需要具有实体的所有属性。 向视图添加删除属性时,您无需更新代码。 完全通用

缺点

2 次点击数据库,1 次加载原始实体,1 次保存。

对我来说,这种方法的简单性和低维护要求胜过增加的数据库调用。

【讨论】:

【参考方案2】:

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

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

对于客户端场景,您有几个选择

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

    使用像微风.js 这样隐藏大部分复杂性的库(如 1 中所述)并尝试使其适合您的用例。

希望对你有帮助

【讨论】:

【参考方案3】:
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; 这控制了更新语句的set部分。【参考方案4】:

我在我的存储库基类中添加了一个额外的更新方法,它类似于由 Scaffolding 生成的更新方法。它没有将整个对象设置为“已修改”,而是设置了一组单独的属性。 (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();

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

【讨论】:

啊哈......视图模型的单独项目和使用视图模型的存储库的单独项目。 真的很喜欢这种做法。仍然明确,但更容易。【参考方案5】:

只是添加到选项列表中。您还可以从数据库中抓取对象,并使用 Auto Mapper 等自动映射工具来更新您想要更改的记录部分。..

【讨论】:

【参考方案6】:
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 项目符号 - 是否有任何理由没有更多投票? 它没有。它具有最大的“缺点”之一,对数据库的打击不止一次。您仍然需要使用此答案加载原件。 @smd 为什么说它多次访问数据库?除非使用 SetValues() 有这种效果,否则我看不到这种情况发生,但这似乎不是真的。 @parliament 我想我写这篇文章的时候一定是睡着了。道歉。实际问题是覆盖预期的空值。如果更新后的用户不再引用某些内容,那么如果您打算清除它,则将其替换为原始值是不对的。【参考方案7】:

我真的很喜欢接受的答案。我相信还有另一种方法可以解决这个问题。假设您有一个非常短的属性列表,您不想在视图中包含这些属性,因此在更新实体时,这些属性将被省略。假设这两个字段是密码和 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();   

此示例允许您在将新字段添加到您的用户表和视图之后基本上不理会业务逻辑。

【讨论】:

如果我没有为 SSN 属性指定值,我仍然会收到错误消息,即使我将 IsModified 设置为 false,它仍然会根据模型规则验证属性。因此,如果该属性被标记为 NOT NULL,如果我没有设置任何不同于 null 的值,它将失败。 您不会收到错误消息,因为这些字段不会出现在您的表单中。您省略了您绝对不会更新的字段,使用通过附加返回的表单从数据库中获取条目,并告诉条目这些字段没有被修改。模型验证在 ModelState 中控制,而不是在上下文中。此示例引用现有用户,因此是“updatedUser”。如果您的 SSN 是必填字段,那么它在首次创建时就已存在。 如果我理解正确,“updatedUser”是一个已经填充了 FirstOrDefault() 或类似的对象的实例,所以我只更新我更改的属性并将其他属性设置为 ISModified=false。这工作正常。但是,我想要做的是更新一个对象而不先填充它,而不是在更新之前进行任何 FirstOrDefault() 。如果我没有为所有需要的字段指定值,即使我在这些属性上设置了 ISModified = false,也会在这种情况下收到错误消息。 entry.Property(e => e.columnA).IsModified = false;没有这一行 ColumnA 将失败。 您所描述的是创建一个新实体。这仅适用于更新。 RolandoCC,把 db.Configuration.ValidateOnSaveEnabled = false;在 db.SaveChanges(); 之前【参考方案8】:

您正在寻找:

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

【讨论】:

嗨@Ladislav Mrnka,如果我想一次更新所有属性,我可以使用下面的代码吗? db.Departments.Attach(部门); db.Entry(部门).State = EntityState.Modified; db.SaveChanges(); 这种方法的一个问题是不能模拟 db.Entry(),这是一个严重的 PITA。 EF 在其他地方有一个相当不错的嘲弄故事 - (据我所知)他们这里没有,这很烦人。 @Foysal 单独执行 context.Entry(entity).State = EntityState.Modified 就足够了,无需附加。它将在修改后自动附加... @Sandman4,这意味着所有其他属性都需要存在并设置为当前值。在某些应用程序设计中,这是不可行的。 “EF 有一个相当不错的嘲讽故事” - 为什么现在的一切都必须是一个故事?

以上是关于Entity Framework 5 更新记录的主要内容,如果未能解决你的问题,请参考以下文章

通过 ViewModel 使用 Entity Framework 更新记录的方法

更新现有记录时出现 Entity Framework Core 错误

Entity Framework 5/6 中的可更新视图

如何使用 Entity Framework ASP.Net MVC 5 删除多条记录? [关闭]

无法使用 Entity Framework 5 在 SQLite 数据库中插入记录

Entity Framework Core 中的日志记录与拦截器