EF 6.1.1和Web API错误添加带子项的实体

时间:2014-10-28 22:36:40

标签: c# wpf entity-framework asp.net-web-api

当我尝试添加具有新子节点的实体时,我在EntityFramework.dll中得到InvalidOperationException。

我已经设置了一个小型测试应用,试图了解这个问题。

我有两个模型:父母和孩子。

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方法(未显示)它返回父实体 - GOOD。

我遇到的问题是当我尝试使用新的子实体'PUT'父实体时。如Button_Click方法中所示。

具有新子节点的Parent实体到达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());
     }

完美无缺。将Child添加到数据库中,更新ParentID,并生成自己的密钥。

那么为什么遇到电线的物体会炸毁EF?

我尝试首先附加对象(db.Parents.Attach(parent);)但是会抛出相同的错误。

我很困惑。

1 个答案:

答案 0 :(得分:1)

实体框架需要跟踪对象以了解哪些对象在哪里并相应地生成SQL查询,部分内容由您通过设置对象的状态来完成,因此如果您将父级的状态设置为要修改但是新子项的状态未设置为“添加”(默认为Unchanged),此处的实体frameowork会将此对象视为已存在于内存中,而实际情况并非如此。

但是当将子项添加到API中的子项列表时,实体框架会将Child的状态设置为Added,并生成SQL以插入新子项并相应地链接ID。

希望有所帮助。

修改 在断开连接的场景中,你通过线路发送对象来修改,添加,删除对象,我确实定义了一个枚举,它将传递给我发送给客户端的每个Dto /实体,客户端将修改此属性为当您尝试使用Entity框架保存整个图形时,让服务器知道每个对象的状态,因此枚举将如下所示

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 =&gt;中的状态。 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");
        }
    }
}
希望有所帮助。