我的存储库中有一个 Update
方法,我用它来更新项目中的文章。我最初使用此方法只是为了对文章进行管理编辑。它正确地处理了这个问题,但我决定添加一个简单的机制来计算“最多阅读”的文章。为此,我想在每次查看文章时更新 TimesRead
属性。这给我带来了更新的麻烦,这些更新似乎围绕着使用ObjectStateManager.ChangeObjectState
。这是我的 Update
方法:
public void Update(Article article)
{
if (article == null) return;
db.Articles.Attach(article);
db.ObjectStateManager.ChangeObjectState(article, EntityState.Modified);
db.SaveChanges();
}
在我的 AdminController
中,以下方法正确更新:
[HttpPost]
public ActionResult Edit(AdminEditViewModel viewModel)
{
if (ModelState.IsValid)
{
Article article = Mapper.Map<AdminEditViewModel, Article>(viewModel);
articleRepository.Update(article);
return RedirectToAction("Index");
}
viewModel.Categories = new SelectList(categoryRepository.GetAll(), "CategoryID", "Name", viewModel.CategoryID);
return View(viewModel);
}
但是,在 TimesRead
场景中,更新将触发以下异常:
无法附加该对象,因为它已经在对象上下文中。对象只有在未更改的状态下才能重新附加。
该控制器方法的相关代码:
var model = articleRepository.GetByID(id);
model.TimesRead++;
articleRepository.Update(model);
return View(model);
环顾四周看看我能做些什么来解决这个问题后,我找到了 这个 SO问题。因此,我通过用建议的代码替换我的 Update
方法来实现这个答案。这在我的管理场景中也可以正常工作,但在 TimesRead
场景中却无法正常工作。抛出以下异常:
ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象。
这些例外的含义非常明确,但它确实让我想知道我应该如何处理诸如此类的简单更新。我发现我可以通过设置 EntityState.Unchanged 来“欺骗”EF 认为模型未更改,这将更新 TimesRead 但为管理员更新提供例外,说明ObjectStateManager
不保存对该对象的引用。
我也很清楚这些场景有何不同。 Edit
操作将属性从 ViewModel 映射到新的、未附加的 Article
对象,而 ArticleController
则处理直接从 ViewModel 检索的对象。语境。这让我觉得我应该重构这些控制器方法之一,以便更新步骤是相同的。我只是不太确定我应该如何处理这个问题,因为这两种方法对我来说似乎应该能够共存。所以我的问题是,我可以更改什么以使两种类型的更新都能正常工作?
感谢您抽出宝贵的时间,对于发布的代码量我感到非常抱歉。我只是觉得这一切都与问题相关。
I have an Update
method in my repository which I'm using to update articles on my project. I was initially using this method only to carry out admin edits for articles. It handles that correctly, but I decided I'd like to add a simple mechanism to calculate "most read" articles. In order to do that, I'd like to update TimesRead
property each time an article has been viewed. This has been giving me trouble with the updates which seem to revolve around using ObjectStateManager.ChangeObjectState
. Here's my Update
method:
public void Update(Article article)
{
if (article == null) return;
db.Articles.Attach(article);
db.ObjectStateManager.ChangeObjectState(article, EntityState.Modified);
db.SaveChanges();
}
In my AdminController
the following method updates correctly:
[HttpPost]
public ActionResult Edit(AdminEditViewModel viewModel)
{
if (ModelState.IsValid)
{
Article article = Mapper.Map<AdminEditViewModel, Article>(viewModel);
articleRepository.Update(article);
return RedirectToAction("Index");
}
viewModel.Categories = new SelectList(categoryRepository.GetAll(), "CategoryID", "Name", viewModel.CategoryID);
return View(viewModel);
}
However, in the TimesRead
scenario, the update will trigger an exception of:
The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
Relevant code from that controller method:
var model = articleRepository.GetByID(id);
model.TimesRead++;
articleRepository.Update(model);
return View(model);
After having a look around to see what I can do to solve this, I came across the answer to this SO question. So I implemented that answer by replacing my Update
method with the code suggested. This also works correctly in my admin scenario but not in the TimesRead
scenario. The following exception is thrown:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
The exceptions are quite clear in their meaning but it does leave me wondering how I am supposed to handle simple updates such as these. I found that I can "fool" the EF into thinking the model is unchanged by setting EntityState.Unchanged
and that will update TimesRead
but give an exception for admin updates, stating the ObjectStateManager
doesn't hold a reference to the object.
It's also clear to me how these scenarios differ. The Edit
action is mapping properties from a ViewModel onto a new, unattached Article
object, whereas, ArticleController
is dealing with an object retrieved directly from the context. That leaves me with the feeling I should refactor one of those controller methods so the steps taken to update are the same. I'm just not really sure how I should even approach that as both approaches seem like they should be able to coexist to me. So my question is, what can I change to get both types of update to work correctly?
Thanks for your time and I'm really sorry for the amount of code posted. I just feel it is all relevant to the problem.
发布评论
评论(1)
两种方法之间的主要区别在于,Admin
Edit
方法从 AdminEditViewModel 创建一篇新文章,然后将此新创建的文章附加到您的数据库。这是有效的,因为它是一个从未附加到 dc 的新对象。在第二种情况下,您从存储库获取一篇文章,更新该文章,然后尝试再次附加它,这会失败,因为它不是新创建的文章,它是首先从数据库上下文返回的文章,因此它已经附加。并且您正在尝试再次附加它。
The primary difference between your two methods is that the Admin
Edit
method creates a new Article from your AdminEditViewModel, then it attaches this newly created Article to your database. This works because it's a new object that has never been attached to a dc.In the second case, you get an Article from the repository, update that Article, then try and attach it again, this fails because it's not a newly created Article, it's an Article returned from the db Context in the first place so it's already attached. and you are trying to attach it again.