21

Code First EF 5ベータ版を使用するアプリケーションの場合、次のようになります。

public class ParentObject
{
    public int Id {get; set;}
    public virtual List<ChildObject> ChildObjects {get; set;}
    //Other members
}

public class ChildObject
{
    public int Id {get; set;}
    public int ParentObjectId {get; set;}
    //Other members
}

関連するCRUD操作は、必要に応じてリポジトリによって実行されます。

OnModelCreating(DbModelBuilder modelBuilder)

私はそれらを設定しました:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
            .WithOptional()
            .HasForeignKey(c => c.ParentObjectId)
            .WillCascadeOnDelete();

したがって、aParentObjectが削除されると、そのChildObjectsも削除されます。

ただし、実行した場合:

parentObject.ChildObjects.Clear();
_parentObjectRepository.SaveChanges(); //this repository uses the context

例外が発生します:

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

エンティティの定義には、破られている外部キー制約が含まれているため、これは理にかなっています。

孤立したときにエンティティを「クリア」するように構成できますか、またはChildObjectコンテキストからこれらを手動で削除する必要があります(この場合はChildObjectRepositoryを使用します)。

4

7 に答える 7

33

これは実際にサポートされていますが、識別関係を使用する場合に限ります。最初にコードでも機能します。ChildObjectとの両方Idを含む複雑なキーを定義する必要がありますParentObjectId

modelBuilder.Entity<ChildObject>()
            .HasKey(c => new {c.Id, c.ParentObjectId});

このようなキーを定義すると、自動インクリメントIDのデフォルトの規則が削除されるため、手動で再定義する必要があります。

modelBuilder.Entity<ChildObject>()
            .Property(c => c.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

ここで、parentObject.ChildObjects.Clear()を呼び出すと、依存オブジェクトが削除されます。

ところで。WithRequiredFKがnull許容でない場合はオプションではないため、リレーションマッピングは実際のクラスを追跡するために使用する必要があります。

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
            .WithRequired()
            .HasForeignKey(c => c.ParentObjectId)
            .WillCascadeOnDelete();
于 2012-05-31T17:18:00.607 に答える
4

アップデート:

子から親エンティティにナビゲーションプロパティを追加したり、複雑なキーを設定したりする必要がない方法を見つけました。

これは、を使用して削除されたエンティティを検索するこの記事に基づいています。ObjectStateManager

リストが手元にあると、削除された関係を表す、それぞれからObjectStateEntryのペアを見つけることができます。EntityKey

この時点で、どれを削除する必要があるかを示すものは見つかりませんでした。また、記事の例とは逆に、子が親に戻るナビゲーションプロパティを持っている場合は、2番目のものを選択するだけで親が削除されます。それで、それを修正するために、どのタイプをクラスで処理する必要があるかを追跡しますOrphansToHandle

モデル:

public class ParentObject
{
    public int Id { get; set; }
    public virtual ICollection<ChildObject> ChildObjects { get; set; }

    public ParentObject()
    {
        ChildObjects = new List<ChildObject>();
    }
}

public class ChildObject
{
    public int Id { get; set; }
}

他のクラス:

public class MyContext : DbContext
{
    private readonly OrphansToHandle OrphansToHandle;

    public DbSet<ParentObject> ParentObject { get; set; }

    public MyContext()
    {
        OrphansToHandle = new OrphansToHandle();
        OrphansToHandle.Add<ChildObject, ParentObject>();
    }

    public override int SaveChanges()
    {
        HandleOrphans();
        return base.SaveChanges();
    }

    private void HandleOrphans()
    {
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;

        objectContext.DetectChanges();

        var deletedThings = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).ToList();

        foreach (var deletedThing in deletedThings)
        {
            if (deletedThing.IsRelationship)
            {
                var entityToDelete = IdentifyEntityToDelete(objectContext, deletedThing);

                if (entityToDelete != null)
                {
                    objectContext.DeleteObject(entityToDelete);
                }
            }
        }
    }

    private object IdentifyEntityToDelete(ObjectContext objectContext, ObjectStateEntry deletedThing)
    {
        // The order is not guaranteed, we have to find which one has to be deleted
        var entityKeyOne = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[0]);
        var entityKeyTwo = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[1]);

        foreach (var item in OrphansToHandle.List)
        {
            if (IsInstanceOf(entityKeyOne, item.ChildToDelete) && IsInstanceOf(entityKeyTwo, item.Parent))
            {
                return entityKeyOne;
            }
            if (IsInstanceOf(entityKeyOne, item.Parent) && IsInstanceOf(entityKeyTwo, item.ChildToDelete))
            {
                return entityKeyTwo;
            }
        }

        return null;
    }

    private bool IsInstanceOf(object obj, Type type)
    {
        // Sometimes it's a plain class, sometimes it's a DynamicProxy, we check for both.
        return
            type == obj.GetType() ||
            (
                obj.GetType().Namespace == "System.Data.Entity.DynamicProxies" &&
                type == obj.GetType().BaseType
            );
    }
}

public class OrphansToHandle
{
    public IList<EntityPairDto> List { get; private set; }

    public OrphansToHandle()
    {
        List = new List<EntityPairDto>();
    }

    public void Add<TChildObjectToDelete, TParentObject>()
    {
        List.Add(new EntityPairDto() { ChildToDelete = typeof(TChildObjectToDelete), Parent = typeof(TParentObject) });
    }
}

public class EntityPairDto
{
    public Type ChildToDelete { get; set; }
    public Type Parent { get; set; }
}

元の回答

複雑なキーを設定せずにこの問題を解決するには、SaveChangesのをオーバーライドしますが、孤立したオブジェクトを見つけるためにデータベースへのアクセスを回避するためにDbContext使用します。ChangeTracker

まず、ナビゲーションプロパティをに追加しChildObjectます(必要に応じてプロパティを保持できint ParentObjectIdます。どちらの方法でも機能します)。

public class ParentObject
{
    public int Id { get; set; }
    public virtual List<ChildObject> ChildObjects { get; set; }
}

public class ChildObject
{
    public int Id { get; set; }
    public virtual ParentObject ParentObject { get; set; }
}

次に、以下を使用して孤立したオブジェクトを探しますChangeTracker

public class MyContext : DbContext
{
    //...
    public override int SaveChanges()
    {
        HandleOrphans();
        return base.SaveChanges();
    }

    private void HandleOrphans()
    {
        var orphanedEntities =
            ChangeTracker.Entries()
            .Where(x => x.Entity.GetType().BaseType == typeof(ChildObject))
            .Select(x => ((ChildObject)x.Entity))
            .Where(x => x.ParentObject == null)
            .ToList();

        Set<ChildObject>().RemoveRange(orphanedEntities);
    }
}

構成は次のようになります。

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
            .WithRequired(c => c.ParentObject)
            .WillCascadeOnDelete();

10.000回繰り返す簡単な速度テストを行いました。有効にHandleOrphans()すると、完了までに1:01.443分かかり、無効にすると、0:59.326分でした(どちらも平均3回の実行です)。以下のテストコード。

using (var context = new MyContext())
{
    var parentObject = context.ParentObject.Find(1);
    parentObject.ChildObjects.Add(new ChildObject());
    context.SaveChanges();
}

using (var context = new MyContext())
{
    var parentObject = context.ParentObject.Find(1);
    parentObject.ChildObjects.Clear();
    context.SaveChanges();
}
于 2017-02-05T04:19:14.553 に答える
2

私のために働いた別の.netefコアソリューションを共有したいのですが、誰かがそれが役に立つと思うかもしれません。

2つの外部キー(またはのいずれか)を持つ子テーブルがあったため、受け入れられたソリューションは機能しませんでした。マルコス・ディミトリオの答えに基づいて、私は次のことを思いついた。

私のカスタムDbContextでは:

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
  {
    var modifiedEntities = this.ChangeTracker.Entries().Where(c => c.State == EntityState.Modified);
    foreach (var entityEntry in modifiedEntities)
    {
      if (entityEntry.Entity is ChildObject)
      {
         var fkProperty = entityEntry.Property(nameof(ChildObject.ParentObjectId));
         if (fkProperty.IsModified && fkProperty.CurrentValue == null && fkProperty.OriginalValue != null)
         {
           // Checked if FK was set to NULL
           entityEntry.State = EntityState.Deleted;
         }
      }
    }

    return await base.SaveChangesAsync(cancellationToken);
  }
于 2019-07-01T11:35:49.740 に答える
2

EFコアでは、孤立を削除することで実行できます。

このような:

dbContext.Children.Clear();
于 2020-03-03T18:35:48.473 に答える
1

はい。以下はEFコアで機能します。

カスケード動作を次Cascadeのように設定してください。

entity.HasOne(d => d.Parent)
                    .WithMany(p => p.Children)
                    .HasForeignKey(d => d.ParentId)
                    .OnDelete(DeleteBehavior.Cascade);

次に、Parent次のように、削除するすべての子エンティティでプロパティをnullに設定します。

var childrenToBeRemoved = parent.Children.Where(filter);
foreach(var child in childrenToBeRemoved)
{
    child.Parent = null;
}

ここでcontext.SaveAsync()、孤立した子エンティティをすべて削除する必要があります。

于 2021-04-19T14:35:05.763 に答える
1

これは、特定のスキーマの知識がなくても、EntityFramework6.4.4の一般的なソリューションです。

私の場合、他の回答が示唆するように、削除された関係エントリを検索するものが見つからなかったため、変更されたエンティティエントリから孤立したエンティティの検索を開始することに注意してください。

このアプローチの背後にあるロジックは、必要な関係のコレクションから削除されたエンティティの外部キーがEntityFrameworkによってnullに更新されることです。したがって、多重度「One」で終わりに少なくとも1つの関係があるが、外部キーがnullに設定されているすべての変更されたエンティティを検索します。

DbContextこのメソッドをサブクラスに追加します。SaveChanges/メソッドをオーバーライドして、SaveChangesAsyncこのメソッドを自動的に呼び出すことができます。

public void DeleteOrphanEntries()
{
  this.ChangeTracker.DetectChanges();

  var objectContext = ((IObjectContextAdapter)this).ObjectContext;

  var orphanEntityEntries =
    from entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
    where !entry.IsRelationship
    let relationshipManager = entry.RelationshipManager
    let orphanRelatedEnds = from relatedEnd in relationshipManager.GetAllRelatedEnds().OfType<EntityReference>()
                            where relatedEnd.EntityKey == null // No foreign key...
                            let associationSet = (AssociationSet)relatedEnd.RelationshipSet
                            let associationEndMembers = from associationSetEnd in associationSet.AssociationSetEnds
                                                        where associationSetEnd.EntitySet != entry.EntitySet // ... not the end pointing to the entry
                                                        select associationSetEnd.CorrespondingAssociationEndMember
                            where associationEndMembers.Any(e => e.RelationshipMultiplicity == RelationshipMultiplicity.One) // ..but foreign key required.
                            select relatedEnd
    where orphanRelatedEnds.Any()
    select entry;

  foreach (var orphanEntityEntry in orphanEntityEntries)
  {
    orphanEntityEntry.Delete();
  }
}

于 2021-06-30T02:16:52.237 に答える
-2

これは、現在EFによって自動的にサポートされているものではありません。これを行うには、コンテキストでSaveChangesをオーバーライドし、親がなくなった子オブジェクトを手動で削除します。コードは次のようになります。

public override int SaveChanges()
{
    foreach (var bar in Bars.Local.ToList())
    {
        if (bar.Foo == null)
        {
            Bars.Remove(bar);
        }
    }

    return base.SaveChanges();
}
于 2012-05-31T15:26:53.363 に答える