EF Core更新部分字段
- 一、利用TryUpdateModelAsync
- 二、查询出实体,然后将表单form中的实体与查询出来的实体对应修改
- 三、字段绑定更新部分,将所有字段值读出来,然后在html中将不更新的字段设置为readonly,这样用户只能操作需要更新的字段值,但这也有一个问题,如果用户懂计算机,将实体在html中的readonly去掉,会导致后台更新数据 的同时会把值更新
- 四、利用属性设置更新部分字段,适用于更新字段少的情况,比如实体总共有10个字段,仅更新两个或一个字段的情况下,可以设置如下 :比如只更新了username字段
- 五、如果更新字段较多,上面的方法都要写一堆需要更新字段的代码,假如有10个字段,更新9个,仅有一个字段不更新,那么,可以用以下方法,此方法适用总字段较多,但更新的字段较少的情况,和上面的正好相反。具体如下:
- 六、使用原生SQL执行更新操作
- 关于过度发布:
一、利用TryUpdateModelAsync
public ActionResult Edit(int id)
{
var q = dbContext.LeaveRecord.Find(id);
if (q == null)
{
return NotFound();
}
return View(q);
}
// POST: LeaveController/Edit/5,
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditAsync(int? id)
{
if (id ==null)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var q = dbContext.LeaveRecord.Find(id);
if (await TryUpdateModelAsync(q, "", o => o.PersonName, o => o.LeaveType, o => o.Sex))
{
await dbContext.SaveChangesAsync();
}
}
catch
{
return View();
}
return RedirectToAction(nameof(Index));
}
return View();
}
此方法首先查询出实体数据,然后根据lambda表达式中的字段进行更新对应的实体字段,注意lambda表达式,都为这样的参数格式o=>o.x1,o=>o.x2而不是o=>o.x1,o.x2
二、查询出实体,然后将表单form中的实体与查询出来的实体对应修改
// GET: LeaveController/Edit/5
public ActionResult Edit(int id)
{
var q = dbContext.LeaveRecord.Find(id);
if (q == null)
{
return NotFound();
}
return View(q);
}
// POST: LeaveController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditAsync(int? id, LeaveRecord leaveRecord)
{
if (id ==null)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var q = dbContext.LeaveRecord.Find(id);
q.PersonName = leaveRecord.PersonName;
q.Sex = leaveRecord.Sex;
q.LeaveType = leaveRecord.LeaveType;
q.LeaveReason = leaveRecord.LeaveReason;
dbContext.Update(q);
dbContext.SaveChanges();
}
catch
{
return View();
}
return RedirectToAction(nameof(Index));
}
return View();
}
三、字段绑定更新部分,将所有字段值读出来,然后在html中将不更新的字段设置为readonly,这样用户只能操作需要更新的字段值,但这也有一个问题,如果用户懂计算机,将实体在html中的readonly去掉,会导致后台更新数据 的同时会把值更新
view视图文件
<input type="text" readonly asp-for="Title" />
<input type="text" asp-for="Content" />
假设只更新content字段数据
四、利用属性设置更新部分字段,适用于更新字段少的情况,比如实体总共有10个字段,仅更新两个或一个字段的情况下,可以设置如下 :比如只更新了username字段
// GET: UserController/Edit/5
public ActionResult Edit(int id)
{
var q = dbContext.User.Find(id);
return View(q);
}
// POST: UserController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, User user)
{
try
{
dbContext.Attach(user);
//将需要更新的字段设置为true,没有设置的字段不更新,这种方法不用查询数据库找到实体
dbContext.Entry(user).Property(o => o.UserName).IsModified = true;
dbContext.SaveChanges();
return RedirectToAction(nameof(Index));
}
catch(Exception ex)
{
return View();
}
}
但如果需要更新的列多的话,会照样写成很多类似语句
dbContext.Entry(user).Property(o => o.UserName).IsModified = true;
dbContext.Entry(user).Property(o => o.UserPassword).IsModified = true;
当然这样的语句可以用循环的方式实现,将需要更新的字段封装起来,然后通过循环遍历设置为true,以下代码可以参考
public void UpdateEntityFields(T entity, List<string> fileds)
{
if (entity != null&&fileds!=null)
{
dbContext.CreateObjectSet<T>().Attach(entity);
var SetEntry = ((IObjectContextAdapter)dbContext).ObjectContext.
ObjectStateManager.GetObjectStateEntry(entity);
foreach (var t in fileds)
{
SetEntry.SetModifiedProperty(t);
}
}
}
五、如果更新字段较多,上面的方法都要写一堆需要更新字段的代码,假如有10个字段,更新9个,仅有一个字段不更新,那么,可以用以下方法,此方法适用总字段较多,但更新的字段较少的情况,和上面的正好相反。具体如下:
// GET: UserController/Edit/5
public ActionResult Edit(int id)
{
var q = dbContext.User.Find(id);
return View(q);
}
// POST: UserController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, User user)
{
try
{
var d = dbContext.Attach(user);
dbContext.Entry(user).State = EntityState.Modified;//将所有字段设置为要更新的状态
dbContext.Entry(user).Property(o=>o.UserName).IsModified = false;//再将不需要的字段设置为不更新,那么这样就达到了更新多个字段,除此字段外
dbContext.SaveChanges();
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
return View();
}
}
注意:加了Attach在SaveChanges()前面一定不要再加dbContext.Update(user);加上此句会出错,导致上面的设置状态无效,会全部更新,所以一定不要加。
六、使用原生SQL执行更新操作
dbContext.Database.ExecuteSqlRaw(“sql语句”,参数1值, 参数2值,…);
public ActionResult Edit(int id, User user)
{
try
{
dbContext.Database.ExecuteSqlRaw($"update User set userName='{user.UserName}',userpassword='{user.UserPassword}' where id={user.Id}");
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
return View();
}
}
警告
始终对原始 SQL 查询使用参数化
向原始 SQL 查询引入任何用户提供的值时,必须注意防范 SQL 注入攻击。 除了验证确保此类值不包含无效字符,请始终使用会将值与 SQL 文本分开发送的参数化处理。
具体而言,如果连接和内插的字符串 ($“”) 带有用户提供的未经验证的值,则切勿将其传递到 FromSqlRaw 或 ExecuteSqlRaw。 通过 FromSqlInterpolated 和 ExecuteSqlInterpolated 方法,可采用一种能抵御 SQL 注入攻击的方式使用字符串内插语法。一般这种方法用于内部确定的更新操作。比如将某个字段更新为某个值等。
以上方法必须保证参数值是经过处理的,否则不建议用$""这样的形式。下面的两个方法进行参数化防止sql注入攻击:
第一种简写:
dbContext.Database.ExecuteSqlRaw("update User set userName={0},userpassword={1} where id={2}","user01","123123",3);//将id=3的user用户名和密码进行更新
第二种参数写法:
这里是mysql用 MySqlParameter,sql用SqlParameter,注意对应关系否则出错,代码如下:
int id=3;
dbContext.Database.ExecuteSqlRaw("update User set userName=@userName,userpassword=@password where id=@id",
new MySqlParameter("@userName", "jone"),
new MySqlParameter("@password", "12345"),
new MySqlParameter("@id",id));
上面使用的参数化赋值,参数可与字段名不同,可以参考对比。
FromSqlRaw只用作查询。
params object[] parameters或params object[] args作为方法参数时,指明该参数为可变长度和类型参数,比如“abc",1,true可传入。
前面几种方法根据具体情况使用即可。
关于过度发布:
经常见类似这样的[bind(“”)] User user,为什么要绑定对应字段呢,主要是为了防止过度发布,比如User实体中有3个字段,分别 为id,username,userpassword,在进行更新Edit操作时,虽然表单里对应的只id,name两个input,但下面的代码user对象里是3个字段,黑客会利用工具将userpassword字段什传入了,导致更新时把密码也修改了!
// POST: UserController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id,[Bind("id,username")] User user)
微软官方实例如下图
防止以上过度发布可以使用Bind属性来绑定需要更新的字段,其它字段即可传送过来也会清空,但这样在更新数据时不能直接dbcontext.update(user)然后进行savechanges()提交。只能查询到的user实体,再将参数中user赋值到需要更新的字段,当然还有更好的办法是使用上面提到的,
dbContext.Entry(user).Property(o => o.UserName).IsModified = true;
将需要更新的字段更新,这种方法不需要进行查询,直接使用的user实体。
参考资料:
EntityState的5个状态解释:
成员名称 | 说明 |
---|---|
Detached | 对象存在,但没有被跟踪。 在创建实体之后、但将其添加到对象上下文之前,该实体处于此状态。 An entity is also in this state after it has been removed from the context by calling the Detach method or if it is loaded by using a NoTrackingMergeOption. 没有 ObjectStateEntry 实例与状态为 Detached 的对象关联。 |
Unchanged | 自对象附加到上下文中后,或自上次调用 SaveChanges 方法后,此对象尚未经过修改。 |
Added | 对象为新对象,并且已添加到对象上下文,但尚未调用 SaveChanges 方法。 在保存更改后,对象状态将更改为 Unchanged。 状态为 Added 的对象在 ObjectStateEntry 中没有原始值。 |
Modified | 对象上的一个标量属性已更改,但尚未调用 SaveChanges 方法。 在不带更改跟踪代理的 POCO 实体中,调用 DetectChanges 方法时,已修改属性的状态将更改为 Modified。 在保存更改后,对象状态将更改为 Unchanged。 |
上下文正在跟踪实体的状态。
public enum EntityState
继承
Enum
EntityState
字段 | ||
---|---|---|
Added | 4 | 实体正在由上下文跟踪,但数据库中尚不存在。 |
Deleted | 2 | 实体正在由上下文跟踪,并存在于数据库中。 已将其标记为从数据库中删除。 |
Modified | 3 | 实体正在由上下文跟踪,并存在于数据库中。 已经修改了其部分或全部属性值。 |
Unchanged | 1 | 实体正在由上下文跟踪,并存在于数据库中。 不会更改数据库中的值的属性值。 |