Entity Framework 5更新记录

我一直在探索在ASP.NETMVC3环境中在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。

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

优点

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

缺点

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

问题

我的问题是:有没有一种干净的方法可以实现这一系列目标?

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

我知道这是一个很小的问题,但我可能会错过一个简单的解决方案。如果不是方法,一个将占上风;-)

452814 次浏览

您正在寻找:

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

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

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

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

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

我喜欢一次数据库之旅。不过,为了避免重复属性集,视图模型可能更好。我还没有这样做,因为我不知道如何避免将视图模型验证器上的验证消息带入我的域项目。

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

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

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

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

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

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

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

希望有帮助

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

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次命中数据库,一次加载原始实体,一次保存它。

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

EF Core 7.0新功能:ExecuteUpdate

终于!经过漫长的等待,EF Core 7.0现在有了原生支持的方式来运行UPDATE(以及DELETE)语句,同时还允许您使用任意LINQ查询(.Where(u => ...)),而无需首先从数据库中检索相关实体:名为ExecuteUpdate的新内置方法-请参阅“EF Core 7.0有什么新功能?”

ExecuteUpdate正是针对这些场景,它可以对任何IQueryable实例进行操作,并允许您更新任意数量的行上的特定列,同时始终在幕后发布单一UPDATE语句,使其尽可能高效。

用法:

假设您想更新特定用户的电子邮件和显示名称:

dbContext.Users.Where(u => u.Id == someId).ExecuteUpdate(b => b.SetProperty(u => u.Email, "NewEmail@gmail.com").SetProperty(u => u.DisplayName, "New Display Name"));

如您所见,ExecuteUpdate要求您对SetProperty方法进行一次或多次调用,以指定要更新的属性以及要为其分配的新值。

EF Core会将其转换为以下UPDATE语句:

UPDATE [u]SET [u].[Email] = "NewEmail@gmail.com",[u].[DisplayName] = "New Display Name"FROM [Users] AS [u]WHERE [u].[Id] = someId

此外,ExecuteDelete用于删除行:

还有一个与ExecuteUpdate对应的称为ExecuteDelete,顾名思义,它可用于一次删除单行或多行,而无需首先获取它们。

用法:

// Delete users that haven't been active in 2022:dbContext.Users.Where(u => u.LastActiveAt.Year < 2022).ExecuteDelete();

ExecuteUpdate类似,ExecuteDelete将在幕后生成DELETESQL语句-在这种情况下,如下所示:

DELETE FROM [u]FROM [Users] AS [u]WHERE DATEPART(year, [u].[LastActiveAt]) < 2022

其他备注:

  • 请记住,ExecuteUpdateExecuteDelete都是“终止”,这意味着更新/删除操作将在您调用该方法后立即发生。你不应该在之后调用dbContext.SaveChanges()
  • 如果您对SetProperty方法感到好奇,并且您对为什么ExectueUpdate没有收到成员初始化表达式(例如.ExecuteUpdate(new User { Email = "..." }))感到困惑,那么请参阅GitHub问题上的这一评论(以及周围的)以了解此功能。
  • 此外,如果您对命名背后的基本原理以及为什么选择前缀Execute(还有其他候选者)感到好奇,请参阅这一评论和前面(相当长)的对话。
  • 这两种方法也有async等价物,分别命名为ExecuteUpdateAsyncExecuteDeleteAsync