web-dev-qa-db-ja.com

多対多の関係が保存されていない

EF5コードファーストで作成したかなり標準的な多対多の関係を持つ2つのエンティティがあります。これらはServiceとServiceItemです。 ServiceエンティティにはServiceItemのコレクションが含まれ、ServiceItemにはServicesのコレクションが含まれます。どちらのエンティティの基本プロパティにも問題なくデータを作成、変更、保存できます。 ServiceItemをServiceに、またはServiceをServiceItemに追加しようとすると、機能しているように見えますが、何も保存されません。クロスキーを持つServiceItemServiceテーブルを含め、すべての適切なデータベーステーブルが作成されていることを確認しました。アイテムを追加しても、データベースのServiceItemServiceテーブルにエントリがありません。エラーはなく、他のすべては完全に機能しているようです。

私は少し困惑していて、いくつかの助けを使うことができます。以下はクラスです。

サービスクラス。

public class Service
{
    //Default constructor
    public Service()
    {
        //Defaults
        IsActive = true;
        ServicePeriod = ServicePeriodType.Monthly;
        ServicePeriodDays = 0;

        ServiceItems = new Collection<ServiceItem>();
    }

    public int ServiceID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public ICollection<ServiceItem> ServiceItems { get; set; }
    public string TermsOfService { get; set; }
    public ServicePeriodType ServicePeriod { get; set; }
    public int ServicePeriodDays { get; set; }
    public bool IsActive { get; set; }
}

ServiceItemクラス。

public class ServiceItem
{
    public ServiceItem()
    {
        IsActive = true;
    }

    public int ServiceItemID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public ICollection<Service> Services { get; set; }
    public string UserRole { get; set; }
    public bool IsActive { get; set; }

}

これは、この問題をデバッグしようとしたときに行ったFluentマッピングです。このマッピングを追加する前後に同じ問題が発生しました。

    public DbSet<Service> Services { get; set; }
    public DbSet<ServiceItem> ServiceItems { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Service>()
           .HasMany(p => p.ServiceItems)
           .WithMany(r => r.Services)
           .Map(mc =>
           {
               mc.MapLeftKey("ServiceItemID");
               mc.MapRightKey("ServiceID");
               mc.ToTable("ServiceItemService");
           });
    }

これは、Service.ServiceItemsコレクションに2〜3個のServiceItemを含むServiceアイテムを保存するために使用するコードです。 ServiceItemが適切なコレクションに含まれていることを注意深く確認しました。

                db.Entry(dbService).State = EntityState.Modified;
                db.SaveChanges();

DbServiceオブジェクトは影響を受けていないようです。 ServiceItemsは引き続き適切なコレクションにありますが、ServiceItemServiceデータベーステーブルは更新されません。どんなアドバイスでも大歓迎です。

-ありがとう

20
drobertson

何も起こらないことが期待されます。

変更または追加したいのは、エンティティServiceServiceItemの間の関係です。ただし、エンティティの状態をModifiedに設定して関係を操作することはできません。これは、スカラープロパティと複雑なプロパティのみを更新し、ナビゲーションプロパティ(=関係)は更新しません。 (たとえば、Serviceエンティティの状態をModifiedに設定すると、Service.TitleService.Descriptionなどが変更済みとしてマークされ、これらのプロパティがデータベースに確実に保存されます。 。ただし、Service.ServiceItemsの内容は関係ありません。)

状態をModifiedに設定してrelationshipを変更できる唯一の例外は、Foreign Key Associationsです。これらは、モデルエンティティで公開されている外部キープロパティを持つ関連付けであり、1対多または1対1の関連付けでのみ発生します。多対多の関係は常に独立した関連付けです。つまり、エンティティ内に外部キープロパティを持つことはできません。 (FKは結合テーブルにありますが、結合テーブルはエンティティではなく、モデルクラスから「非表示」になっているためです。)

多対多の関連付けの関係を直接操作する方法はありますが、ObjectContextとそのRelationshipManagerに移動する必要があります。これは、私の意見では、かなり高度で注意が必要です。

多対多の関連付けとの間で関係エントリを追加および削除する通常の簡単な方法は、コレクションにアイテムを追加したり、コレクションからアイテムを削除したりすることですエンティティがアタッチされている間コンテキストに。 EFの変更追跡メカニズムは、SaveChangesを呼び出すと、行った変更を認識し、適切なINSERT、UPDATE、およびDELETEステートメントを生成します。

正確な手順は、Serviceおよび/またはServiceItemも新しいエンティティとして保存するか、既存のエンティティ間の関係のみを追加するかによって異なります。次にいくつかの例を示します。

  • serviceを挿入し、すべてのserviceItemsを挿入し、エンティティ間の関係も結合テーブルに挿入する必要があります。

    using (var context = new MyContext())
    {
        var service = new Service();
        var serviceItem1 = new ServiceItem();
        var serviceItem2 = new ServiceItem();
        service.ServiceItems.Add(serviceItem1);
        service.ServiceItems.Add(serviceItem2);
    
        context.Services.Add(service);
    
        context.SaveChanges();
    }
    

    オブジェクトグラフの「ルート」serviceを追加するだけで十分です。これは、EFがグラフ内の他のすべてのエンティティがコンテキストにアタッチされていないことを認識し、データベースに挿入する必要があると想定するためです。

  • serviceはすでに存在するため、挿入しないでください。すべてのserviceItemを挿入し、エンティティ間の関係も結合テーブルに挿入する必要があります。

    using (var context = new MyContext())
    {
        var service = new Service { ServiceID = 15 };
        context.Services.Attach(service);
    
        var serviceItem1 = new ServiceItem();
        var serviceItem2 = new ServiceItem();
        service.ServiceItems.Add(serviceItem1);
        service.ServiceItems.Add(serviceItem2);
    
        context.SaveChanges();
    }
    

    EFは、ここで(SaveChangesが呼び出されたとき)serviceがアタッチされていることを認識しますが、他のエンティティはアタッチされていません。 serviceのINSERTは発生しませんが、serviceItem1/2はリレーションシップエントリと一緒にINSERTされます。

  • serviceはすでに存在するため、挿入しないでください。すべてのserviceItemは既に存在するため、挿入しないでください。ただし、エンティティ間の関係は、結合テーブルに挿入する必要があります。

    using (var context = new MyContext())
    {
        var service = new Service { ServiceID = 15 };
        context.Services.Attach(service);
    
        var serviceItem1 = new ServiceItem { ServiceItemID = 23 };
        context.ServiceItems.Attach(serviceItem1);
    
        var serviceItem2 = new ServiceItem { ServiceItemID = 37 };
        context.ServiceItems.Attach(serviceItem2);
    
        service.ServiceItems.Add(serviceItem1);
        service.ServiceItems.Add(serviceItem2);
    
        context.SaveChanges();
    }
    
  • 完全を期すために:どのように削除既存のエンティティ間の関係?

    using (var context = new MyContext())
    {
        var service = context.Services
            .Include(s => s.ServiceItems) // load the existing Items
            .Single(s => s.ServiceID == 15);
    
        var serviceItem1 = service.ServiceItems
            .Single(s => s.ServiceItemID == 23); // query in memory, no DB query
        var serviceItem2 = service.ServiceItems
            .Single(s => s.ServiceItemID == 37); // query in memory, no DB query
    
        service.ServiceItems.Remove(serviceItem1);
        service.ServiceItems.Remove(serviceItem2);
    
        context.SaveChanges();
    }
    

    サービス15をserviceItem23および37にリンクする結合テーブルの2つの関係行が削除されます。

または、Attachを呼び出す代わりに、データベースから既存のエンティティをロードできます。それも機能します:

var service = context.Services.Single(s => s.ServiceID == 15);

また、既存のServiceItemsについても同じです。

64
Slauma