3

抽象型のプロパティを持つエンティティがあります。これにより、階層ごとのテーブルの継承を使用する 1 対 1 の関係が作成されます。すべてが正しく機能しているようです。

Item を作成し、BaseプロパティをConcreteOne;に設定できます。すべてが正しく保存されます。ただし、更新しようとするBaseConcreteTwo、EFBaseはデータベース内のレコードを新しいユーザー値で更新しますが、型の識別子は更新されません。したがって、 の余分なデータConcreteTwoは永続化されますが、ディスクリミネーターはまだConcreteOne.

以下は、問題を明らかにする簡単な例です

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            App_Start.EntityFrameworkProfilerBootstrapper.PreStart();

            Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>());

            // Create our item with ConcreteOne for Base
            using (var context = new DataContext())
            {
                var item = new Item
                    {
                        Base = new ConcreteOne { Name = "Item", Data = 3 }
                    };
                context.Items.Add(item);
                context.SaveChanges();
            }

            // Update Base with a new ConcreteTwo
            using (var context = new DataContext())
            {
                var item = context.Items.FirstOrDefault();

                var newBase = new ConcreteTwo()
                    {
                        Item = item,  
                        Name = "Item 3", 
                        User = new User { Name = "Foo" }
                    };

                // If I don't set this to null, EF tries to create a new record in the DB which causes a PK exception
                item.Base.Item = null;  
                item.Base = newBase;

                // EF doesn't save the discriminator, but DOES save the User reference
                context.SaveChanges();
            }

            // Retrieve the item -- EF thinks Base is still ConcreteOne
            using (var context = new DataContext())
            {
                var item = context.Items.FirstOrDefault();
                Console.WriteLine("{0}: {1}", item.Name, item.Base.Name);
            }

            Console.WriteLine("Done.");
            Console.ReadLine();
        }
    }

    public class DataContext : DbContext
    {
        public DbSet<Item> Items { get; set; }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual Base Base { get; set; }
    }

    public abstract class Base
    {
        public int Id { get; set; }
        public string Name { get; set; }

        [Required]
        public virtual Item Item { get; set; }
    }

    public class ConcreteOne : Base
    {
        public int Data { get; set; }
    }

    public class ConcreteTwo : Base
    {
        public virtual User User { get; set; }
    }
}

変更が保存されると、EF は次の SQL を生成します。

update [dbo].[Bases]
set    [Name] = 'Item 3' /* @0 */,
       [User_Id] = 1 /* @1 */
where  (([Id] = 1 /* @2 */)
        and [User_Id] is null)

[Discriminator] = 'ConcreteTwo'したがって、ほぼ正しいですが、更新ステートメントで確認できると思います。私の期待は根拠のないものですか、それとも何か間違ったことをしていますか?

テストとして、table-per-type を使用してみましたが、予想どおり、エントリがテーブルから削除され、ConcreteOneテーブルに追加されました。ConcreteTwoそれでうまくいきますが、私の実際のアプリケーションには少なくとも 7 つのサブタイプがあり、Baseプロパティを取得するための SQL ステートメントは非常に厄介です。可能であれば、TPHを使用してこれを実現したいと思います。

更新: 問題が EF5 と EF6 に存在することを確認しました。

4

3 に答える 3

0

これをモデルに追加します。

public enum BaseType
{
    ConcreteOne = 1,
    ConcreteTwo = 2
}

public abstract class Base
{
   ...
   public BaseType BaseType { get; set; }
   ...
}

そして OnModelCreating メソッドでは:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Base>()
                .ToTable("Base");

    modelBuilder.Entity<ConcreteOne>()
                .Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteOne))
                .ToTable("ConcreteOne");

    modelBuilder.Entity<ConcreteTwo>()
                .Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteTwo))
                .ToTable("ConcreteTwo");
}
于 2013-08-09T03:39:14.783 に答える
-1

これにより、ConcreteTwo ディスクリミネーターを使用して新しいインスタンス (レコード) が作成されることが期待されます。

using (var context = new DataContext())
{
    var item = context.Items.FirstOrDefault();
    var newBase = new ConcreteTwo()
    {
        Name = "Item 3", 
        User = new User { Name = "Foo" }
    };

    item.Base = newBase;

    context.SaveChanges();
}
于 2013-08-09T02:13:07.943 に答える