关于c#:Entity Framework 5更新记录

Entity Framework 5 Updating a Record

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

我发现了三种方法,我会提到优缺点:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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-加载原始记录,设置更改值

1
2
3
4
5
6
7
var original = db.Users.Find(updatedUser.UserId);

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

赞成的意见

  • 只有修改过的属性才会发送到数据库

欺骗

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

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

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

赞成的意见

  • 1 x要更新的数据库查询

欺骗

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

问题

我的问题是:有没有一个干净的方法可以让我实现这一套目标?

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

我知道这是一个很小的事情指出,但我可能会错过一个简单的解决办法。如果不是方法1,则以方法1为准;-)


您正在寻找:

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


我真的很喜欢这个公认的答案。我相信还有另一种方法可以解决这个问题。假设您有一个非常短的属性列表,您不希望在视图中包含这些属性,因此在更新实体时,这些属性将被忽略。假设这两个字段是密码和ssn。

1
2
3
4
5
6
7
8
9
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();

这个示例允许您在向用户表和视图中添加新字段后,基本上不必使用业务逻辑。


1
2
3
4
5
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();


我在我的知识库基类中添加了一个额外的更新方法,类似于脚手架生成的更新方法。它不是将整个对象设置为"已修改",而是设置一组单独的属性。(t是类的通用参数。)

1
2
3
4
5
6
7
8
9
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;
    }
}

然后打电话,例如:

1
2
3
4
5
6
7
8
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();
}

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
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语句锁定某些记录的并行执行场景外,我总是使用乐观控制),但这种情况很少发生。根据代码和场景,影响可以减少到几乎为零。

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

  • 使用视图模型。模型应该有一个属性updateStatus(未修改的inserted updated deleted)。客户端有责任根据用户操作(插入更新删除)为此列设置正确的值。服务器可以查询数据库中的原始值,或者客户机应该将原始值连同更改的行一起发送给服务器。服务器应该附加原始值,并使用每行的updateStatus列来决定如何处理新值。在这个场景中,我总是使用乐观并发。这只会执行insert-update-delete语句,而不会执行任何选择,但可能需要一些巧妙的代码来遍历图表并更新实体(取决于您的方案-应用程序)。映射器可以帮助但不处理CRUD逻辑

  • 使用一个像breeze.js这样的库来隐藏大部分的复杂性(如1中所述),并尝试将其与您的用例相匹配。

  • 希望它有帮助


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