1つ以上の外部キープロパティがnull可能ではないため、関係を変更できませんでした


192

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

操作が失敗しました:1つ以上の外部キープロパティがnull可能ではないため、関係を変更できませんでした。リレーションシップが変更されると、関連する外部キープロパティはnull値に設定されます。外部キーがnull値をサポートしていない場合は、新しい関係を定義するか、foreign-keyプロパティに別のnull以外の値を割り当てるか、無関係なオブジェクトを削除する必要があります。

私はこの行を完全に理解していません:

1つ以上の外部キープロパティがnull可能ではないため、関係を変更できませんでした。

2つのエンティティ間の関係を変更するのはなぜですか?アプリケーション全体の存続期間を通して、同じままである必要があります。

例外が発生するコードは、コレクション内の変更された子クラスを既存の親クラスに割り当てるだけです。これにより、子クラスの削除、新しいクラスの追加、変更が可能になります。Entity Frameworkがこれを処理すると思ったでしょう。

コード行は、次のように抽出できます。

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

私は以下の記事でソリューション#2を使用して自分の答えを見つけました。基本的に、親テーブルを参照するために子テーブルに主キーを追加しました(そのため、2つの主キー(親テーブルの外部キーとID)があります。 )子テーブルのため。c-sharpcorner.com/UploadFile/ff2f08/...
yougotiger

@jaffa、私はここに私の答えが見つからstackoverflow.com/questions/22858491/...
アントニオ

回答:


159

古い子アイテムを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、コードは少し異なります。)また、シンプルにするために、すべてのリポジトリ抽象化を削除しました。

それが良い解決策であるかどうかはわかりませんが、ナビゲーションコレクションのあらゆる種類の変更に対応するために、これらの線に沿って何らかのハードワークを行う必要があると思います。より簡単な方法を教えていただければ幸いです。


では、一部のみが変更された場合はどうなりますか?つまり、それらを削除して再度追加する必要があるということですか?
jaffa

@ジョン:いいえ、もちろん既存のアイテムを更新することもできます。子コレクションを更新する方法の例を追加しました。上記の編集セクションを参照してください。
スラウマ

@Slauma:笑、あなたがあなたの答えを変更するつもりなら、私は私の答えを書きません...
Ladislav Mrnka

@Ladislav:いいえ、いいえ、あなたがあなた自身の答えを書いてくれてうれしいです。少なくとも私はそれが完全なナンセンスではなく、私が上記でやったことよりも複雑すぎることを知っています。
スラウマ

1
foreachでoriginalChildItemを取得するときに条件を追加します:... Where(c => c.ID == childItem.ID && c.ID!= 0)そうでない場合、childItem.IDの場合、新しく追加された子が返されます== 0
perfect_element

116

これに直面している理由は、構成集計の違いによるものです。

コンポジションでは、子オブジェクトは親が作成されるときに作成され、親が破棄されるときに破棄されます。したがって、その存続期間は親によって制御されます。例:ブログの投稿とそのコメント。投稿が削除された場合、そのコメントは削除されます。存在しない投稿へのコメントは意味がありません。注文と注文アイテムについても同じです。

集約では、子オブジェクトはその親に関係なく存在できます。親が破棄されても、後で別の親に追加される可能性があるため、子オブジェクトは引き続き存在できます。例:プレイリストとそのプレイリストの曲との関係。プレイリストが削除されても、曲は削除されません。それらは別のプレイリストに追加できます。

Entity Frameworkが集約と構成の関係を区別する方法は次のとおりです。

  • 合成の場合:子オブジェクトが複合主キー(ParentID、ChildID)を持つことを期待します。子のIDは親のスコープ内にある必要があるため、これは仕様によるものです。

  • 集約の場合:子オブジェクトの外部キープロパティがnull可能であることを期待しています。

したがって、この問題が発生するのは、子テーブルに主キーを設定した方法が原因です。それは複合であるべきですが、そうではありません。そのため、Entity Frameworkはこの関連付けを集計と見なします。つまり、子オブジェクトを削除またはクリアしても、子レコードは削除されません。関連付けを削除し、対応する外部キー列をNULLに設定するだけです(これらの子レコードは後で別の親に関連付けることができます)。列でNULLが許可されていないため、前述の例外が発生します。

ソリューション:

1-複合キーを使用したくない強い理由がある場合は、子オブジェクトを明示的に削除する必要があります。そして、これは以前に提案されたソリューションよりも簡単に行うことができます:

context.Children.RemoveRange(parent.Children);

2-それ以外の場合、子テーブルに適切な主キーを設定することにより、コードはより意味のあるものになります。

parent.Children.Clear();

9
この説明が最も役に立ちました。
Booji Boy 2015

7
コンポジションとアグリゲーションの適切な説明と、エンティティフレームワークとの関係。
Chrysalis

#1は、問題を修正するために必要な最小限のコードでした。ありがとうございました!
ryanulit 2016年

73

これは非常に大きな問題です。あなたのコードで実際に起こることはこれです:

  • Parentデータベースから読み込み、アタッチされたエンティティを取得します
  • その子コレクションを、分離された子の新しいコレクションで置き換えます
  • 変更を保存しますが、この操作中、EFはこの時点までEFがそれらについて知らなかったため、すべての子が追加されたと見なされます。したがって、EFは古い子の外部キーにnullを設定し、すべての新しい子を挿入しようとします=>重複行。

今、解決策は本当にあなたが何をしたいか、そしてあなたがそれをどのようにしたいかに依存しますか?

ASP.NET MVCを使用している場合は、UpdateModelまたはTryUpdateModelを使用してみてください。

既存の子を手動で更新するだけの場合は、次のようにすることができます。

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

アタッチは実際には必要ありません(状態をに設定するModifiedとエンティティもアタッチされます)が、プロセスがより明確になるので気に入っています。

既存のものを変更し、既存のものを削除し、新しい子を挿入したい場合は、次のようなことをする必要があります:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();

1
しかし、を使用することについての興味深い見解があります.Clone()。にChildItemは他のサブ子ナビゲーションプロパティがあるということを覚えていますか?しかし、その場合、子自体が新しい場合、すべてのサブ子が新しいオブジェクトであると予想されるので、サブグラフ全体がコンテキストに関連付けられないのではないでしょうか。(まあ、モデルごとに異なる場合がありますが、子が親から依存しているように、サブ子が子から「依存」していると仮定しましょう。)
Slauma

おそらく「スマート」クローンが必要です。
Ladislav Mrnka、2011

1
あなたのコンテキストに子のコレクションを持ちたくない場合はどうでしょうか?http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed 2013年

1
parent.ChildItems.Remove(child); context.Childs.Remove(child); この二重削除の修正により問題が発生する可能性があります。両方の削除が必要なのはなぜですか?子は子としてしか生きないので、なぜparent.ChildItemsからだけを削除するのが難しいのですか?
フェルナンドトーレス

40

私はこのエラーが同じエラーに対してはるかに役立つことを発見しました。あなたが削除するとき、EFはそれを好まないようです、それは削除を好みます。

このようにして、レコードに添付されているレコードのコレクションを削除できます。

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

この例では、注文に添付されたすべての詳細レコードの状態が削除に設定されています。(注文の更新の一部として、更新された詳細を元に戻す準備として)


それが正しい答えだと思います。
デスマティ2018

論理的で簡単なソリューション。
sairfan 2018年

19

他の2つの回答がなぜそれほど人気が​​あるのか​​、私にはわかりません!

私はあなたがORMフレームワークがそれを処理するべきだと仮定するのは正しかったと信じています-結局のところ、それはそれが提供することを約束するものです。そうしないと、永続性の問題によりドメインモデルが破損します。カスケード設定を正しくセットアップすると、NHibernateはこれをうまく管理します。Entity Frameworkでは、データベースモデルを設定するときに、より良い標準に従うことを期待することも可能です。

識別関係」を使用して、親子関係を正しく定義する必要があります

これを行うと、Entity Frameworkは子オブジェクトが親によって識別されることを認識しているため、「カスケード削除孤児」の状況である必要があります。

上記以外に、必要になる場合あります(NHibernateエクスペリエンスから)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

リストを完全に置き換えるのではなく。

更新

@Slaumaのコメントは、分離されたエンティティが全体的な問題の別の部分であることを思い出させました。これを解決するには、コンテキストからモデルをロードすることでモデルを構築するカスタムモデルバインダーを使用する方法があります。このブログ投稿は私が何を意味するかの例を示しています。


問題のシナリオは切り離されたエンティティ(「MVCビューから取得した新しいリスト」)を処理する必要があるため、関係を特定するように設定してもここでは役に立ちません。それでも、DBから元の子をロードし、分離されたコレクションに基づいてそのコレクションから削除されたアイテムを見つけて、DBから削除する必要があります。唯一の違いは、識別関係ではのparent.ChildItems.Remove代わりに呼び出すことができるということです_dbContext.ChildItems.Remove。他の回答のような長いコードを回避するために、EFからの組み込みサポートはまだ(EF <= 6)ありません。
Slauma 2013年

要点を理解しました。ただし、コンテキストからエンティティをロードするか、新しいインスタンスを返すカスタムモデルバインダーでは、上記のアプローチが機能すると思います。私は答えを更新して、その解決策を提案します。
Andre Luus 2013年

はい、モデルバインダーを使用することはできますが、今はモデルバインダーの他の回答から何かを行う必要がありました。問題をリポジトリ/サービスレイヤーからモデルバインダーに移動するだけです。少なくとも、簡単なことはわかりません。
Slauma 2013年

単純化は孤立したエンティティの自動削除です。モデルバインダーに必要なのは、一般的な同等のものですreturn context.Items.Find(id) ?? new Item()
Andre Luus

EFチームへのフィードバックは良好ですが、提案されたソリューションではEFランドの問題を解決できません。
Chris Moschini、2014

9

AutoMapperとEntity Frameworkを同じクラスで使用している場合、この問題が発生する可能性があります。たとえば、クラスが

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

これにより、両方のプロパティがコピーされます。この場合、ClassBIdはnull可能ではありません。AutoMapperはコピーするのでdestination.ClassB = input.ClassB;するため、これは問題を引き起こします。

AutoMapperをIgnore ClassBプロパティに設定します。

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId

AutoMapperで同様の問題に直面していますが、これは私にとっては機能しません:( stackoverflow.com/q/41430679/613605
J86

4

同じエラーが発生しました。親子関係のある2つのテーブルがありますが、子テーブルのテーブル定義の外部キー列に「削除時のカスケード」を構成しました。そのため、データベースで(SQLを介して)親行を手動で削除すると、自動的に子行が削除されます。

ただし、これはEFでは機能せず、このスレッドで説明されているエラーが表示されました。これは、エンティティデータモデル(edmxファイル)で、親テーブルと子テーブルの関連付けのプロパティが正しくなかったためです。End1 OnDeleteオプションが可能に構成されましたnone(私のモデルで「END1」は1の多重度を持っている端部です)。

私は手動でEnd1 OnDeleteオプションをに変更し、Cascadeそれが機能しました。データベースからモデルを更新するときに、EFがこれを取得できない理由がわかりません(データベースの最初のモデルがあります)。

完全性のために、これは私の削除するコードがどのように見えるかです:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

カスケード削除を定義していない場合、親行を削除する前に、子行を手動で削除する必要があります。


4

これは、子エンティティが削除ではなく変更としてマークされているために発生します。

また、EF parent.Remove(child)が実行されたときにEFが子エンティティに対して行う変更は、単にその親への参照をに設定することnullです。

実行後に例外が発生したときにVisual Studioのイミディエイトウィンドウに次のコードを入力すると、子のEntityStateを確認できますSaveChanges()

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

ここで、Xは削除されたエンティティーで置き換える必要があります。

ObjectContextexecute toへのアクセス権がない場合は_context.ChildEntity.Remove(child)、外部キーを子テーブルの主キーの一部にすることで、この問題を解決できます。

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

このようにして、を実行するとparent.Remove(child)、EFはエンティティを削除済みとして正しくマークします。


2

このタイプのソリューションは私にとってトリックを行いました:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

これにより、すべてのレコードが削除され、再度挿入されるということが重要です。しかし、私の場合(10未満)は問題ありません。

お役に立てば幸いです。


再挿入は新しいIDで行われますか、それとも最初から子供のIDを保持しますか?
Pepito Fernandez 2014

2

今日この問題に遭遇し、私の解決策を共有したいと思いました。私の場合、解決策は、データベースから親を取得する前に子アイテムを削除することでした。

以前は以下のコードのようにしていた。次に、この質問に記載されているのと同じエラーが発生します。

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

私にとってうまくいったのは、最初にparentId(外部キー)を使用して子アイテムを取得してから、それらのアイテムを削除することです。次に、データベースから親を取得できます。その時点で、子アイテムはなくなっているはずであり、新しい子アイテムを追加できます。

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here

2

ChildItemsコレクションを手動でクリアして、新しいアイテムを追加する必要があります。

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

その後、孤立したエンティティを処理するDeleteOrphans拡張メソッドを呼び出すことができます(DetectChangesメソッドとSaveChangesメソッドの間で呼び出す必要があります)。

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}

これは私にはうまくいきました。追加するだけですcontext.DetectChanges();
アンディエディンバラ

1

私はこれらのソリューションや他の多くのソリューションを試しましたが、どれもうまくいきませんでした。これはグーグルの最初の答えなので、ここに私の解決策を追加します。

私にとってうまくいった方法は、コミット中に関係を写真から取り除くことでした。そのため、EFが失敗することはありませんでした。これを行うには、DBContextで親オブジェクトを再検索し、それを削除しました。再検出されたオブジェクトのナビゲーションプロパティはすべてnullであるため、子の関係はコミット時に無視されます。

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

これは、外部キーがON DELETE CASCADEで設定されていることを前提としているため、親行が削除されると、子はデータベースによってクリーンアップされます。


1

私はMoshのソリューションを使用しましたが、最初にコードに正しく構成キーを実装する方法がわかりませんでした。

だからここに解決策があります:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}

1

私は同じ問題を抱えていましたが、他の場合でも問題なく機能することを知っていたので、問題を次のように減らしました。

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItemsには複合主キー(parentId +いくつかのローカル列)があり、問題なく動作しました
  • ProblematicItemsは独自の単一列の主キーがあり、parentIdはFK のみでした。これにより、Clear()の後で例外が発生していました。

私がしなければならなかったのは、ParentIdを複合PKの一部にして、親がなければ子が存在できないことを示すことだけでした。私はDBファーストモデルを使用し、PK 追加して、parentId列をEntityKeyとしてマークしました(したがって、DBとEFの両方で更新する必要がありました。EFだけで十分かどうかはわかりません)。

RequestIdをPKの一部にしました そして、EFモデルを更新し、エンティティキーの一部として他のプロパティを設定します

一度考えると、EFは非常にエレガントな違いであり、EFは親なしで子供が「理にかなっている」かどうかを判断するために使用します(この場合、Clear()はそれらを削除せず、ParentIdを他の/特別な値に設定しない限り例外をスローします) )、または-元の質問と同様に、親から削除されたアイテムは削除されると予想されます。


0

この問題は、子テーブルのデータがまだ存在しているにもかかわらず、親テーブルを削除しようとしたために発生します。カスケード削除を使用して問題を解決します。

モデルのdbcontextクラスのCreateメソッド。

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

その後、API呼び出しで

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

カスケード削除オプションは、この単純なコードで親および親に関連する子テーブルを削除します。この簡単な方法で試してみてください。

データベース内のレコードのリストを削除するために使用された範囲の削除


0

私もモッシュの答えで問題を解決しました、そして私はピーターBの答えを考えましたは、列挙型を外部キーとして使用していたので、少しだけ。このコードを追加した後、新しい移行を追加する必要があることに注意してください。

他の解決策としてこのブログ投稿をお勧めすることもできます。

http://www.kianryan.co.uk/2013/03/orphaned-child/

コード:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

0

Slaumaのソリューションを使用して、子オブジェクトと子オブジェクトのコレクションの更新に役立ついくつかの汎用関数を作成しました。

すべての永続オブジェクトはこのインターフェースを実装しています

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

これで、これら2つの関数をリポジトリに実装しました

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

それを使用するには、次のようにします。

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

お役に立てれば


追加:別のDbContextExtentions(または独自のコンテキストインターフェイス)クラスを作成することもできます。

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

そしてそれを次のように使用します:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

また、これらの機能を使用してコンテキストの拡張子のクラスを作ることができる:
Bluemoon74

0

私のレコードを削除しようとすると、いくつかの問題が発生したのと同じ問題に直面しました。この問題の解決策は、ヘッダー/マスターレコードを削除する前にレコードを削除しようとすると、何かが欠けているということです。ヘッダー/マスターの前に詳細を削除してください。問題が解決することを願っています。


-1

私は数時間前にこの問題に遭遇し、すべてを試しましたが、私の場合、解決策は上記のリストとは異なりました。

データベースから既に取得されたエンティティを使用してその子を変更しようとすると、エラーが発生しますが、データベースからエンティティの新しいコピーを取得する場合は問題ありません。これは使わないでください:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

これを使って:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.