在控制器的同一个动作中使用db上下文和绑定对象.如何更新数据库

本文关键字:何更新 对象 绑定 数据库 更新 上下文 同一个 控制器 db | 更新日期: 2023-09-27 17:49:37

我在这里没有找到任何相关的答案,所以我将触发你,提前感谢:

我有一个具有2种编辑操作方法的控制器,(为了更好地理解,我简化了它):

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的视图。然后,我需要通过将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
    }
}

理念:每个帖子都需要有它的类别。每个类别可以有多个帖子或没有。(其它关系).

问题:

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

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

说:

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

这个错误是因为有一个冲突的行:

cold.Posts.Remove(post);

甚至到这一行:

cnew.Posts.Add(post);

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

db.As.Attach(post)

但是那一行甚至不能编译

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

在控制器的同一个动作中使用db上下文和绑定对象.如何更新数据库

1)您不必调用.Attach().State = anything

你有你的实体创建为代理对象(cold = db.Categories.Find(fromCategoryId);),它的代理责任跟踪任何变化。如例外所说,这可能是你的问题。

2) public int CategoryId { get; set; }应该标记为[Key](我不确定约定是否将其标记为主键,但我怀疑它-我认为EF约定将此PK作为FK到类别,这可能会混淆对象图和行为奇怪…)

嗯,我刚注意到……你为什么要使用FromCategory方法?我可能会忽略一些东西,但看起来它只是从集合中删除类别并将其添加到另一个…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 proxy会自动完成。

我不熟悉ASP,我与桌面MVP/MVVM一起工作,所以我不确定这里-但从我的角度来看,你真的不需要触摸EntityState,只要你使用var x = db.Set<X>().Create(); (== db.X.Create();)(不是var x = new X();)为新的实体和db.Set<X>().FetchMeWhatever(); (== db.X.FetchMeWhatever();)为其他一切(否则你只得到POCO没有代理。从你的例子中,看起来你做得对;))。

然后你有一个代理实体(这就是为什么你在模型virtual上有你的引用属性——这个新的发出的代理类型覆盖它们),这个代理将为你照顾1:n, m:n, 1:1的关系。我认为这就是为什么人们主要使用映射器(不仅仅是EF和不仅仅是DB映射器):)对我来说,它看起来像,你正在尝试手动执行此操作,这是不必要的,它只是制造混乱。

代理也照顾变化跟踪(所以我说,你不需要手动设置EntityState,只有在极端情况下-我现在想不出任何…即使有并发)

所以我的建议是:

  1. 只使用ICollection<>来引用集合
  2. 检查并删除任何var entity = new Entity();(正如我所说,看起来你正在这样做)
  3. 扔掉所有db.Entry(x).State = EntityState.whatever;(信任EF和他的变化跟踪器)
  4. 只设置参考的一侧-无论Category.PostsPost.Category甚至Post.CategoryId -并让映射器完成工作。请注意,这只适用于代理类型(如上所述)的实体与virtual引用&身份证,ICollection<>属性。

顺便说一下,有两种类型的变化跟踪,代码片段和代理-代码片段在RAM中具有原始值,并且在SaveChanges()时间比较它们,对于代理跟踪,您需要将所有属性标记为虚拟-并在x. prop ="x"时间比较它们。但是这跑题了;)