.NET API更新包括ID

我来自Ruby on Rails API背景,但我目前正在开发.NET C#WebAPI。 我熟悉C#和.NET webforms。

我正在尝试设置一个PUT请求来更新数据库中的记录。 scaffolded方法会覆盖所有字段,而我只想简单地更新通过PUT传递的字段。 我尝试使用以下代码:

// PUT: api/Users/5 [ResponseType(typeof(void))] public IHttpActionResult PutUser(string id, User user) { if (!ModelState.IsValid) { return BadRequest(ModelState); } User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault(); if (userToUpdate == null) { return NotFound(); } db.Entry(userToUpdate).CurrentValues.SetValues(user); try { db.SaveChanges(); } catch (DbUpdateException) { if (UserExists(id)) { return Conflict(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } 

但是,该方法在db.SaveChanges()上失败,因为它无法覆盖’id’,这是一个键。 put请求是content-type: application/json ,json如下:

 { "preferredEmailUpdates":"true" } 

这需要适用于所有字段并接受空条目。 所以下面的内容也是有效的,并且应该使用null更新现场电话。

 { "preferredEmailUpdates":"true", "phone":null } 

如何在不更新密钥的情况下更新这些值?

您可以考虑使用PATCH HTTP谓词和Delta对象。

  [AcceptVerbs("Patch"), ResponseType(typeof(void))] public IHttpActionResult PatchUser(string id, Delta changes) { User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault(); if (userToUpdate == null) return NotFound(); changes.Patch(userToUpdate); try { db.SaveChanges() } catch (DbUpdateException) { ... } return StatusCode(HttpStatusCode.NoContent); } 

另请参阅使用Delta的Easy ASP.NET Web API资源更新

您的更新模型(用户请求绑定的类)应与您的数据库模型分开(因为它代表不同的概念)。 它可以使用nullable / maybe字段来表示可能的更改。 它还会隐藏您不希望用户更改的字段(如时间戳)。 然后,您可以使用reflection仅更新已修改的字段。 考虑使用AutoMapper等现成的库

 using System; using System.Reflection; using WhateverNamespaceIsNeededForWebApiAndEF; public class UserUpdate { public String Name {get; set;} public bool? PreferredEmailUpdates {get; set;} } public class User { public int Id {get; set;} public String Name {get; set;} public bool PreferredEmailUpdates {get; set;} public DateTimeOffset CreationDate {get; set;} } [ResponseType(typeof(void))] public IHttpActionResult PutUser(string id, UserUpdate command) { if (!ModelState.IsValid) { return BadRequest(ModelState); } User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault(); if (userToUpdate == null) { return NotFound(); } // THIS IS THE IMPORTANT CODE PropertyInfo[] propInfos = command.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance); foreach(var p in propInfos) { object value = p.GetValue(command); if(value != null) { userToUpdate.GetType().GetProperty(p.Name).SetValue(userToUpdate, value); } } // END IMPORTANT CODE try { db.SaveChanges(); } catch (DbUpdateException) { if (UserExists(id)) { return Conflict(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } 

另一种情况是 – 为什么要更新单个字段? 能够在不更新密码的情况下更新电子邮件首选项是正常的,但是分别更新所述首选项的每个字段是否正常? 我对此表示怀疑。 考虑将您的API拆分为块,就像在OOP中拆分大型接口一样。