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.
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.
I have 2 tables Person and Student. Person has a 1-to-1 relationship with Student. The fields are:
Person
- ID
- FirstName
- LastName
Student
- PersonID
I also have 2 classes:
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public int LastName { get; set; }
}
public class Student : Person
{
}
I now need to create DbSets for them so I can access the tables. My question is, is it OK to create one DbSet for the Student (which would need splitting) or should I just create 2 DbSets. One for Person one for Student?
I have 2 tables in different schemas:
Base.Person
ID
FirstName
LastName
Enrollment.Student
PersonID
StudentNo
This is related one-to-one.
Now in my DbContext, I want to have a DbSet named Students but I want its properties mapped to Person and Students. In particular, I want to get Person.ID, Person.FirstName, Person.LastName, Student.StudentNo mapped into my Student class.
The Student class is:
public class Student
{
public int ID { get; set;}
public string FirstName { get; set;}
public string MiddleName { get; set;}
public string StudentNo { get; set;}
}
One additional question that I'd like to ask which is not related to my problem above but it will be clearer to ask if the example above is present, in designing your DbContext, is DbContext intended to make the whole of the database available to you or is it ok just to expose what you want? For example, in my question above, I don't have a Person DbSet.
You cannot currently do this in EF 7 EF Core. However, you can model one to one relationships like this:
[Table("Student", Schema = "Enrollment")]
public class Student
{
[Key]
public string StudentNo { get; set; }
[ForeignKey("PersonId")]
public Person Person { get; set; }
[Column("PersonID")] // <-- if your db is case sensitive
public int PersonId { get; set; }
}
[Table("Person", Schema="Base")]
public class Person
{
// [Key] - not needed as EF conventions will set this as the "key"
[Column("ID")] // again, if case sensitive
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
// in code, use .Include to materialize dependent entities like so....
context.Students.Include(s => s.Person).Where(s => s.Person.FirstName == "Bob");
For more info on modeling, see https://docs.efproject.net/en/latest/modeling/relationships.html#one-to-one
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
}
}
Let's say I have a model where I have the Person entity with general info (Names, Date of Birth, etc.), and two additional entities (Customer, Worker) which inherit from Person. As you see there is the option of having a Customer who CAN ALSO play the role of a Worker in the model. There is a way to design this in EF (I saw something about TPH, TPT and TPC) but I see that there is a use of discriminator which doesn't allow a Person table to include values for Worker and Customer "simultaneously".
I don't know if maybe I'm getting wrong with the general OOP concept of inheritance :S.
Thanks in advance for all your help.
You cant have multiple inheritance in .net, it is not supported (and the same applies to entity framework). You can implement multiple interfaces, but this is a slightly different notion - i.e. 'Worker' could be an interface that is implemented by some objects, such as customer
In entity framework, I believe that the discriminator is only implemented when using Table-per-hierarchy. This is where both child entities are stored in the same table, and the discriminator identifies which is which.
Table-per-type is essentially where the entities (person, customer, worker) are stored in different tables, but are accessible as single entities in your code (i.e. customer with an inheritance from person)
It may be that you need to create an interface (maybe IWorker), and create a class (maybe WorkerCustomer??) that inherits from Customer, and implements IWorker.
EDIT: 15/02/2013 19:00
Ok, so the below might be what you're looking for in terms of representing the data in a single table:
public class MyDbContext : DbContext
{
public MyDbContext() : base("TestDB")
{
}
public DbSet<Person> People { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Worker> Workers { get; set; }
public DbSet<WorkerCustomer> WorkerCustomers { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Customer : Person
{
public string CustomerNumber { get; set; }
}
public interface IWorker
{
string WorkerNumber { get; set; }
}
public class Worker : Person, IWorker
{
public string WorkerNumber { get; set; }
}
public class WorkerCustomer : Customer
{
public string WorkerNumber { get; set; }
}