2014-10-28 27 views
0

當我嘗試添加具有新子實體的實體時,EntityFramework.dll中出現InvalidOperationException。EF 6.1.1和Web API添加實體時出現錯誤

我已經設置了一個小測試應用程序來試圖瞭解這個問題。

我有兩個模型:父母和孩子。

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; } 
} 

在的WebAPI側我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); 
    } 

我已經一起引發一個WPF應用程序行使API的控制器。

點擊一個按鈕:

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樣子:

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和ApiConsumerResponse樣子:

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(這是不是一個廠),它是響應類看起來像:

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; } 
} 

所以一切都很好。測試Get方法(未顯示)它返回父實體 - 好。

我遇到的問題是當我嘗試用一​​個新的子實體'放'一個父實體。如Button_Click方法中所示。

與新的孩子家長實體在parentController到達但是當我嘗試設置狀態修改:

db.Entry(parent).State = EntityState.Modified; 

引發該錯誤:參照完整性約束衝突發生了:該屬性值(一個或多個)關係一端的'Parent.ParentId'與另一端的'Child.ParentId'的屬性值不匹配。

現在,作爲測試,我更改了控制器上的PUT方法以模擬來自客戶端的嘗試。

修改PUT方法:

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()); 
    } 

這完美的作品。孩子被添加到數據庫它的ParentID被更新並且它自己的密鑰被生成。

那麼爲什麼碰到電線的物體會爆炸EF?

我試着先附加對象(db.Parents.Attach(parent);)但是會拋出相同的錯誤。

我很困惑。

回答

1

實體框架需要跟蹤對象以知道哪個對象在哪裏並相應地生成SQL查詢,並且其中的一部分由您通過設置對象的狀態來完成,因此如果您設置要修改的父對象的狀態但新的孩子的狀態沒有被設置爲添加(默認爲Unchanged),實體框架在這裏將把這個對象視爲已經存在於內存中,而事實並非如此。

但是,當將子項添加到API中的子項列表時,實體框架將設置子項的狀態爲添加項,並將生成SQL以插入新子項並相應鏈接ID。

希望有所幫助。

編輯 在斷開連接的情況下,在你發送的對象跨線進行修改,添加,刪除對象,我定義一個枚舉,將與每個DTO /實體我發送到客戶端傳遞的情況下,和客戶端將修改此屬性,讓服務器知道每個對象是什麼狀態,當您嘗試保存整個圖形實體框架,所以枚舉看起來是這樣

public enum ObjectState 
{ 

    /// <summary> 
    /// Entity wasn't changed. 
    /// </summary> 

    Unchanged, 

    /// <summary> 
    /// Entity is new and needs to be added. 
    /// </summary> 
    Added, 

    /// <summary> 
    /// Entity has been modified. 
    /// </summary> 
    Modified, 

    /// <summary> 
    /// Entity has been deleted (physical delete). 
    /// </summary> 
    Deleted 
} 

,然後我定義的方法那將會把這個枚舉值轉換成實體框架知道的實體的狀態,我的方法會有這樣的事情

// I do this before when the dbcontext about to be saved : 

     foreach (var dbEntityEntry in ChangeTracker.Entries()) 
     { 
      var entityState = dbEntityEntry.Entity as IObjectState; 
      if (entityState == null) 
       throw new InvalidCastException(
        "All entites must implement " + 
        "the IObjectState interface, this interface " + 
        "must be implemented so each entites state" + 
        "can explicitely determined when updating graphs."); 

      **dbEntityEntry.State = StateHelper.ConvertState(entityState.ObjectState);** 

      var trackableObject = dbEntityEntry.Entity as ITrackableObject; 

      // we need to set/update trackable properties 
      if (trackableObject == null) 
      { 
       continue; 
      } 

      var dateTime = DateTime.Now; 

      // set createddate only for added entities 
      if (entityState.ObjectState == ObjectState.Added) 
      { 
       trackableObject.CreatedDate = dateTime; 
       trackableObject.CreatedUserId = userId; 
      } 

      // set LastUpdatedDate for any case other than Unchanged 
      if (entityState.ObjectState != ObjectState.Unchanged) 
      { 
       trackableObject.LastUpdatedDate = dateTime; 
       trackableObject.LastUpdatedUserId = userId; 
      } 
     } 

最後,這是我的幫助器類從我的ObjectState => EF狀態轉換狀態,而反之亦然。

public class StateHelper 
{ 
    public static EntityState ConvertState(ObjectState state) 
    { 
     switch (state) 
     { 
      case ObjectState.Added: 
       return EntityState.Added; 

      case ObjectState.Modified: 
       return EntityState.Modified; 

      case ObjectState.Deleted: 
       return EntityState.Deleted; 

      default: 
       return EntityState.Unchanged; 
     } 
    } 

    public static ObjectState ConvertState(EntityState state) 
    { 
     switch (state) 
     { 
      case EntityState.Detached: 
       return ObjectState.Unchanged; 

      case EntityState.Unchanged: 
       return ObjectState.Unchanged; 

      case EntityState.Added: 
       return ObjectState.Added; 

      case EntityState.Deleted: 
       return ObjectState.Deleted; 

      case EntityState.Modified: 
       return ObjectState.Modified; 

      default: 
       throw new ArgumentOutOfRangeException("state"); 
     } 
    } 
} 

希望有幫助。

+0

謝謝你的指導。我現在發現我的自我閱讀由Julia Lerman和Rowan Millar編寫實體框架DbContext。在其他策略中,他們討論了與您在此處提供的方法非常類似的方法。目前我正在調查:可跟蹤實體:Tony Sneed通過codplex或visual studio gallery提供的對實體框架的N層支持。 – John 2014-10-29 14:59:06