I am using EF Code First and have two classes defined as follows:
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; }
}
This uses Table-Per-Type inheritance and defines the DB schema as follows:
Users Table
Id int PK
Username nvarchar(max)
Email nvarchar(max)
Visitors Table
Id int PK (FK to Users table)
This is exactly how I wanted it to structure it. Now my question is, if I create a User object and save it to the DB, how will I later on be able to extend that into a Visitor (if I need to?) Will I need to delete the user and create a new Visitor or can I some how cast the user into a visitor object and the entry in the user table will remain intact and a new entry will be added to the visitor table referencing the user? Something like the below code?
Context.Set<User>().Add(new User(){Id=1, Username="Bob", Email="[email protected]"});
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();
I think the second approach in the above code may work if I can only attach it to the context correctly. Anyway, the code above is only to show you what I am trying to achieve. I know it will not work. Can anyone suggest a more elegent approach?
Thank you
You were almost there... The key is to detach the existing entity, then attach the new one.
Here's an example:
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));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With