1. ホーム
  2. entity-framework

[解決済み】1つ以上の外部キーのプロパティが非NULLであるため、リレーションシップを変更できませんでした。

2022-04-10 10:12:26

質問

あるエンティティでGetById()を行い、子エンティティのコレクションをMVCビューから来る私の新しいリストに設定すると、このエラーが発生します。

操作に失敗しました。その リレーションシップを変更できませんでした。 というのは、1つ以上の外部キー プロパティが NULL 値でない。このような場合 リレーションシップに変更が加えられると 関連する foreign-key プロパティが設定されます。 はヌル値である。foreign-keyがNull値でない場合 はNULL値をサポートしないので、新しい リレーションシップを定義し プロパティに割り当てる必要があります。 別の非NULL値、または を削除しなければならない。

この行がよくわからないのですが。

リレーションシップを変更できませんでした というのは、1つ以上の foreign-key プロパティが NULL 以外である。

なぜ2つのエンティティの関係を変更しなければならないのですか?アプリケーション全体のライフタイムを通じて同じであるべきです。

例外が発生したコードは、コレクション内の変更された子クラスを既存の親クラスに割り当てるという単純なものです。 これにより、子クラスの削除、新しいクラスの追加、および修正に対応できることを期待しています。 私はEntity Frameworkがこれを処理すると思っていました。

コードの行は、次のように集約されます。

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

解決方法は?

古い子項目を削除する必要があります thisParent.ChildItems を1つずつ手作業で行っています。Entity Frameworkはそれをやってくれません。古い子項目をどうしたいのか、捨てたいのか、それとも残して他の親エンティティに割り当てたいのか、最終的に決めることはできません。Entity Framework にあなたの決定を伝える必要があります。しかし、これらの2つの決定のうちの1つは、子エンティティがデータベース内のどの親にも参照されずに単独で生きることができないので、行わなければなりません(外部キー制約のため)。それは基本的に例外が言うことです。

編集

子項目の追加、更新、削除ができたらどうするか。

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

注:これはテストされていません。これは、子項目コレクションの型が ICollection . (私は通常 IList とすると、コードの見た目が少し変わってきます)。また、シンプルにするために、リポジトリの抽象的なものをすべて取り除きました。

それが良い解決策かどうかは分かりませんが、ナビゲーションコレクションのあらゆる変更に対応するためには、この路線で何らかの苦労をしなければならないと考えています。また、より簡単な方法があれば嬉しいですね。