如何使用entity framework只更新一个字段?

这是桌子

用户

UserId UserName Password EmailAddress 

和代码..

 public void ChangePassword(int userId, string password){ //code to update the password.. } 

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

您可以通过这种方式告诉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(); } } 

你基本上有两个select:

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

在这种情况下,EF如何处理这个细节。 我只是testing了这一点,在这种情况下,我只改变了一个对象的单个字段,EF创build的东西也是你手动创build的东西,比如:

 `UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId` 

因此,EF足够聪明,能够确定哪些列确实发生了变化,并且会创build一个T-SQL语句来处理实际上必需的更新。

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

我使用这个:

实体:

 public class Thing { [Key] public int Id { get; set; } public string Info { get; set; } public string OtherStuff { get; set; } } 

的DbContext:

 public class MyDataContext : DbContext { public DbSet<Thing > Things { get; set; } } 

访问者代码:

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

在寻找解决scheme时,我通过Patrick Desjardins的博客find了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(); } 

正如你所看到的,它将第二个参数作为一个函数的expression式,这将通过在Lambdaexpression式中指定要更新的属性来使用这个方法。

 ...Update(Model, d=>d.Name); //or ...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

(有些类似的解决scheme也在这里给出: https : //stackoverflow.com/a/5749469/2115384 )

我目前在我自己的代码中使用的方法 ,扩展到处理(Linq)ExpressionType.Converttypes的ExpressionType.Convert这在我的情况下是必要的,例如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(); } 

我在这里比赛迟到了,但是这就是我所做的,我花了一段时间寻找一个我满意的解决scheme。 这只会产生一个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; } 

如果你愿意的话,这可以包装在try..catch中,但我个人喜欢我的调用者了解这种情况下的exception。

这将被称为像这样的方式(对我来说,这是通过一个ASP.NET Web API):

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

entity framework跟踪您通过DbContext从数据库查询的对象的更改。 例如,如果你的DbContext实例名是dbContext

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

我知道这是一个古老的线程,但我也在寻找一个类似的解决scheme,并决定去解决scheme@ Doku-所提供的。 我正在评论回答@Imran Rizvi提出的问题,我跟着@ Doku-so链接,显示了类似的实现。 @Imran Rizvi的问题是他使用提供的解决scheme出现错误'不能将Lambdaexpression式转换为Type'Expression> []',因为它不是委托types'。 我想提供一个我对@ Doku-so的解决scheme进行的小修改,以解决这个错误,以防其他人遇到这个post,并决定使用@ Doku-so的解决scheme。

问题是Update方法中的第二个参数,

 public int Update(T entity, Expression<Func<T, object>>[] properties). 

要使用提供的语法调用此方法…

 Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

您必须在第二个地址前添加“params”关键字。

 public int Update(T entity, params Expression<Func<T, object>>[] properties) 

或者如果您不想更改方法签名,那么要调用Update方法,您需要添加' new '关键字,指定数组的大小,然后最后使用每个属性的集合对象初始值设定语法来更新下面。

 Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn }); 

在@ Doku-so的例子中,他指定了一个expression式数组,所以你必须传递属性来更新一个数组,因为数组还必须指定数组的大小。 为了避免这种情况,你也可以改变expression式参数来使用IEnumerable而不是数组。

这是我执行@ Doku-so的解决scheme。

 public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties) where TEntity: class { entityEntry.State = System.Data.Entity.EntityState.Unchanged; properties.ToList() .ForEach((property) => { var propertyName = string.Empty; var 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); } entityEntry.Property(propertyName).IsModified = true; }); dataContext.Configuration.ValidateOnSaveEnabled = false; return dataContext.SaveChanges(); } 

用法:

 this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId); 

@ Doku-所以提供了一个很酷的方法使用generics,我用这个概念来解决我的问题,但你不能使用@ Doku-so的解决scheme,在这篇文章和链接后没有人回答了使用错误的问题。

在entity framework核心, Attach返回条目,所以你需要的是:

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

结合几点build议,我提出如下build议:

  async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class { try { var entry = db.Entry(entity); db.Set<T>().Attach(entity); foreach (var property in properties) entry.Property(property).IsModified = true; await db.SaveChangesAsync(); return true; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message); return false; } } 

由…调用

 UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.); 

或者通过

 await UpdateDbEntryAsync(dbc, d => d.Property1); 

或者通过

 bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result; 

我使用ValueInjecter nuget将绑定模型注入数据库实体使用以下内容:

 public async Task<IHttpActionResult> Add(CustomBindingModel model) { var entity= await db.MyEntities.FindAsync(model.Id); if (entity== null) return NotFound(); entity.InjectFrom<NoNullsInjection>(model); await db.SaveChangesAsync(); return Ok(); } 

注意自定义约定的用法,如果它们从服务器为空,则不会更新属性。

ValueInjecter v3 +

 public class NoNullsInjection : LoopInjection { protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp) { if (sp.GetValue(source) == null) return; base.SetValue(source, target, sp, tp); } } 

用法:

 target.InjectFrom<NoNullsInjection>(source); 

价值注射器V2

查找这个答案

警告

你不会知道这个属性是故意清除的,还是只是没有任何价值。 换句话说,属性值只能被replace为另一个值,但不能被清除。

 public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties) { try { this.Context.Set<TEntity>().Attach(entity); EntityEntry<TEntity> entry = this.Context.Entry(entity); entry.State = EntityState.Modified; foreach (var property in properties) entry.Property(property).IsModified = true; await this.Context.SaveChangesAsync(); return true; } catch (Exception ex) { throw ex; } } 
 public void ChangePassword(int userId, string password) { var user = new User{ Id = userId, Password = password }; using (var db = new DbContextName()) { db.Entry(user).State = EntityState.Added; db.SaveChanges(); } }