When I try to add an entity with new children, I get InvalidOperationException in the EntityFramework.dll.
I have set a small test app to attempt to understand this issue.
I have two models: Parent and Child.
public class Parent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ParentId { get; set; }
public String Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ChildId { get; set; }
public Guid ParentId { get; set; }
public string Name { get; set; }
// Navigation
[ForeignKey("ParentId")]
public Parent Parent { get; set; }
}
at the WebAPI side I have a controller ParentController
// PUT: api/Parents/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutParent(Guid id, Parent parent)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != parent.ParentId)
{
return BadRequest();
}
db.Entry(parent).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ParentExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I have thrown together a WPF app to exercise the API.
On a Button click:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
ParentApi parentApi = new ParentApi();
var response = await parentApi.GetParents();
if(response.ResponseCode.Equals(200))
{
var parent = ((List<Parent>)response.ResponseObject).Where(prnt => prnt.Name.Equals("Parent1", StringComparison.Ordinal)).Single();
if(parent != null)
{
// Put child entity/
if (parent.Children == null)
parent.Children = new List<Child>();
Child newChild = new Child();
newChild.Name = "Child One";
parent.Children.Add(newChild);
response = await parentApi.PutParent(parent.ParentId, parent);
if(response.ResponseCode.Equals(200))
{
// Success
Debug.WriteLine(response.ResponseObject.ToString());
}
else
{
// Other/
if (response.ResponseObject != null)
Debug.WriteLine(response.ResponseObject.ToString());
}
}
}
}
ParentAPi looks like:
public class ParentApi : ApiBase
{
public async Task<ApiConsumerResponse> GetParents()
{
return await GetAsync<Parent>("http://localhost:1380/api/Parents/");
}
public async Task<ApiConsumerResponse> PutParent(Guid parentId, Parent parent)
{
return await PutAsync<Parent>(parent, "http://localhost:1380/api/Parents/" + parentId);
}
}
ApiBase and ApiConsumerResponse look like:
public class ApiBase
{
readonly RequestFactory _requester = new RequestFactory();
public async Task<ApiConsumerResponse> GetAsync<T>(string uri)
{
ApiConsumerResponse result = new ApiConsumerResponse();
try
{
var response = await _requester.Get(new Uri(uri));
result.ResponseCode = response.ResponseCode;
result.ReasonPhrase = response.ReasonPhrase;
if (result.ResponseCode == 200)
{
result.ResponseObject = await Task.Factory.StartNew(
() => JsonConvert.DeserializeObject<List<T>>(
response.BodyContentJsonString));
}
else
{
string msg = response.ReasonPhrase + " - " + response.BodyContentJsonString;
result.ErrorReceived = true;
}
}
catch (Newtonsoft.Json.JsonReaderException jsonE)
{
result.ErrorReceived = true;
}
catch (Exception e)
{
// Some other error occurred.
result.ErrorReceived = true;
}
return result;
}
public async Task<ApiConsumerResponse> PutAsync<T>(T apiModel, string uri)
{
ApiConsumerResponse result = new ApiConsumerResponse();
try
{
string json = await Task.Factory.StartNew(
() => JsonConvert.SerializeObject(
apiModel, Formatting.Indented));
var response = await _requester.Put(new Uri(uri), json);
result.ResponseCode = response.ResponseCode;
result.ReasonPhrase = response.ReasonPhrase;
// if 200: OK
if (response.ResponseCode.Equals(200))
{
result.ResponseObject = await Task.Factory.StartNew(
() => JsonConvert.DeserializeObject<T>(
response.BodyContentJsonString));
}
else
{
string msg = response.ReasonPhrase + " - " + response.BodyContentJsonString;
result.ErrorReceived = true;
}
}
catch (Newtonsoft.Json.JsonReaderException jsonE)
{
result.ErrorReceived = true;
}
catch (Exception e)
{
// Some other error occurred.
result.ErrorReceived = true;}
return result;
}
}
public class ApiConsumerResponse
{
public int ResponseCode { get; set; }
public string ReasonPhrase { get; set; }
public object ResponseObject { get; set; }
public bool ErrorReceived { get; set; }
}
RequestFactory (which is not a factory) and it's response class look like:
public class RequestFactory
{
public async Task<NetworkWebRequestMakerResponse> Get(Uri uri)
{
if (uri.UserEscaped)
{
uri = new Uri(Uri.EscapeUriString(uri.OriginalString));
}
using (var client = new HttpClient())
{
try
{
client.Timeout = TimeSpan.FromSeconds(60);
var response = await client.GetAsync(uri);
var stringResponse = await response.Content.ReadAsStringAsync();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Post(Uri url, string json)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
Debug.WriteLine("POSTING JSON: " + json);
var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(@"application/json");
response = await client.PostAsync(url, content);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Put(Uri url, string json)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
Debug.WriteLine("PUTING JSON: " + json);
var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(@"application/json");
response = await client.PutAsync(url, content);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Delete(Uri url)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
response = await client.DeleteAsync(url);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
}
public class NetworkWebRequestMakerResponse
{
public bool UnknownErrorReceived { get; set; }
public Exception UnknonErrorExceptionObject { get; set; }
public int ResponseCode { get; set; }
public string ReasonPhrase { get; set; }
public string BodyContentJsonString { get; set; }
}
So all good. Testing the Get Method (not shown) it returns parent entities - GOOD.
The problem I have is when I try to 'PUT' a parent entity with a new child entity. As Shown in the Button_Click method.
The Parent entity with the new child arrives at the parentController however when I try to set state as modified:
db.Entry(parent).State = EntityState.Modified;
The Error is thrown: A referential integrity constraint violation occurred: The property value(s) of 'Parent.ParentId' on one end of a relationship do not match the property value(s) of 'Child.ParentId' on the other end.
Now as a test I changed out the PUT method on the controller To emulate the attempt from the client.
Modified PUT method:
public async Task<IHttpActionResult> PutParent(Guid id, Parent parent)
{
parent = db.Parents.Where(pe => pe.Name.Equals("Parent1", StringComparison.Ordinal)).Single();
var child = new Child();
child.Name = "Billy";
if (parent.Children == null)
parent.Children = new List<Child>();
parent.Children.Add(child);
db.Entry(parent).State = EntityState.Modified;
var result = await db.SaveChangesAsync();
Debug.Write(result.ToString());
}
Which works perfectly. The Child gets added to the DB it's ParentID is updated and it's own key is generated.
So why does the object that comes across the wire blow up EF?
I tried attaching the object first (db.Parents.Attach(parent);) but that throws the same error.
Im confused.