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

C# 属性类型更改时不更新鉴别器,c#,entity-framework-6,C#,Entity Framework 6,我有一个实体,它有一个抽象类型的属性。这将创建一个一对一的关系,该关系使用每个层次结构的表继承。看起来一切正常 我可以创建一个项目并将Base属性设置为ConcreteOne;一切都保存正确。但是,当我尝试将Base更新为ConcreteTwo时,EF使用新的用户值更新数据库中的Base记录,它不会更新类型的鉴别器。因此,ConcreteTwo的额外数据被持久化,但鉴别器仍然显示ConcreteOne 下面是一个暴露问题的简单示例 namespace ConsoleApplication1 {

我有一个实体,它有一个抽象类型的属性。这将创建一个一对一的关系,该关系使用每个层次结构的表继承。看起来一切正常

我可以创建一个项目并将
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; }
    }
}
所以它几乎是正确的,但我希望在update语句中看到
[Discriminator]='ConcreteTwo'
。我的期望是没有根据的还是我做错了什么

作为一个测试,我尝试使用每种类型的表,该条目从
ConcreteOne
表中删除,并按照我的预期添加到
ConcreteTwo
表中。所以它是可行的,但我的实际应用程序至少有七个子类型,用于检索
Base
属性的SQL语句变得非常糟糕。因此,如果可能的话,我当然希望使用TPH来实现这一点

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

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

我希望这将创建一个新实例(记录),其中包含一个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();
}

将此添加到您的模型中:

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");
}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
modelBuilder.Entity()
.ToTable(“基础”);
modelBuilder.Entity()
.Map(t=>t.Requires(m=>m.BaseType).Equals(BaseType.ConcreteOne))
.ToTable(“混凝土一号”);
modelBuilder.Entity()
.Map(t=>t.Requires(m=>m.BaseType).Equals(BaseType.ConcreteTwo))
.ToTable(“两个”);
}

如果EF支持从一种类型更新到另一种类型。真糟糕!讨厌的,讨厌的。看起来它的工作方式与预期一致,没有更新。为什么它不支持更改类型?举一个简单的例子,如果一个人有一个主要交通工具类型的财产,那么让他们把它从汽车换成船难道没有意义吗?汽车不能换成船。如果一个人的主要交通方式改变了,他不会神奇地把他的汽车变成一艘船。他买了第二种交通工具。如果你使用的是
初级交通工具
,那么这是一种糟糕的设计。我同意这个人的车不会变成船,但他们的主要交通工具可以。我将使用
primarytransport
作为属性,它将是
Vehicle
类型。我不在乎EF是否想要删除ConcreteOne记录并创建ConcreteTwo记录;那很好。我的观点是,它既没有使用TPH,也没有使用TPT。我会查看论坛,看看是否有其他人有这个问题,但我已经证实问题也发生在EF5中。另外,我真的只是希望我的新对象得到持久化,旧对象被删除。我沿着更新路径启动,因为EF在将我的更改保存到基表时正在生成update语句。我还发生了其他事情。以下代码有效吗?(我现在不能自己运行)我希望这也能起作用;然而,事实并非如此。此代码将导致PK异常,因为它试图插入新的ConcreteTwo记录而不首先删除原始ConcreteOne记录。对于一对一关系,从属记录的PK应等于主记录。如果我在设置
item.Base=newBase
之前添加
item.Base.item=null
行,EF将原始标记标记为已删除,将新标记为已添加。但随后它将其转化为一个更新声明…抱歉,我忽略了一对一,尽管它被声明了。。。我的错;我以家长的身份在多对一中阅读Base。我想说这是当时的预期行为。如果要将属性基更改为新的Concrete2实例,那么原始Concrete1实例将指向哪个项实例?我会说这是一个模型问题,而不是EF问题。我希望ConcreteOne的原始实例会消失;因此,ConcreteOne的存在将不再引用任何项目。明确地说,我的意图是扔掉ConcreteOne的实例,并用ConcreteTwo的实例完全替换它。这将导致EF使用每种类型的表,这是我想要避免的。我已经验证了TPT是否能按预期工作,但我正在尝试使用TPH来实现这一点。
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");
}