EF 6.1.1和Web API错误添加实体孩子 [英] EF 6.1.1 and Web API Error adding entity with children

查看:189
本文介绍了EF 6.1.1和Web API错误添加实体孩子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我尝试用新的子项添加一个实体,我在EntityFramework.dll得到InvalidOperationException异常。

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

在的WebAPI端我有一个控制器ParentController

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

我已经一起引发一个WPF应用程序行使API。

I have thrown together a WPF app to exercise the 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如下:

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

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(这不是一个厂),它的响应类的样子:

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

因此​​,所有的好。测试GET方法(未显示)返回父实体 - 好

So all good. Testing the Get Method (not shown) it returns parent entities - GOOD.

我的问题是,当我尝试'把'父实体与新的子实体。作为图中所示的Button_Click方法

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.

与新的子父实体在parentController到达但是当我尝试设置状态修改:

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;

引发该错误:发生参照完整性约束违规:Parent.ParentId的属性值(S)的关系不上比赛Child.ParentId的属性值(S)的一端另一端。

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.

现在作为一个测试我改变了PUT方法的控制器上模拟客户端的尝试。

Now as a test I changed out the PUT method on the controller To emulate the attempt from the client.

修改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更新数据库和它的生成自己的密钥。

Which works perfectly. The Child gets added to the DB it's ParentID is updated and it's own key is generated.

那么,为什么说遇到电线对象炸毁EF?

So why does the object that comes across the wire blow up EF?

我第一次尝试连接对象(db.Parents.Attach(父),),但抛出了同样的错误。

I tried attaching the object first (db.Parents.Attach(parent);) but that throws the same error.

林困惑。

推荐答案

实体框架需要跟踪的对象知道哪去的地方,并相应地生成SQL查询,这部分是由你做,通过设置国家对象,因此,如果您设置进行修改,父状态,但并没有设置新的孩子的状态下增加了(默认为<一个href=\"http://msdn.microsoft.com/en-us/library/system.data.objects.dataclasses.entityobject.entitystate(v=vs.110).aspx\"相对=nofollow>不变),这里的实体frameowork将作为在存储器中已经存在治疗该对象,这是并非如此。

Entity Framework needs to track the objects to know which goes where and generates the SQL query accordingly, and part of this is done by you, by setting the State of the object, so if you set the parent's state to be modified but the new child's state was not set to be Added (Default is Unchanged), the entity frameowork here will treat this object as already exists in memory, and this is not the case.

但是,当添加孩子给孩子列表中的API中的实体框架将设置孩子的状态以复加,并且将生成SQL插入新的孩子,并相应链接标识。

but when added the child to the list of the children within the API the entity framework will set the Child's state to be Added, and will generate the SQL to insert the new child and link the ids accordingly.

希望有所帮助。

修改
在断开连接的情况下,您发送的对象跨线进行修改,添加,删除对象,我定义一个枚举,将与每个DTO /实体我发送到客户端传递,并在客户端将修改这一属性的情况下,让服务器知道每个对象的状态,当您尝试保存整个图形实体框架,所以枚举看起来像这样

EDIT In the case of disconnected scenario, where you send the objects across the wire to modify, add, delete objects, I do define an enum that will pass with each Dto/entity I send to the client, and the client will modify this property to let the server knows what the status of each object when you try to save the whole graph with Entity framework, so the enum will look like this

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
}

然后我定义将状态的实体框架知道翻译这个枚举值给实体的方法,我的方法是这样的

and then I define a method that will translate this enum value to the entity' state that the entity framework knows about, my method will have something like this

// 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状态转换,并且反之亦然。

And finally this is my helper class to convert the states from my ObjectState => EF State, and Vise versa.

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

希望帮助。

这篇关于EF 6.1.1和Web API错误添加实体孩子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆