抽象型のプロパティを持つエンティティがあります。これにより、階層ごとのテーブルの継承を使用する 1 対 1 の関係が作成されます。すべてが正しく機能しているようです。
Item を作成し、Base
プロパティをConcreteOne
;に設定できます。すべてが正しく保存されます。ただし、更新しようとするBase
とConcreteTwo
、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 に存在することを確認しました。