3

私はBusinessCategoryモデルを持っています。

それぞれには、公開されたコレクションを介しBusinessて多くのものがあります (エンティティは無視されます)。CategoriesCategoryBusiness

ここに私のコントローラーアクションがあります:

[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ものに設定されているものがいくつかあります(のプロパティがありません)。CategoryIdCategoryTitleCategory

サーバーからヒットSaveChangesしてリロードした後、そこにはありません。BusinessCategories

したがって、私の質問はBusiness.Categories、既存CategoryIdの s の特定の配列を設定する適切な方法は何かということです。

Businessただし、新しいものを作成するDbUpdateExceptionと、呼び出し時に次の例外がスローされますSaveChanges

リレーションシップの外部キー プロパティを公開しないエンティティの保存中にエラーが発生しました。単一のエンティティを例外のソースとして識別できないため、EntityEntries プロパティは null を返します。保存中の例外の処理は、エンティティ タイプで外部キー プロパティを公開することで簡単に行うことができます。詳細については、InnerException を参照してください。

内部例外 ( OptimisticConcurrencyException):

Store update、insert、または delete ステートメントが予期しない数の行 (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既存/新規にアタッチしているBusinesses は既に DB に存在し、アタッチに使用している ID を持っています。

4

3 に答える 3

5

あなたが言及した2つの問題は異なる理由があるため、既存のビジネスの更新と新しいビジネスの追加の2つのケースを別々に扱います。

既存の事業体の更新

あなたの例ではそうですifif (business.BusinessId > 0))。CategoryオブジェクトとBusinessエンティティをアタッチして を呼び出すだけなので、ここでは何も起こらず、データベースに変更が保存されないことは明らかですSaveChanges。アタッチとは、エンティティが状態のコンテキストに追加され、Unchangedその状態にあるエンティティの場合、EF はデータベースにコマンドをまったく送信しないことを意味します。

デタッチされたオブジェクト グラフを更新する場合 (Businessさらに、エンティティのコレクションを追加Categoryする場合)、通常、コレクション アイテムがコレクションから削除され、アイテムが追加される可能性があるという問題があります - に格納されている現在の状態と比較してデータベース。コレクション アイテムのプロパティと、親エンティティが変更されている可能性もありますBusiness。オブジェクト グラフがデタッチされている間、すべての変更を手動で追跡していない限り (つまり、EF 自体が変更を追跡できませんでした)、これはブラウザー UI でこれを行う必要があったため Web アプリケーションでは困難であり、正しい 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");
}

新しい事業体の追加

Business関連するものと一緒に新しいものを追加する手順Categoriesは正しいです。すべてを既存のエンティティとしてコンテキストにアタッチしCategories、新しいエンティティをコンテキストに追加するだけBusinessです。

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

アタッチしているすべてのCategoriesものが実際にデータベースに存在するキー値を持っている場合、これは例外なく機能するはずです。

あなたの例外は、少なくとも 1 つにCategories無効なキー値がある (つまり、データベースに存在しない) ことを意味します。その間に DB から削除されたか、Web UI から正しくポストバックされていない可能性があります。

独立した関連付け (FK プロパティBusinessIdを持たない関連付け) の場合Category、実際にこれが得られますOptimisticConcurrencyExceptionBusinessId(ここでは、EF は別のユーザーによってカテゴリが DB から削除されたと想定しているようです。) 外部キーの関連付け (FK プロパティを持つ関連付け) の場合Category、外部キー制約違反に関する例外が発生します。 .

この例外を回避したい場合 -が空でCategoryあるためではなく、別のユーザーが a を削除したために実際に発生する場合(代わりに非表示の入力フィールドでこれを修正してください) - あなたはより良いカテゴリをアタッチする代わりに( ) でデータベースからロードし、存在しない場合は無視してコレクションから削除します (または、エラー ページにリダイレクトしてユーザーに通知するなど)。Category0CategoryIdFindbusiness.Categories

于 2013-06-16T15:35:17.970 に答える
0

私にもこの例外がありました。私の問題は、追加されたオブジェクトの主キーが ADO​​ Entity Framework によって設定されていなかったことです。これにより、データベースに追加されたオブジェクトの外部キーも設定できないという問題が発生しました。

主キーがデータベース自体によって設定されていることを確認することで、この問題を解決しました。SQL Server を使用している場合は、CREATE TABLE ステートメントの列宣言に IDENTITY キーワードを追加することで、これを行うことができます。

お役に立てれば

于 2013-06-22T18:39:24.427 に答える
0

MaxLength 属性を超えて設定しようとしていたフィールドで、この例外が発生していました。フィールドには Required 属性もありました。私は Oracle バックエンド データベースに対して作業しています。長さを増やして初めて、基礎となるデータベース エンジンから、長さが長すぎるという説明的なエラー メッセージが表示されました。外部キー関連のエラー メッセージは、挿入の 1 つが失敗し、キーが更新されなかったため、すべての関係を更新できなかったことを漠然と意味していると思われます。

于 2013-10-23T20:31:10.480 に答える