如何使用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代码中定义一个完全符合你需要的存储过程(只需更新给定
UserId
的Password
列,而不是其他任何东西 – 基本上执行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(); } }