如何使用Entity Framework仅更新一个字段?

h3n*_*h3n 180 sql entity field entity-framework-4 dbcontext

这是桌子

用户

UserId
UserName
Password
EmailAddress
Run Code Online (Sandbox Code Playgroud)

和代码..

public void ChangePassword(int userId, string password){
//code to update the password..
}
Run Code Online (Sandbox Code Playgroud)

Stu*_*art 348

Ladislav的答案更新为使用DbContext(在EF 4.1中引入):

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我只能通过添加db.Configuration.ValidateOnSaveEnabled = false来使这段代码工作; 在db.SaveChanges()之前? (54认同)
  • 我认为值得一提的是,如果您使用`db.Configuration.ValidateOnSaveEnabled = false;`您可能希望继续验证要更新的字段:`if(db.Entry(user).Property(x => x.Password) ).GetValidationErrors().Count == 0)` (9认同)
  • 要包含哪个命名空间以使用`db.Entry(user).Property(x => x.Password).IsModified = true;`而不是`db.Entry(user).Property("Password").IsModified = true; ` (3认同)
  • 当表具有时间戳字段时,此方法抛出OptimisticConcurencyException. (2认同)
  • 如果表中包含您在更新期间未提供的必填字段,则需要将ValidateOnSaveEnabled设置为false (2认同)
  • @dandax设置db.Entry(user).State = EntityState.Unchanged,然后为我要更新的属性设置IsModified对我来说不起作用,仍然在另一个属性上收到验证错误。 (2认同)

Lad*_*nka 55

您可以告诉EF哪些属性必须以这种方式更新:

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}
Run Code Online (Sandbox Code Playgroud)


Ami*_*adi 40

新的 EF Core 7 本机功能 \xe2\x80\x94 ExecuteUpdate

\n

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

\n

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

\n

用法:

\n

让我们以 OP\xe2\x80\x94 为例,即更新特定用户的密码列:

\n
dbContext.Users\n    .Where(u => u.Id == someId)\n    .ExecuteUpdate(b =>\n        b.SetProperty(u => u.Password, "NewPassword")\n    );\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,调用ExecuteUpdate需要您调用该SetProperty方法,指定要更新哪个属性,以及要为其分配什么新值。

\n

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

\n
UPDATE [u]\n    SET [u].[Password] = "NewPassword"\nFROM [Users] AS [u]\nWHERE [u].[Id] = someId\n
Run Code Online (Sandbox Code Playgroud)\n

另外,ExecuteDelete对于删除行:

\n

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

\n

用法:

\n
// Delete users that haven\'t been active in 2022:\ndbContext.Users\n    .Where(u => u.LastActiveAt.Year < 2022)\n    .ExecuteDelete();\n
Run Code Online (Sandbox Code Playgroud)\n

与 类似ExecuteUpdate,会在后台ExecuteDelete生成SQL 语句 \xe2\x80\x94 在本例中,如下所示:DELETE

\n
DELETE FROM [u]\nFROM [Users] AS [u]\nWHERE DATEPART(year, [u].[LastActiveAt]) < 2022\n
Run Code Online (Sandbox Code Playgroud)\n
\n

其他注意事项:

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


mar*_*c_s 16

你基本上有两个选择:

  • 一路走EF方式,在这种情况下,你会的
    • 根据userId提供的内容加载对象 - 整个对象被加载
    • 更新password字段
    • 使用上下文的.SaveChanges()方法保存对象

在这种情况下,由EF如何详细处理.我只测试了这个,在这种情况下我只更改一个对象的单个字段,EF创建的几乎就是你手动创建的东西 - 例如:

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Run Code Online (Sandbox Code Playgroud)

因此,EF非常聪明,可以确定哪些列确实已更改,并且它将创建一个T-SQL语句来处理实际上必要的更新.

  • 你可以在T-SQL代码中定义一个完全符合你需要的存储过程(只需更新Password给定的列,UserId而不是其他任何内容 - 基本上执行UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId),然后在EF模型中为该存储过程创建一个函数导入,并调用它功能而不是执行上述步骤

  • 介意更多关于为什么你“没有”加载它的解释吗?是怎么做到的? (2认同)

gro*_*ava 11

我正在使用这个:

实体:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

的DbContext:

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

访问代码:

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)


Edw*_*rey 9

在Entity Framework Core中,Attach返回条目,因此您只需要:

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)


Dok*_*-so 8

在寻找这个问题的解决方案时,我通过Patrick Desjardins的博客找到了GONeale答案的变体:

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}
Run Code Online (Sandbox Code Playgroud)

" 正如您所看到的,它将第二个参数作为函数的表达式.这将通过在Lambda表达式中指定要更新的属性来使用此方法. "

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
Run Code Online (Sandbox Code Playgroud)

(这里也给出了一个类似的解决方案:https://stackoverflow.com/a/5749469/2115384)

我目前在我自己的代码中使用的方法,扩展为处理(Linq)类型的表达式ExpressionType.Convert.在我的情况下这是必要的,例如with Guid和其他对象属性.那些被"包裹"在Convert()中,因此不会被处理System.Web.Mvc.ExpressionHelper.GetExpressionText.

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression bodyExpression = property.Body;
        if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
        {
            Expression operand = ((UnaryExpression)property.Body).Operand;
            propertyName = ((MemberExpression)operand).Member.Name;
        }
        else
        {
            propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)


GON*_*ale 6

我在这里比赛迟到了,但这就是我这样做的原因,我花了一些时间寻找一个我满意的解决方案; 这只会UPDATE为更改的字段生成一个语句,因为您通过"白名单"概念明确定义它们是什么,这样可以更安全地防止Web表单注入.

我的ISession数据存储库的摘录:

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以将其包装在try..catch中,但我个人希望我的调用者知道此场景中的异常.

它将以类似这种方式调用(对我来说,这是通过ASP.NET Web API):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
Run Code Online (Sandbox Code Playgroud)

  • @GONeale - 刚刚经过.有人[使用Lambdas破解它!](http://stackoverflow.com/a/5749469/609176) (3认同)
  • 所以你的更好的解决方案是Elisa?您应该明确说明允许更新的属性(就像ASP.NET MVC的`UpdateModel`命令所需的白名单一样),这样就可以确保不会发生黑客表单注入,并且它们无法更新不允许更新的字段.但是,如果有人可以将字符串数组转换为某种lambda表达式参数并在"Update <T>"中使用它,那么很棒 (2认同)

Bah*_*ere 6

实体框架跟踪您通过 DbContext 从数据库查询的对象的更改。例如,如果您的 DbContext 实例名称是 dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*xei 6

在 EntityFramework Core 2.x 中,不需要Attach

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

在 SQL Server 中尝试了这个并对其进行了分析:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1
Run Code Online (Sandbox Code Playgroud)

Find 确保已加载的实体不会触发 SELECT 并在需要时自动附加实体(来自文档):

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.
Run Code Online (Sandbox Code Playgroud)