属性类型更改时不更新鉴别器

本文关键字:更新 鉴别 类型 属性 | 更新日期: 2023-09-27 18:26:59

我有一个实体,它有一个抽象类型的属性。这将创建一个一对一的关系,该关系使用按层次结构继承的表。一切似乎都正常工作。

我可以创建一个Item并将Base属性设置为ConcreteOne;一切都保存正确。然而,当我尝试将Base更新为ConcreteTwo时,EF使用新的用户值更新数据库中的Base记录,它不会更新类型的鉴别器。因此,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表中,正如我所期望的那样。所以它是有效的,但我的实际应用程序至少有七个子类型,并且检索Base属性的SQL语句变得非常糟糕。因此,如果可能的话,我当然想使用TPH来实现这一点。

更新:我已经验证了EF5和EF6中都存在问题。

属性类型更改时不更新鉴别器

这个问题是基于对更新的预期,这似乎是一个有争议的预期。目前,如果TPH层次结构没有按预期运行,并且考虑到EF6目前处于测试阶段,你最好的选择是在Codeplex论坛上开始讨论。

将其添加到您的模型中:

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");
}

我希望这能创建一个带有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();
}