Shi*_*mmy 3 c# many-to-many entity-framework foreign-keys ef-code-first
我有一个Business和一个Category模型.
每个Business都有很多Categories通过暴露的集合(Category无视Business实体).
现在这是我的控制器动作:
[HttpPost]
[ValidateAntiForgeryToken]
private ActionResult Save(Business business)
{
//Context is a lazy-loaded property that returns a reference to the DbContext
//It's disposal is taken care of at the controller's Dispose override.
foreach (var category in business.Categories)
Context.Categories.Attach(category);
if (business.BusinessId > 0)
Context.Businesses.Attach(business);
else
Context.Businesses.Add(business);
Context.SaveChanges();
return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)
现在有几个business.Categories已经CategoryId设置为现有Category(缺少tho 的Title属性Category).
从服务器点击SaveChanges并重新加载后Business,Categories不存在.
所以我的问题是Business.Categories使用给定的现有CategoryIds 数组设置的正确方法是什么.
Business但是,在创建新的时,DbUpdateException调用时会抛出以下异常SaveChanges:
保存不公开其关系的外键属性的实体时发生错误.EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源.通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常.有关详细信息,请参阅InnerException.
内部异常(OptimisticConcurrencyException):
存储更新,插入或删除语句会影响意外的行数(0).自实体加载后,实体可能已被修改或删除.刷新ObjectStateManager条目.
回答后,这是更新代码:
var storeBusiness = IncludeChildren().SingleOrDefault(b => b.BusinessId == business.BusinessId);
var entry = Context.Entry(storeBusiness);
entry.CurrentValues.SetValues(business);
//storeBusiness.Categories.Clear();
foreach (var category in business.Categories)
{
Context.Categories.Attach(category);
storeBusiness.Categories.Add(category);
}
Run Code Online (Sandbox Code Playgroud)
打电话时SaveChanges,我得到以下内容DbUpdateException:
保存不公开其关系的外键属性的实体时发生错误.EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源.通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常.有关详细信息,请参阅InnerException.
以下是业务/类别模型的外观:
public class Business
{
public int BusinessId { get; set; }
[Required]
[StringLength(64)]
[Display(Name = "Company name")]
public string CompanyName { get; set; }
public virtual BusinessType BusinessType { get; set; }
private ICollection<Category> _Categories;
public virtual ICollection<Category> Categories
{
get
{
return _Categories ?? (_Categories = new HashSet<Category>());
}
set
{
_Categories = value;
}
}
private ICollection<Branch> _Branches;
public virtual ICollection<Branch> Branches
{
get
{
return _Branches ?? (_Branches = new HashSet<Branch>());
}
set
{
_Branches = value;
}
}
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Unique]
[Required]
[MaxLength(32)]
public string Title { get; set; }
public string Description { get; set; }
public int? ParentCategoryId { get; set; }
[Display(Name = "Parent category")]
[ForeignKey("ParentCategoryId")]
public virtual Category Parent { get; set; }
private ICollection<Category> _Children;
public virtual ICollection<Category> Children
{
get
{
return _Children ?? (_Children = new HashSet<Category>());
}
set
{
_Children = value;
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了再次说清楚,Category我附加到现有/新的Businesses已经存在于DB中并且有一个ID,这就是我用它来附加它.
我将这两种情况 - 更新现有业务和添加新业务 - 分开处理,因为您提到的两个问题有不同的原因.
在你的例子中就是这种if情况(if (business.BusinessId > 0)).很明显,这里没有任何事情发生,并且不会将更改存储到数据库中,因为您只是附加Category对象和Business实体然后调用SaveChanges.附加意味着实体被添加到状态中的上下文中,Unchanged并且对于处于该状态的实体,EF将根本不向数据库发送任何命令.
如果你想更新一个分离的对象图 - Business加上Category你的案例中的实体集合- 你通常会遇到这样的问题:集合项可能已从集合中删除而且一个项可能已添加 - 与存储在该集合中的当前状态相比数据库.集合项的属性和父实体也可能Business已被修改.除非您在分离对象图时手动跟踪所有更改 - 即EF本身无法跟踪更改 - 这在Web应用程序中很难,因为您必须在浏览器UI中执行此操作,这是您执行正确UPDATE的唯一机会整个对象图将它与数据库中的当前状态进行比较,然后将对象置于正确的状态Added,Deleted和Modified(也许Unchanged对于其中的一些).
因此,过程是从数据库加载Business包含其当前值Categories,然后将分离图的更改合并到已加载(=附加)的图中.它可能看起来像这样:
private ActionResult Save(Business business)
{
if (business.BusinessId > 0) // = business exists
{
var businessInDb = Context.Businesses
.Include(b => b.Categories)
.Single(b => b.BusinessId == business.BusinessId);
// Update parent properties (only the scalar properties)
Context.Entry(businessInDb).CurrentValues.SetValues(business);
// Delete relationship to category if the relationship exists in the DB
// but has been removed in the UI
foreach (var categoryInDb in businessInDb.Categories.ToList())
{
if (!business.Categories.Any(c =>
c.CategoryId == categoryInDb.CategoryId))
businessInDb.Categories.Remove(categoryInDb);
}
// Add relationship to category if the relationship doesn't exist
// in the DB but has been added in the UI
foreach (var category in business.Categories)
{
var categoryInDb = businessInDb.Categories.SingleOrDefault(c =>
c.CategoryId == category.CategoryId)
if (categoryInDb == null)
{
Context.Categories.Attach(category);
businessInDb.Categories.Add(category);
}
// no else case here because I assume that categories couldn't have
// have been modified in the UI, otherwise the else case would be:
// else
// Context.Entry(categoryInDb).CurrentValues.SetValues(category);
}
}
else
{
// see below
}
Context.SaveChanges();
return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)
添加新内容Business及其相关内容的过程Categories是正确的.只需将所有Categories现有实体附加到上下文中,然后将新内容添加Business到上下文中:
foreach (var category in business.Categories)
Context.Categories.Attach(category);
Context.Businesses.Add(business);
Context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
如果Categories您所附加的所有内容确实都具有数据库中存在的键值,则该应该可以正常工作.
您的异常意味着至少有一个Categories具有无效的键值(即它在数据库中不存在).也许它同时从数据库中被删除,或者因为它没有从Web UI正确发回.
在一个独立的协会的情况下-也就是没有FK属性关联BusinessId的Category-你这确实OptimisticConcurrencyException.(EF似乎这里假设类别已经从DB由另一个用户被删除.)在一个外键关联的情况下-这是具有FK属性关联BusinessId的Category-你会得到有关违反外键约束的例外.
如果你想避免这个异常 - 如果它实际上是因为另一个用户删除了一个Category,而不是因为它Category是空的/ 0因为它没有被发回服务器(用一个隐藏的输入字段修复它) - 你最好通过CategoryId(Find)从数据库加载类别而不是附加它们,如果不存在,则忽略它并将其从business.Categories集合中删除(或重定向到错误页面以通知用户或类似的东西).
| 归档时间: |
|
| 查看次数: |
4759 次 |
| 最近记录: |