I have an issue when defining some POCO objects (entities) in entity framework code first 4. I have a main entity, let's say Entity_A, which has only a property, only the ID (primary key). The rest of entities (Entity_B in this example) inherits from it (childs) except some of them (Entity_C) that inherits from another entity (from Entity_B, not from Entity_A). For example:
public class Entity_A
{
public virtual Guid ID { get; set; }
}
// Entity_B has not primary key defined within as it inherits from Entity_A
public class Entity_B : Entity_A
{
public virtual string propertyB1 { get; set; }
public virtual string propertyB2 { get; set; }
public virtual string propertyB3 { get; set; }
}
// Entity_C has not primary key defined within as it inherits from Entity_A through Entity_B
public class Entity_C : Entity_B
{
public virtual string propertyC1 { get; set; }
public virtual string propertyC2 { get; set; }
public virtual string propertyC3 { get; set; }
}
so after executing it, tables for Entity_A, Entity_B, Entity_C are generated automatically but only table for Entity_A and Entity_B are correct but not for Entity_C:
Table Entity_A has fields:
-ID
which is correct.
Table Entity_B has fields:
-ID
-propertyB1
-propertyB2
-propertyB3
which is correct as well.
Table Entity_C has fields:
-ID
-propertyC1
-propertyC2
-propertyC3
which is not correct for me, as for Entity_C I expect the following fields:
-ID
-propertyB1
-propertyB2
-propertyB3
-propertyC1
-propertyC2
-propertyC3
What am I doing wrong? Is not entity framework code first (version 4.1) supporting inheritance at all?
Thanks in advance.
Try adding a record to Entity_C, you will notice that a record is added to Entity_B and Entity_A as well.
This is table inheritance. An Entity_C is an Entity_B, so why duplicate the rows?
Related
I am new to entity framework and I am using code first approach to create entities using TPT inheritance.
My requirement is to create the entities as per the attached diagram where ID is PK for Customers table and FK for the AddressDetails and ContactDetails table. Based on the keys I also need to create the association and navigation properties for the entities. Table Diagram
In my code I have created entities as
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int ZipCode { get; set; }
public virtual ContactDetails ContactDetails { get; set; }
public virtual AddressDetails AddressDetails { get; set; }
}
[Table("ContactDetails")]
public class ContactDetails: Customer
{
public string MobileNo { get; set; }
public string EmailId { get; set; }
}
[Table("AddressDetails")]
public class AddressDetails: Customer
{
public string BillingAddress { get; set; }
public string DeliveryAddress { get; set; }
}
My question is, have I created the association and navigation properties correctly or do I need to add them in the ContactDetails and AddressDetails class as well? Also, when I run the code the entities are getting created in the database but for the Customer table there are 2 additional columns created as AddressDetails_Id(FK,int,null) and ContactDetails_Id(FK,int,null). I think they are created because of the navigation property but I do not need these columns in the database to be created. Also the values are always null in these two columns.
Any help would be appreciated. Thanks in advance.
Say I have this existing schema:
and have this domain mapping as follows:
public class SchoolContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Subject> Subjects { get; set; }
}
protected override OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().ToTable("People");
modelBuilder.Entity<Student>().ToTable("Students");
}
public abstract class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Student : Person
{
public int StudentId { get; set; }
public string Course { get; set; }
public ICollection<Subject> Subjects { get; set; }
}
public class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int StudentId { get; set; }
}
And I was given a scenario that I need to query on the subject via PersonId, EF will throw me an exception saying "Invalid column name 'Student_PersonId'".
I understand EF can't see the FK well and I opted to make the Person class as a Base class since there's a chance that I'll have a Teachers table in which it is a Person as well.
Note that Student table need to have its own Primary Key and let's just say the schema was design with a relationship of:
Person -> Student (One-to-Zero or One relationship)
Student -> Subject (One-to-Many relationship)
Is there a way to fix this? Also note that if it's made using Code-First, EF will ommit StudentId on Students table and I do have an existing DB anyway
You should start off by reading this article about TPT in entity framework. Now you don't have a 'Student is a person' kind of relationship, and you'll have to change some things to your database for it to work.
Student's primary key should at the same time be the foreign key to your people table. Since student is a person, it has that database as its baseclass and the Student table should only contain specific properties for student. The properties by Person are inherited.
Person is your abstract base class. Every student, teacher... is a person which is why you can't have a DbSet of Student/teacher... They are persons, so DbSet<Person> is all you need.
You can't map Person to a table. If you really want TPT every person is also a teacher, student... A person alone shouldn't exist, so you shouldn't map it to a table. There's a reason the class is abstract, you can't have just a person. For example Person p = context.Students.FirstOrDefault(); is perfectly valid code for TPT.
That being said, if you think Person can have instances of his own (so certain persons don't have a derived class) you shouldn't opt for TPT and just work with the foreign key to the person table like you do now. If you do want to use TPT you'll have to make above adjustments.
Many times I have a general purpose entity that other entities contain a collection of. I don't want to have a new collection entity for each parent entity type that needs it but would like to re-use a single general purpose entity. For performance reasons, I also don't want to explicitly define many-to-many relationships as in this answer. The simplest example would be a collection of strings.
public class MyString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyString> { get; set; }
}
public class MyOtherString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherString> { get; set; }
}
I'd really like to combine MyString and MyOtherString into a single entity:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
Except now I'm going to have an additional foreign key in GeneralPurposeString for every entity that contains a collection of GeneralPurposeString.
What I would like would be a way to have an additional parent category column on the GeneralPurposeString table (but not the entity) that would specify which entity the item belongs to. I use Guid for primary keys, so the tables could look something like this:
CREATE TABLE [GeneralPurposeString]
(
[Id] uniqueidentifier NOT NULL
CONSTRAINT PK_GeneralPurposeString PRIMARY KEY,
[ParentEntityCategory] uniqueidentifier NOT NULL,
[ParentEntityId] uniqueidentifier NOT NULL,
[Value] nvarchar(MAX)
)
And some how in Code First to specify that MyEntity has a certain category, and that it's collection of GeneralPurposeString uses that category, and MyOtherEntity uses another category (Guid) for it's collections of GeneralPurposeString.
The key would be that GeneralPurposeString could be a collection in any other entity and that loading the parent entity and including the collection would automatically load without having to explicitly specify the category.
The purposes for all of this are
Allow .NET code to have GeneralPurposeString code that wasn't replicated everywhere (actual utility or business logic code). This can probably also be accomplished through inheritance and explicit mapping but that would still leave multiple tables in the database (see #2).
Have only one table in the database for GeneralPurposeString. This is more of a tidiness issue. Performance would possibly be better with multiple tables, but indexing on ParentEntityCategory/ParentEntityId and covering Value should be good performance for lookups.
Not have to explicitly code this relationship and the lookups everywhere it's needed.
I'm thinking if I can get over #2 and be OK with a separate table behind the scenes and implementing a derived class, that will be the simplest route to go.
So just:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
// It's just a GeneralPurposeString with a fancy MyEntity membership pin
public class MyEntityString: GeneralPurposeString {}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyEntityString> Strings { get; set; }
}
// Cool GeneralPurposeStrings belong to MyOtherEntity
public class MyOtherEntityString: GeneralPurposeString {}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherEntityString> Strings { get; set; }
}
public class MyContext: DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
}
I don't have to add the derived classes to the DbContext and the tables get named with the plural of the derived class by default, so it's actually pretty straight forward.
My previous train of thought with the Parent Category would require additional coding/annotation even if EF supported it. This uses purely convention and nothing extra needed in annotations or in OnModelCreating().
I'm not seeing any harm in extra tables at this point in time. I don't see a need (currently) to have all of the data in one table for reporting, but that really depends on the type of general purpose entity, so I may need to revisit this in the future, or I may just take the many-to-many route if I do need the data in one table.
And I can still have:
public static class GeneralPurposeStringExtensions
{
public static void SassThatHoopyFrood(this GeneralPurposeString s)
{
// do stuff
}
}
I have a property on my items class called vend_id which of course EF thinks is a foreign key to the vendor table. It actually should be a foreign key in the database but for reasons unknown to me the designers of the db chose not to make it a foreign key.
I am using EF to create a copy of the db schema on the local machine. When EF creates the database I want to tell it not to create a foreign key on the vend_id column. How do I do that? Ideally I do not want to rename the property because there are several such instances in my db and it just makes it confusing.
Thank you,
Sam
You can't have a navigation property to a Vendor entity in your Item entity class if the Items table does not have a foreign key to table Vendor. If you did not specify a navigation property in entity class Item, EF would not infer that vend_id is a foreign key.
Update:
Unable to reproduce with the following:
[Table("EntityA")]
public partial class EntityA
{
public int Id { get; set; }
public Nullable<int> EntityBId { get; set; }
public string Description { get; set; }
[ForeignKey( "EntityBId" )]
public virtual EntityB EntityB { get; set; }
// this is not created as a FK
// nor does EntityCId cause a FK
public int EntityC_Id { get; set; }
}
[Table("EntityC")]
public class EntityC
{
public EntityC()
{
EntitiesD = new HashSet<EntityD>();
}
public int EntityCId { get; set; }
public string Name { get; set; }
public virtual ICollection<EntityD> EntitiesD { get; set; }
}
We have the following set of objects.
public class Form
{
public int FormId { get; set; }
public DateTime DateCreatedOn { get; set; }
public string Status { get; set; }
}
// This is using TPT inheritance from Form
[Table("FormA")]
public class FormA : Form
{
public string ExtraInfoA { get; set; }
public virtual ICollection<Child> Children
}
// This is using TPT inheritance from Form
[Table("FormB")]
public class FormB : Form
{
public string ExtraInfoB { get; set; }
public virtual ICollection<Adult> Adults
}
public class Person
{
public int PersonId { get; set; }
public int FormId
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
// This is using TPH inheritance from Person
public class Adult : Person
{
public int HowManyCars { get; set; }
public string NationalInsuranceNo { get; set; }
[ForeignKey("FormId")]
public virtual FormB FormB { get; set; }
}
// This is using TPH inheritance from Person
public class Child : Person
{
public int HowManyToys { get; set; }
public string SchoolName { get; set; }
[ForeignKey("FormId")]
public virtual FormA FormA { get; set; }
}
This creates 3 tables for the forms Form, FormA, and FormB, all with the appropriate fields in them. It also creates 1 table for Person.
The problem is When we rely on the convention and don't specify the ForeignKey attribute the Person table contains 2 additional foreign key columns.
However when we do specify the ForeignKey attribute (as in the code above) we get the following error message.
`The foreign key component 'FormId' is not a declared property on type 'Child'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.`
FormId is definitely a property of Child so I'm not sure what is going wrong.
Our real world situation is a lot more complicated that the situation above so I'd like to get it right now rather tham have multiple foreign keys.
Any help is very much appreciated.
You cannot define foreign key in the parent entity and navigation property in the child entity. They must both be defined in the same entity. What you are trying to do is even not valid in the database because you cannot have conditional foreign key constraint on the column - constraints to both FormA and FormB will be applied for every record and you will never be able to insert any record (because it would always violate constraint to FormA or FormB).
In short: You need either single navigation property in parent or separate foreign key for every child.