在控制器的同一操作中使用db context和binded对象。如何更新数据库?

时间:2015-05-17 14:29:35

标签: c# asp.net asp.net-mvc entity-framework controller

我在这里找不到任何相关的答案,所以我会提前联系你:

我有一个带有2个Edit操作方法的控制器(为了更好地理解我简化了它):

MrSaleBeta01.Controllers
{
    public class PostsController : Controller
    {
        private MrSaleDB db = new MrSaleDB();  
        ...

        // GET: Posts/Edit/5
        public ActionResult Edit(int? id)
        {
            ...
        }       

        // POST: Posts/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit( Post post, int? CategoryIdLevel1, int? CategoryIdLevel2, int? originalCategoryId)
        {       
            ...
            Category cnew = db.Categories.Find(post.CategoryId);

            MoveFromCategory(post, originalCategoryId);
            ...

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

            db.SaveChanges();

            return RedirectToAction("Index");
        }       

        //move post from his old category (fromCategoryId) to a new one (post.CategoryId):
        //returns true on success, false on failure.
        public bool MoveFromCategory(Post post, int? fromCategoryId)
        {
            try
            {

                if (post.CategoryId == fromCategoryId)
                    return true;

                Category cold = null, cnew = null;
                if (fromCategoryId!=null)                               
                    cold = db.Categories.Find(fromCategoryId);
                if (post.CategoryId != 0)                               
                    cnew = db.Categories.Find(post.CategoryId);

                if (cold != null)
                {
                    cold.Posts.Remove(post);
                }
                if( cnew != null)
                    cnew.Posts.Add(post);

                db.Entry(cold).State = EntityState.Modified;
                db.Entry(cnew).State = EntityState.Modified;
                //db.Entry(p).State = EntityState.Modified;
                //db.SaveChanges();

                return true;
            }
            catch (Exception)
            {
                return false;
                //throw;
            }
        }
    }
}

所以,这个想法是非常默认的:第一个方法由Get调用并返回Edit of Edit。然后我需要通过将post对象从视图发送到HttpPost Edit方法来保存更改。

我的模型是这样的(为了更好地理解我简化了它):

MrSaleBeta01.Models
{

    public class Post
    {
        public int Id { get; set; }

        [ForeignKey("Category")]
        public virtual int CategoryId { get; set; }               
        public virtual Category Category { get; set; }
    }


    public class Category
    {
        public Category()
        {
            this.Categories = new List<Category>();
            this.Posts = new List<Post>();
        }

        #region Primitive Properties
        public int CategoryId { get; set; }       
        public string Name { get; set; }
        #endregion

        #region Navigation Properties
        public virtual IList<Post> Posts { get; set; }
        #endregion
    }
}

这个想法:每个帖子都需要拥有它的类别。每个类别可以有多个帖子或没有。 (1-N关系)。

问题:

在Edit(HttpPost)方法中,在我更新Category的对象(将Post从它的类别移动到另一个类别对象。之后我对post对象进行了一些其他修改)之后,我得到了一个错误。编辑方法:

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

说:

  

{“附加类型'MrSaleBeta01.Models.Post'的实体失败,因为同一类型的另一个实体已经具有相同的主键值。当使用'Attach'方法或设置实体的状态时,可能会发生这种情况如果图中的任何实体具有冲突的键值,则为“未更改”或“已修改”。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下使用“添加”方法或'添加'实体状态以跟踪图表,然后根据需要将非新实体的状态设置为'未更改'或'已修改'。“}

错误是因为线路存在冲突:

cold.Posts.Remove(post);

甚至连线:

cnew.Posts.Add(post);

我尝试使用AsNoTracking()的解决方案,但没有成功, 我还尝试将行“db.Entry(post).State = EntityState.Modified”行更改为:

db.As.Attach(post)

但该行甚至无法编译。

我做错了什么?我该如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

1)您不必致电.Attach().State = anything

您已将您的实体创建为代理对象(cold = db.Categories.Find(fromCategoryId);),其代理职责是跟踪任何更改。例外情况说,这可能是你的问题。

2)public int CategoryId { get; set; }应标有[Key](我不确定约会是否将其标记为主键,但我对此表示怀疑 - 我认为EF约定将此PK作为FK归类别,可能会混淆对象图并且行为异常......)

3)呃,刚刚注意到......你为什么要使用FromCategory方法?我可能会忽略某些内容,但看起来只是从集合中删除Category并将其添加到另一个... EF代理会在post.CategoryId = newCatId;

之后自动为您执行此操作

<强> EDIT1:

4)将public virtual IList<Post> Posts { get; set; }更改为public virtual ICollection<Post> Posts { get; set; }

<强> EDIT2:

  

1)当我根据Post模型构建PostsController时自动创建。所以我想我需要它?

     

3)它不仅仅是从集合中删除类别并将其添加到另一个集合中,而是将帖子从一个类别的帖子中删除到另一个类别。所以我不认为EF代理会自动执行此操作。

我不熟悉ASP,我使用桌面MVP / MVVM,所以我不确定 - 但从我的观点来看,只要你使用{你真的不需要触摸EntityState {新实体{1}}(== var x = db.Set<X>().Create();)(非db.X.Create();)和其他所有内容var x = new X();(== db.Set<X>().FetchMeWhatever();)(否则您只获得没有代理的POCO从你的例子来看,看起来你做得对;))。

然后你有了代理实体(这就是为什么你在模型db.X.FetchMeWhatever();上有你的引用属性 - 这个新发出的代理类型覆盖它们)并且这个代理将负责1:n,m:n,1: 1关系适合你。我认为这就是为什么人们正在使用映射器(不仅仅是EF而不仅仅是数据库映射器):)对我来说看起来像是,你试图手动完成它并且它是不必要的,它只是弄得一团糟。

代理也会处理变更跟踪(所以我说,你不需要手动设置virtual,只是在极端情况下 - 我现在想不到任何东西......即使是并发。)< / p>

所以我的建议是:

  1. 仅使用EntityState来引用集合
  2. 检查并删除任何ICollection<>(正如我所说,看起来你正在这样做)
  3. 丢弃每个var entity = new Entity();(信任EF和他的更改跟踪器)
  4. 只设置参考的一面 - 如果db.Entry(x).State = EntityState.whatever;Category.Posts或甚至Post.Category无关紧要 - 让mapper完成工作。请注意,这仅适用于Post.CategoryId引用&amp;的实体的代理类型(如上所述) id&amp; virtual属性。
  5. 顺便说一下,有2种类型的更改跟踪,代码段和代理 - 代码段在RAM中都有原始值,并且会在ICollection<>时进行比较,对于代理跟踪,您需要标记所有属性虚拟 - 并在x.Prop =&#34; x&#34;时间。但那是偏离主题的;)