885

我一直在探索在 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 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 查询要更新的数据库

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

4

8 回答 8

684

您正在寻找:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
于 2013-03-11T13:12:34.607 回答
176

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

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

于 2013-08-01T21:01:38.323 回答
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();
于 2013-11-12T11:33:31.747 回答
24

我在我的存储库基类中添加了一个额外的更新方法,它类似于由 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();
}

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

于 2015-04-25T16:19:49.580 回答
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();
    }
}
于 2016-03-16T16:04:01.973 回答
3

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

于 2015-12-03T15:58:36.417 回答
3

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

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

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

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

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

希望能帮助到你

于 2016-05-30T12:07:46.630 回答
0

已经给出了一些非常好的答案,但我想投入两分钱。这是将视图对象转换为实体的一种非常简单的方法。简单的想法是只有存在于视图模型中的属性才会被写入实体。这类似于@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 次保存。

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

于 2020-11-05T13:53:34.180 回答