实体框架和MVC 3:关系不能被改变,因为一个或多个外键的属性是不可为空 [英] Entity Framework and MVC 3: The relationship could not be changed because one or more of the foreign-key properties is non-nullable

查看:1182
本文介绍了实体框架和MVC 3:关系不能被改变,因为一个或多个外键的属性是不可为空的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试使用更新对象及其所有子集一个View(基于一个一对多在SQL Server数据库与实体框架模型关系)。

I have been trying to use one View for updating an object and all its child collections (based on one-to-many relationships in an SQL Server database with an Entity Framework model).

有人建议我应该使用AutoMapper,我试过了,并得到它的工作。 (见<一href=\"http://stackoverflow.com/questions/5126603/trying-to-use-automapper-for-model-with-child-collections-getting-null-error-in/5130861#5130861\">Trying使用AutoMapper与孩子集合的模式,让零错误Asp.Net MVC 3 )。

It was suggested I should use AutoMapper, and I tried that and got it to work. (see Trying to use AutoMapper for model with child collections, getting null error in Asp.Net MVC 3 ).

但解决真的很难维持。当我尝试简单的一个我曾与开始,通过直接使用一个实体对象模型(一个顾问的对象,所有的子集的父),我能得到所有正确的更改过的数据回POST,我可以使用的UpdateModel让他们,包括儿童集合。简单。当然,仅仅的UpdateModel在所以在这里建立从尖端的自定义模型绑定后的工作:

But the solution is really hard to maintain. And when I try the simple one I had to begin with, using an entity object directly as the model (a "Consultant" object, the parent of all the child collections), I am able to get all the correct changed data back in the POST, and I can use UpdateModel to get them, including child collections. Simple. Granted, UpdateModel only worked after creating a custom model binder from a tip here at SO:

从我的自定义模型绑定:

From my custom model binder:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;

            return base.BindModel(controllerContext, bindingContext);
        }

        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
            propertyMetadata.Model = value;
            string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);

            // Try to set a value into the property unless we know it will fail (read-only 
            // properties and null values with non-nullable types)
            if (!propertyDescriptor.IsReadOnly)
            {
                try
                {
                    if (value == null)
                    {
                        propertyDescriptor.SetValue(bindingContext.Model, value);
                    }
                    else
                    {
                        Type valueType = value.GetType();

                        if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
                        {
                            IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model);
                            IList list = ls.GetList();

                            foreach (var item in (IEnumerable)value)
                            {
                                list.Add(item);
                            }
                        }
                        else
                        {
                            propertyDescriptor.SetValue(bindingContext.Model, value);
                        }
                    }

                }
                catch (Exception ex)
                {
                    // Only add if we're not already invalid
                    if (bindingContext.ModelState.IsValidField(modelStateKey))
                    {
                        bindingContext.ModelState.AddModelError(modelStateKey, ex);
                    }
                }
            }
        }

下面是我的简单编辑POST方法:

Here's my simple Edit POST method:

    [HttpPost]
    [ValidateInput(false)] //To allow HTML in description box
    public ActionResult Edit(int id, FormCollection collection)
    {

        Consultant consultant = _repository.GetConsultant(id);
        UpdateModel(consultant);
        _repository.Save();

        return RedirectToAction("Index");
    }

但在那之后的UpdateModel工作。问题是,在下一阶段,试图调用上下文调用SaveChanges时,失败。我得到这个错误:

But after that UpdateModel worked. The problem is, at the next stage, when trying to call SaveChanges on the context, that fails. I'm getting this error:

操作失败:关系
  不能因为一个或改变
  更多的外键的属性是
  非空的。当变化来做出
  的关系,相关
  外键属性被设置为空
  值。如果外键不
  支持空值,一个新的
  关系必须被定义,则
  外键属性必须被分配
  另一个非空值,或
  无关的对象必须被删除。

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

我不明白什么是错的。我看到张贴的顾问对象的所有正确的价值观,我只是不能将它保存到数据库中。 AutoMapper在此情况下(尽管一个有趣的工具)的路由都不尽如人意,它是非常复杂的我的code和制作应用程序,这应该是相当简单的,一场噩梦维护。

I don't understand what is wrong. I'm seeing all the correct values in the Consultant object posted, I just can't save it to database. The route of AutoMapper in this case (although an interesting tool) is not working well, it's complicating my code immensely and making the application, which should be rather simple, a nightmare to maintain.

任何人都可以提供任何洞察为什么我收到此错误,以及如何克服呢?

Can anyone offer any insight into why I'm getting this error and how to overcome it?

更新:

阅读一些职位在这里,我发现了一个似乎略显相关:<一href=\"http://stackoverflow.com/questions/2720512/how-to-update-model-in-the-database-from-asp-net-mvc2-using-entity-framework\">How在数据库中更新模式,从asp.net MVC2,使用实体框架?。我不知道,如果它涉及到这一点,但是当我POST之后视察了顾问的对象,似乎这个对象本身具有的EntityKey,而是一个集合中的个别项目没有(EntityKeySet = NULL)。然而,每个项目确实有正确的ID。我不pretend了解任何这与的EntityKey,所以请解释一下,如果有对我的问题的任何轴承,如果有的话,如何解决...

Reading some posts here, I found one that seemed slightly related: How to update model in the database, from asp.net MVC2, using Entity Framework? . I don't know if it relates to this, but when I inspected the Consultant object after POST it seems this object itself has entitykey, but the individual items in a collection do not (EntityKeySet = null). Each item however does have the correct id. I don't pretend to understand any of this with the EntityKey, so please explain if it has any bearings on my issue, and if so, how to resolve it...

更新2:

我想到的东西,可能有一些跟我的问题:视图使用由史蒂芬·桑德森所描述的技术(见的http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ ),而当调试在我看来,如果拥有的UpdateModel匹配集合中的项目与实际顾问对象的人查看的麻烦。我不知道是否这与在此技术索引做。下面是从code辅助(我不能按照它非常好自己,但它采用的是GUID来创建索引,这可能是问题):

I thought of something that might have something to do with my problems: The View is using a technique described by Steven Sanderson (see http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ ), and when debugging it seems to me as if UpdateModel has trouble matching the items in a collection in the View with the ones in the actual Consultant object. I'm wondering if this has to do with the indexing in this technique. Here's the helper from that code (I can't follow it very well myself, but it uses a Guid to create indexes, which might be the problem):

public static class HtmlPrefixScopeExtensions
    {
        private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

        public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
        {
            var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
            string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

            // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
            html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

            return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
        }

        public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
        {
            return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
        }

        private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
        {
            // We need to use the same sequence of IDs following a server-side validation failure,  
            // otherwise the framework won't render the validation error messages next to each item.
            string key = idsToReuseKey + collectionName;
            var queue = (Queue<string>)httpContext.Items[key];
            if (queue == null)
            {
                httpContext.Items[key] = queue = new Queue<string>();
                var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
                if (!string.IsNullOrEmpty(previouslyUsedIds))
                    foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                        queue.Enqueue(previouslyUsedId);
            }
            return queue;
        }

        private class HtmlFieldPrefixScope : IDisposable
        {
            private readonly TemplateInfo templateInfo;
            private readonly string previousHtmlFieldPrefix;

            public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
            {
                this.templateInfo = templateInfo;

                previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
                templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
            }

            public void Dispose()
            {
                templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
            }
        }
    }

不过话说回来,我也不会想到,因为隐藏的输入包含value属性的ID,这应该是这个问题,我想的UpdateModel只是看着场上得到程序(集合)的名称,名(属性),然后将该值到的ID ...?然后再有似乎是更新过程中的一些不匹配。总之,这里的萤火虫从生成的HTML也:

But then again, I wouldn't have thought this should be the problem since the hidden input contains the id in the value attribute, and I thought UpdateModel just looked at the name of the field to get Programs (the collection) and Name (the property), and then the value to the the id...? And then again there's seems to be some mismatch during update. Anyway, here's the generated html from FireBug also:

<td>
            <input type="hidden" value="1" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Id" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Id" data-val-required="The Id field is required." data-val-number="The field Id must be a number." data-val="true"> 
            <input type="text" value="Visual Studio" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Name">
            <span data-valmsg-replace="true" data-valmsg-for="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" class="field-validation-valid"></span>
        </td>

任何人都知道这是什么问题?如果是这样,我怎么能解决它能够轻松地更新的UpdateModel集合? (虽然仍然能够在POST,这是该技术的目的,开始与前视图中添加或删除项目)。

Anyone know if this is the problem? And if so, how can I work around it to be able to easily update the collections with UpdateModel? (While still being able to add or remove items in the View before POST, which was the purpose of this technique to begin with).

推荐答案

它看起来像有是有一个与你的顾问实体一对多关系父实体。当您更改用作ForeignKey的为这种关系顾问实体的属性,实体框架设置相关领域中的父实体为null脱钩的关系。当该字段不能为空,你会得到这个错误。其实这个错误的定义出奇的好,我已经看到了这个问题,更神秘的错误。

It looks like there is a Parent entity that has a one to many relationship with your Consultant entity. When you change an attribute of the Consultant entity that is used as the ForeignKey for that relationship, Entity Framework sets the relevant field in the Parent entity to null to decouple the relationship. When that field is not nullable you'll get this error. Actually that error definition is surprisingly good, I've seen this problem with far more cryptic errors.

所以,我建议你检查数据库中的父实体,并从那里进行补救(如果你可以改变它可空一切都很好,如果它是一个不同的约束-pk或suchlike-你的一部分'会与你的对象模型捣鼓)。我问你后你的实体模型,但文本块被恐吓,因为它是。

So, I recommend that you check the parent entity in the database, and proceed to a remedy from there (if you can change it to nullable all is well, if it is part of a different constraint -pk or suchlike- you'll have to fiddle with your object models). I'd ask you to post your entity models, but the chunk of text is intimidating as it is.

这篇关于实体框架和MVC 3:关系不能被改变,因为一个或多个外键的属性是不可为空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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