Reputation: 560
I have a very simple MVC5 WebApi project with the following models:
public class Product
{
public Product()
{
}
public int ProductId { get; set; }
public string ProductCode { get; set; }
public int BomId { get; set; }
public virtual BOM BOM { get; set; }
}
public class BOM
{
public BOM()
{
Products = new List<Product>();
}
public int BomId { get; set; }
public string Description { get; set; }
public int Counter { get; set; }
public virtual List<Product> Products { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<BOM> BOMs { get; set; }
}
Here is the PUT action (the scaffolded action):
public IHttpActionResult PutProduct(int id, Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != product.ProductId)
{
return BadRequest();
}
db.Entry(product).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
When the code executes db.SaveChanges(); the code executes as expected but the database doesn't get updated.
Here is the code in the console application that triggers the PUT request:
static async Task UpdateProduct(int Id)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost.fiddler:49195/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// New code:
HttpResponseMessage response = await client.GetAsync("api/products/" + Id.ToString());
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
Product product = JsonConvert.DeserializeObject<Product>(json);
product.BOM.Counter += 1;
json = JsonConvert.SerializeObject(product);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
response = await client.PutAsync("api/products/" + Id.ToString(), httpContent);
Console.WriteLine(response.IsSuccessStatusCode.ToString() + ": Counter = " + product.BOM.Counter);
}
}
}
What this method does is get the product requested (1), updates a field in the child (BOM.Counter) by incrementing a value on that field.
It's just that the database does not update so when the application starts again, obviously we're getting the old value and not the updated one. The SaveChanges method seems to execute but the database doesn't suggest that it has.
Here are the get and put json strings from the PUT method as per Fiddler2:
Note: I have these lines of code in my Global.asax file so that Json handles the models correctly:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
I'm hoping someone can see where I'm going wrong. Any help would really be appreciated.
Thanks in advance,
Paul.
Upvotes: 0
Views: 2379
Reputation: 4130
Look at 2 lines of code:
Product product = JsonConvert.DeserializeObject<Product>(json);
product.BOM.Counter += 1;
Your problem is that because you have a parent entity with a child entity, You are setting the parent's entity state to be Modified but what changed actually was the child entity, so you left the child's entity state to be Unchanged (Entity framework does apply the modified state on single entity not like "Added" which will be applied on the whole graph), so on the server side if you just set the BOM entity state to be Modified, you will get your changes into the database.
Now what I suggested is not a permanent solution, but this is just to show you your problem and how to solve it temporary.
To solve it permanently and because your client is a disconnected graph, your client needs to track the objects and being responsible of what happened with the graph entities (added, modified, deleted, or unchanged).
You need to implement an interface like IObjectState and let your clients change the state of the disconnected graph and on the server you convert that state to the entity state in before you save the entity.
Look at my answer here from a detailed answer.
Hope that helps.
Upvotes: 1