30

EF での更新中に次のエラーが発生します。

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

上記のエラーの原因となっている外部キー プロパティを見つける一般的な方法はありますか?

[アップデート]

上記のエラーの原因となる次のコードの 1 つのケース (切断された環境で作業していたので、以前graphdiffはオブジェクト グラフを更新していました) を実行したい場合_uow.Commit();:

public void CopyTechnicalInfos(int sourceOrderItemId, List<int> targetOrderItemIds)
{
  _uow = new MyDbContext();
   var sourceOrderItem = _uow.OrderItems
          .Include(x => x.NominalBoms)
          .Include("NominalRoutings.NominalSizeTests")
          .AsNoTracking()
          .FirstOrDefault(x => x.Id == sourceOrderItemId);


   var criteria = PredicateBuilder.False<OrderItem>();
   foreach (var targetOrderItemId in orderItemIds)
   {
      int id = targetOrderItemId;
      criteria = criteria.OR(x => x.Id == id);
   }
   var targetOrderItems = _uow.OrderItems
                              .AsNoTracking()
                              .AsExpandable()   
                              .Where(criteria)
                              .ToList();

  foreach (var targetOrderItem in targetOrderItems)
  {
        //delete old datas and insert new datas 
        targetOrderItem.NominalBoms = sourceOrderItem.NominalBoms;
        targetOrderItem.NominalBoms.ForEach(x => x.Id = 0);

        targetOrderItem.NominalRoutings = sourceOrderItem.NominalRoutings;
        targetOrderItem.NominalRoutings.ForEach(x => x.Id = 0);
        targetOrderItem.NominalRoutings
                       .ForEach(x => x.NominalTests.ForEach(y => y.Id = 0));
        targetOrderItem.NominalRoutings
                       .ForEach(x => x.NominalSizeTests.ForEach(y => y.Id = 0));
       _uow.OrderItems.UpdateGraph(targetOrderItem, 
                                   x => x.OwnedCollection(y => y.NominalBoms)
                                         .OwnedCollection(y => y.NominalRoutings, 
                                          with => with
                                         .OwnedCollection(t => t.NominalTests)));
   }
   _uow.Commit();
}
4

1 に答える 1

64

Entity Framework では、外部キーの関連付けを操作できます。つまり、別のオブジェクトへの外部キーは、プリミティブな外部キー プロパティ ( などNominalRouting.OrderItemId) とオブジェクト参照 ( NominalRouting.OrderItem) の 2 つのプロパティのペアとして表現されます。

これは、プリミティブ値またはオブジェクト参照を設定して、外部キーの関連付けを確立できることを意味します。それらのいずれかを設定すると、可能であれば、EF は他の同期を維持しようとします。残念ながら、これにより、プリミティブな外部キー値とそれに付随する参照の間で競合が発生する可能性もあります。

あなたのケースで正確に何が起こるかを伝えるのは難しいです。ただし、ある親から別の親にオブジェクトを「コピー」するというあなたのアプローチが...理想的ではないことは知っていますまず、主キーの値を変更することは決して良い考えではありません。それらを設定すると0、オブジェクトが新品のように見えますが、そうではありません。次に、同じ子オブジェクトを他の親オブジェクトに何度も割り当てます。結果として、外部キーの値はあるが参照ではない多数のオブジェクトになってしまうと思います

「コピー」と言ったのは、それがあなたが達成しようとしているように見えるからです。その場合は、オブジェクトとAddそれらをそれぞれに適切に複製する必要がありますtargetOrderItem。同時に、なぜこれらすべてのオブジェクトを (どうやら) クローン化したのだろうかと思います。ここでは、多対多の関連付けの方が適しているようです。しかし、それは別の主題です。

さて、あなたの実際の質問:競合する関連付けを見つける方法は?

それはとても、とても難しいことです。概念モデルを検索し、外部キーの関連付けに関連するプロパティを見つけるコードが必要になります。次に、それらの値を見つけて、不一致を見つける必要があります。十分に難しいですが、競合の可能性実際の競合である場合を判断することに比べれば些細なことです。これを 2 つの例で明確にします。ここで、クラスには、プロパティとOrderItemで構成される必須の外部キーの関連付けがあります。OrderOrderId

var item = new OrderItem { OrderId = 1, ... };
db.OrderItems.Add(item);
db.SaveChanges();

したがって、 OrderIdand = null が割り当てられたアイテムがOrderあり、EF は満足しています。

var item = db.OrderItems.Include(x => x.Order).Find(10);
// returns an OrderItem with OrderId = 1
item.Order = null;
db.SaveChanges();

再び、OrderIdand = null が割り当てられたアイテムOrderですが、EF は「関係を変更できませんでした...」という例外をスローします。

(そして、より多くの競合状況が発生する可能性があります)

したがって、ペアで一致しない値を探すだけでは十分ではなくOrderId/Order、エンティティの状態を調べて、どの状態の組み合わせで不一致が許可されないかを正確に知る必要があります。私のアドバイス:忘れて、コードを修正してください。

ただし、汚いトリックが 1 つあります。EF が外部キーの値と参照を照合しようとすると、入れ子になったifs のツリーのどこかで、競合を のメンバー変数に収集しObjectStateManagerます_entriesWithConceptualNulls。いくつかのリフレクションを行うことで、その値を取得できます。

#if DEBUG

db.ChangeTracker.DetectChanges(); // Force EF to match associations.
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var objectStateManager = objectContext.ObjectStateManager;
var fieldInfo = objectStateManager.GetType().GetField("_entriesWithConceptualNulls", BindingFlags.Instance | BindingFlags.NonPublic);
var conceptualNulls = fieldInfo.GetValue(objectStateManager);

#endif

conceptualNullsHashSet<EntityEntry>EntityEntryあり、内部クラスであるため、デバッガーでコレクションを調べて、エンティティの競合を把握することしかできません。診断目的のみ!!!

于 2015-09-26T23:43:57.863 に答える