【问题标题】:Converting a EF CodeFirst Base class to a Inherited class (using table-per-type)将 EF Code First Base 类转换为 Inherited 类(使用 table-per-type)
【发布时间】:2012-10-11 15:53:49
【问题描述】:

我使用的是 EF Code First,并定义了两个类,如下所示:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
}

[Table("Visitors")]
public class Visitor : User
{
    public Visitor()
    {
        Favourites = new List<Building>();
    }
    public virtual IList<Building> Favourites { get; set; }
}

这使用 Table-Per-Type 继承并定义 DB 架构如下:

Users Table
    Id int PK
    Username nvarchar(max)
    Email nvarchar(max)
Visitors Table
    Id int PK (FK to Users table)

这正是我想要的结构。 现在我的问题是,如果我创建一个用户对象并将其保存到数据库中,我以后如何能够将其扩展为访问者(如果需要?)我是否需要删除用户并创建一个新的访问者,或者我可以如何将用户转换为访问者对象,并且用户表中的条目将保持不变,并且将新条目添加到引用用户的访问者表中?类似下面的代码?

Context.Set<User>().Add(new User(){Id=1, Username="Bob", Email="bob@mail.bob"});
Context.SaveChanges();

//and elsewhere in the project I want to do this sort of thing:
Context.Set<Visitor>().Where(v=>v.Id == 1).FirstOrDefault().Favourites.Add(someFavouriteBuilding); //This obviously doesn't work, because the FirstOrDefault call returns null, so it will throw an exception
Context.SaveChanges();

//or maybe this can be modified slightly to work?:
var visitor = Context.Set<Visitor>().Where(v=>v.Id == 1).FirstOrDefault();
if (visitor==null)
{
    visitor = new Visitor(Context.Set<User>().Where(u=>u.Id == 1).FirstOrDefault()); // this contructor copies all the property values accross and returns a new object
}
visitor.Favourites.Add(someFavouriteBuilding); //This obviously doesn't work either
var entry = Context.Entry(visitor);
entry.State = EntityState.Modified;//here it throws this error: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Context.SaveChanges();

我认为,如果我只能将上面代码中的第二种方法正确附加到上下文中,它可能会起作用。无论如何,上面的代码只是向您展示我想要实现的目标。我知道这行不通。谁能提出更优雅的方法?

谢谢

【问题讨论】:

    标签: c# .net entity-framework ef-code-first table-per-type


    【解决方案1】:

    几乎在那里...关键是分离现有实体,然后附加新实体。

    这是一个例子:

    using System.Data;
    using System.Data.Entity;
    using System.Diagnostics;
    
    public class Animal
    {
        public long Id { get; set; }
    }
    
    public class Dog : Animal
    {
    }
    
    public class AnimalsContext : DbContext
    {
        public DbSet<Animal> Animals { get; set; }
    }
    
    
    public class Tester
    {
        public void Test()
        {
            var context = new AnimalsContext();
    
    
            var genericAnimal = new Animal();
            context.Animals.Add(genericAnimal);
            context.SaveChanges();
    
    
            // Make a new clean entity, but copy the ID (important!)
            var dog = new Dog { Id = genericAnimal.Id, };
    
            // Do the old switch-a-roo -- detach the existing one and attach the new one
            // NOTE: the order is important!  Detach existing FIRST, then attach the new one
            context.Entry(genericAnimal).State = EntityState.Detached;
            context.Entry(dog).State = EntityState.Modified;
            context.SaveChanges();
    
    
            var thisShouldBeADog = context.Animals.Find(genericAnimal.Id);
    
            // thisShouldBeADog is indeed a Dog!
            Debug.Assert(thisShouldBeADog is Dog);
    
            // And, of course, all the IDs match because it's the same entity
            Debug.Assert((genericAnimal.Id == dog.Id) && (dog.Id == thisShouldBeADog.Id));
        }
    }
    

    【讨论】:

    • 我喜欢你的回答,但保留(重用)ID 的目的是什么?这与创建新 Dog 并删除旧 Animal 的结果不同吗?谢谢。
    • 抱歉,我有一个问题。如果 Id 像自动增量一样由数据库生成会怎样?
    • @Chalky 是的,它绝对会有不同的结果——它将是一个完全不同的实体。目标是重用相同的实体,即表中的 AKA 行。创建一个新实体(狗)将在表中创建一个新行,删除旧动物将删除原始行。
    • @jlvaquero 没关系 - 只有当实体还没有 id(即 .NET 默认 long 值为 0 的 id)时才会生成 id。现在,如果您将 Id 属性设置为只读以强制自动生成并且不允许任何人以编程方式设置它......是的,那是行不通的。尝试将 setter 公开为内部以允许对其进行设置。
    • 这不会将 Discriminator 更改为 Dog。它仍然是动物。为什么会这样?
    【解决方案2】:

    我会创建一条新记录并删除旧记录。我认为您不应该有效地更改现有对象的类,也不应该删除和创建尝试重用数据库键的记录。

    数据库主键应该是无意义的。如果您需要为您的记录分配一个有意义的 ID,请为此添加一个新字段。想想你的堆栈溢出 ID,我敢打赌这不是他们数据库中的主键。

    【讨论】:

    • 好的,谢谢,在某些情况下,这可能是更好的方法。如果有很多与该用户条目相关的数据怎么办?然后你必须去修改所有相关的数据。然后这个功能会破坏你更新数据库结构的所有东西,通过另一个连接到用户表。
    猜你喜欢
    • 1970-01-01
    • 2015-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多