实体框架 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();
}
var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}
db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

答案

您正在寻找:

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

我真的很喜欢这个公认的答案。我相信还有另一种方法可以解决此问题。假设您有一个很短的属性列表,这些属性是您永远不想包含在 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 添加新字段后,基本上不理会业务逻辑。

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();

我在存储库基类中添加了一个额外的 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();
}

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

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();
    }
}

只是添加到选项列表中。您还可以从数据库中获取对象,并使用自动映射工具(如 “ 自动映射器”)来更新要更改的记录部分。

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

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

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

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

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

希望能帮助到你