3

我有一个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");
}

现在有几个business.Categories将其CategoryId设置为现有的Category(缺少 tho 的Title属性Category)。

击中SaveChanges并重新加载Business从服务器后,Categories不存在。

所以我的问题是Business.Categories用给定的现有数组设置的正确方法是什么CategoryId

但是,当创建一个新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);
}

打电话时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;
    }
  }
}

再次说明一下,Category我附加到现有/新Business的 es 已经存在于数据库中并且有一个 ID,这是我用来附加它的。

4

3 回答 3

5

我分别处理这两种情况——更新现有业务和添加新业务——因为你提到的两个问题有不同的原因。

更新现有业务实体

在您的示例中就是这种if情况 ( if (business.BusinessId > 0))。很明显,这里什么都没有发生,也不会将任何更改存储到数据库中,因为您只是附加Category对象和Business实体,然后调用SaveChanges. 附加意味着实体以状态添加到上下文中Unchanged,对于处于该状态的实体,EF 根本不会向数据库发送任何命令。

如果你想更新一个分离的对象图 -Business加上Category你的情况下的实体集合 - 你通常会遇到一个问题,即一个集合项可能已从集合中删除并且可能已添加一个项 - 与存储在中的当前状态相比数据库。集合项的属性和父实体也可能Business已被修改。除非您在分离对象图时手动跟踪所有更改 - 即 EF 本身无法跟踪更改 - 这在 Web 应用程序中很困难,因为您必须在浏览器 UI 中执行此操作,您唯一的机会执行正确的更新整个对象图将其与数据库中的当前状态进行比较,然后将对象置于正确的状态AddedDeletedModified(也许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");
}

添加新的业务实体

您添加新Business的及其相关的程序Categories是正确的。只需将所有Categories实体作为现有实体附加到上下文中,然后将新实体添加Business到上下文中:

foreach (var category in business.Categories)
    Context.Categories.Attach(category);
Context.Businesses.Add(business);
Context.SaveChanges();

如果Categories您附加的所有内容确实具有数据库中存在的键值,那么这应该毫无例外地工作。

您的异常意味着至少其中一个Categories具有无效的键值(即它在数据库中不存在)。可能它同时已从数据库中删除,或者因为它没有从 Web UI 正确回发。

如果是独立关联——即没有 FK 属性的关联BusinessId——Category你确实会得到这个OptimisticConcurrencyException。(EF 似乎在这里假设该类别已被另一个用户从数据库中删除。)在外键关联的情况下 - 这是一个具有 FK 属性的关联BusinessId-Category你会得到一个关于外键约束违规的异常.

如果你想避免这个异常 - 如果它实际上是因为另一个用户删除了 a Category,而不是因为它Category是空的/0因为它没有被发回服务器(改为使用隐藏的输入字段来解决这个问题) - 你最好通过CategoryId( ) 从数据库中加载类别Find而不是附加它们,如果不再存在,则忽略它并将其从business.Categories集合中删除(或重定向到错误页面以通知用户或类似的东西)。

于 2013-06-16T15:35:17.970 回答
0

我在尝试设置超出其 MaxLength 属性的字段上遇到此异常。该字段还具有必需属性。我正在使用 Oracle 后端数据库。直到我增加了长度,我才最终从底层数据库引擎收到一条描述性错误消息,表明长度太长了。我怀疑与外键相关的错误消息模糊地意味着并非所有关系都可以更新,因为其中一个插入失败并且没有更新其密钥。

于 2013-10-23T20:31:10.480 回答
0

我也有这个例外。我的问题是添加对象的主键不是由 ADO 实体框架设置的。这导致了数据库中添加对象的外键也无法设置的问题。

我通过确保数据库本身设置主键来解决这个问题。如果您使用的是 SQL Server,您可以通过在 CREATE TABLE 语句中添加 IDENTITY 关键字和列声明来实现。

希望这可以帮助

于 2013-06-22T18:39:24.427 回答