I am using Entity Framework code first to try and create a database whereby I have several tables that share unique primary keys.
I have the following structure to my domain classes, and would hope that FirstClass, SecondClass, ThirdClass would all be created as tables that link to a BaseTable where the shared keys are stored. I have included the SharedClass class in this example since I also have this strutcure in my project.
[Table("BaseTable")]
public abstract class BaseClass {
public int Id { get; set; }
}
public abstract class SharedClass : BaseClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("FirstClass")]
public abstract class FirstClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("SecondClass")]
public abstract class SecondClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("ThirdClass")]
public abstract class ThirdClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
What is currently happening is that FirstClass, SecondClass and ThirdClass are all being created but with their own primary keys and no BaseTable is being created.
I am not sure if I am missing something after looking through several questions on here that are asking something similar.
The EF inheritance is not activated by default and even by applying Table data annotation on a base class.
It can be activated by either adding a DbSet to the derived DbContext:
public DbSet<BaseClass> BaseClass { get; set; }
which also allows you to use polymorphic queries, or with fluent configuration - the bare minimum needed is:
modelBuilder.Entity<BaseClass>();
Related
What i want is to have a base class and two separate lists of inherited classes.
This is my model:
public class Instance
{
public int InstanceId { get; set; }
public virtual ICollection<User> Users { get; internal set; }
public virtual ICollection<MasterUser> MasterUsers { get; internal set; }
}
public abstract class CoreUser
{
[Key]
public int UserId { get; set; }
public int InstanceId { get; set; }
public virtual Instance Instance { get; set; }
}
[Table("Users")]
public class User : CoreUser
{
public string UserName { get; set; }
}
[Table("MasterUsers")]
public class MasterUser : CoreUser
{
public string MasterUserName { get; set; }
}
This is my DbContext:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<MasterUser> MasterUsers { get; set; }
public DbSet<Instance> Instances { get; set; }
}
This will create 4 tables with TPT inheritance model which is fine. The problem is Users and MasterUsers table will contain foreign key to Instance (it will be called Instance_InstanceId) which is redundant since this FK is defined in the CoreUser base class. These two FK columns are not even populated, they are always NULL, CoreUsers InstanceId column is populated when you add either User or MasterUser.
If I remove both referenced from Instance class like so:
public class Instance
{
public int InstanceId { get; set; }
}
Problem goes away but that also renders my application unusable.
I can also solve my problem like so:
public class Instance
{
public int InstanceId { get; set; }
public virtual ICollection<CoreUser> Users { get; internal set; }
}
And then iterate trough collection filtering out each type of user but this approach will lazy load all of the users even though I just want to iterate trough MasterUsers only.
One possible solution was to use TPC but in reality, CoreUser class will contain FKs to some other Classes which is not supported in TPC (only top level classes in hierarchy can contain FKs).
Is there any way I can get this to work in EF using two separate lists in Instance class and have them lazy loaded?
EDIT
Actually, the above code would work just fine. It will break if you introduce one more class that references CoreUser for example:
public class UserPolicy
{
public int PolicyId { get; set; }
public virtual CoreUser PolicyUser { get; internal set; }
}
Managed to get around this. Solution that I was able to use is to move relationship between CoreUser and Instance to User and MasterUser like so:
public class Instance
{
public int InstanceId { get; set; }
// Still referencing two lists
public virtual ICollection<User> Users { get; internal set; }
public virtual ICollection<MasterUser> MasterUsers { get; internal set; }
}
public abstract class CoreUser
{
[Key]
public int UserId { get; set; }
// No reference to instance. Works if you don't need it from CoreUser
}
[Table("Users")]
public class User : CoreUser
{
// FK to Instance defined in CoreUser
public int InstanceId { get; set; }
public virtual Instance Instance { get; set; }
public string UserName { get; set; }
}
[Table("MasterUsers")]
public class MasterUser : CoreUser
{
// FK to Instance defined in MasterUser
public int InstanceId { get; set; }
public virtual Instance Instance { get; set; }
public string MasterUserName { get; set; }
}
I created an inheritance hierarchy after a few migrations. Now when I update the database using code first migrations, code-first is not automatically creating the discriminator field. I have since dropped the table and recreated it (using code-first migrations) without any luck. The only thing I can think of is that there are no additional "non-virtual" properties in the derived classes--the inheritance structure was created to enforce a business rule that only a certain derived type can have a relationship with another entity.
Base Type:
public abstract class Process
{
private ICollection<ProcessSpecification> _specifications { get; set; }
protected Process()
{
_specifications = new List<ProcessSpecification>();
}
public Int32 Id { get; set; }
public String Description { get; set; }
public Int32 ToolId { get; set; }
public virtual Tool Tool { get; set; }
public virtual ICollection<ProcessSpecification> Specifications
{
get { return _specifications; }
set { _specifications = value; }
}
}
Derived class (no different/unique scalar properties):
public class AssemblyProcess : Process
{
private ICollection<AssemblyProcessComponent> _components;
public AssemblyProcess()
{
_components = new List<AssemblyProcessComponent>();
}
public virtual ICollection<AssemblyProcessComponent> Components
{
get { return _components; }
set { _components = value; }
}
}
Another derived type
public class MachiningProcess : Process
{
private ICollection<MachiningProcessFeature> _features;
public MachiningProcess()
{
_features = new List<MachiningProcessFeature>();
}
public virtual ICollection<MachiningProcessFeature> Features { get { return _features; } set { _features = value; } }
}
Is code-first not adding the discriminator column in the database because it doesn't see any differences between the derived classes (because of there not being any unique "non-virtual" properties)? If so, how do I get around this? If not, what are some reasons why code-first would not automatically create the discriminator column in the database? I have another TPH structure that works exactly the way it's supposed to.
DbContext:
public LineProcessPlanningContext()
: base("LineProcessPlanning")
{
}
public DbSet<Component> Components { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<OperationDefinition> OperationDefinitions { get; set; }
public DbSet<PartDesign> PartDesigns { get; set; }
public DbSet<Process> Processes { get; set; }
public DbSet<ProcessPlan> ProcessPlans { get; set; }
public DbSet<ProcessPlanStep> ProcessPlanSteps { get; set; }
public DbSet<ProductionLine> ProductionLines { get; set; }
public DbSet<StationCycleDefinition> StationCycleDefinitions { get; set; }
public DbSet<StationCycleStep> StationCycleSteps { get; set; }
public DbSet<StationDefinition> StationDefinitions { get; set; }
public DbSet<UnitOfMeasurement> UnitsOfMeasurement { get; set; }
public DbSet<Tool> Tools { get; set; }
I also tried creating "dummy" properties that are unique to each derived type. Code migrations added the new properties as columns to the table, but the migration did not create a discriminator column.
I figured out the cause of this in my situation, same as yours. The base class is abstract, therefore EF won't create a TPH table for that class since it can't be instantiated. As a result of the abstract base class, EF will create tables for each of the derived classes, and therefore no need for a discriminator column.
In my case, it was acceptable to remove abstract from the base class. Once I did this, EF's TPH worked as expected.
I'm struggling with TPT inheritance in MVC - EF.
I have an AAnimal abstract class and two classes that inherit it, Zebra and Lion. There is a Cage class which holds an AAnimal.
My problem is that because AAnimal is abstract, the EF cannot create an instance of it when I load all Cages. So what I want is a way to override this behavior and make it understand whether it needs to load a Zebra or a Lion.
Zebra and Lion have a Primary key which is also foreign key to Animal table. This is done by the EF (TPT inheritance model).
public abstract class AAnimal
{
[Key]
public int AnimalId { set; get; }
public string Name { set; get; }
}
public class Lion : AAnimal {}
public class Zebra : AAnimal {}
public class Εmployee
{
[Key]
public int EmployeeId { set; get; }
public string Name { set; get; }
}
public class Cage
{
[Key]
public int CageId { set; get; }
[ForeignKey("CagedAnimal")]
public int CagedAnimalId { set; get; }
public AAnimal CagedAnimal { set; get; }
[ForeignKey("CageEmployee")]
public int CageEmployeeId { set; get; }
public Employee CageEmployee { set; get; }
}
// Model mapping
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AAnimal>().ToTable("Animal");
modelBuilder.Entity<Lion>().ToTable("Lion");
modelBuilder.Entity<Zebra>().ToTable("Zebra");
modelBuilder.Entity<Εmployee>().ToTable("Εmployee");
}
// Load all cages
public ActionResult Index()
{
var allCages = db.Cages.ToList();
}
At this point all cages are loaded, all fields have values except the CagedAnimal which is null. Even the CagedAnimalId has value.
How can I tell the EF to follow the same procedure while saving data, in order to load entities?
Note that this is just an example. Also, TPT inheritance model has been selected over other inheritance models.
I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");
Getting an error when trying to set a ForeignKeyAttribute in a base class
class User { }
abstract class FruitBase
{
[ForeignKey("CreateById")]
public User CreateBy{ get; set; }
public int CreateById{ get; set; }
}
class Banana : FruitBase { }
class DataContext : DbContext
{
public DbSet<Banana> Bananas { get; set; }
}
If I move the FruitBase code into the banana, all is well, but I don't want to, as there will be many many fruit and I want to remain relatively DRY if I can
Is this a know issue that will be fixed by March?
Does anyone know a work around?
The problem caused by the fact that in your DbContext you put DbSet<Banana> instead of DbSet<FruitBase>. The following object model works as expected:
public class User
{
public int UserId { get; set; }
}
public abstract class FruitBase
{
public int Id { get; set; }
public int CreateById { get; set; }
[ForeignKey("CreateById")]
public User CreateBy { get; set; }
}
public class Banana : FruitBase { }
public class DataContext : DbContext
{
public DbSet<FruitBase> Fruits { get; set; }
}
You have to be aware that by doing this, you are essentially creating a Polymorphic Association and as of CTP5, not all of the inheritance mapping strategies allow polymorphic association. Here it works fine because you've used Table per Hierarchy (TPH).
Update: Use Table per Type (TPT) Strategy:
Polymorphic Associations work with TPT as well:
public class StackoverflowTestContext : DbContext
{
public DbSet<FruitBase> Fruits { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Banana>().ToTable("Bananas");
}
}
as discussed above, this is TPC (Table per Concrete Type), the answer/work around here is to take the ForeignKeyAttribute out of the base
class User{}
abstract class AuditObjectBase{ // was FruitBase
// [ForeignKey("CreateById")]
public abstract User CreateBy{ get; set; } // made abstract
public int CreateById{ get; set; } // both get and set required public
}
class ItemOne : AuditObjectBase{ // Was Banana
// added
[ForeignKey("CreateById")]
public override User CreateBy{ get; set; }
}
class ItemTwo : AuditObjectBase{ // Added
[ForeignKey("CreateById")]
public override User CreateBy{ get; set; }
}
class DataContext : DbContext{
DbSet<ItemOne> ItemOnes{ get; set; }
DbSet<ItemTwo> ItemTwos{ get; set; }
}
Not completely DRY but at least when you create an object which uses the AuditObjectBase it will force you to implement the Foreign Key property, some nice comments to remind you of the attribute and away you go
Basically it seams, attributes should be added to the class declared in the DbContext as an Entity, in this case has a DbSet property.