Code first mapping issue while using Table per Hierarchy (TPH) - entity-framework

For past few days we started to work on code first, as a part of that i faced one issue while doing mapping which i summarized here with different example. It would be more helpful if i got any solution
for that.
Below are the three entities
Course -> Entity holds information about course like fee, description etc and each course will
belong to some category or Type(i.e.. student, employee etc), It will refer to the person for whom registration is made with the help of ParentId and Type column
Example : To get the coursejoined by the student with ID 10, the sql query would be
"select * from course where type=1 and parentid=10"
Student : CourseJoined entity here should fetch only related record of type student (ie..1) same apply for Employee too
So first how to achieve that first?
I have tried to implement TPH logic here as below but i am getting exception
public enum CourseType
{
Student = 1,
Employee = 2
}
public class Course
{
public int ID { get; set; }
public int ParentID { get; set; }
public decimal Amount { get; set; }
public CourseType Type { get; set; }
public string Description { get; set; }
}
public class StudentCourse : Course
{
}
public class EmployeeCourse : Course
{
}
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentCourse> CourseJoined { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string WorkingAs { get; set; }
public string Experiance { get; set; }
public virtual ICollection<EmployeeCourse> CourseJoined { get; set; }
}
Exception:
The foreign key component 'ParentID' is not a declared property on
type 'EmployeeCourse'. Verify that it has not been explicitly
excluded from the model and that it is a valid primitive property.
2) To avoid that error i removed the ParentID from Course table and
placed them in StudentCourse and EmployeeCourse after that i found below migration script
CreateTable(
"dbo.Course",
c => new
{
ID = c.Int(nullable: false, identity: true),
Amount = c.Decimal(nullable: false, precision: 18, scale: 2),
Type = c.Int(),
Description = c.String(),
ParentID = c.Int(),
ParentID1 = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Employee", t => t.ParentID, cascadeDelete: true)
.ForeignKey("dbo.Student", t => t.ParentID1, cascadeDelete: true)
.Index(t => t.ParentID)
.Index(t => t.ParentID1);
where two columns are created for course but i don't want two columns(t.ParentID and t.ParentID1), I want only ParentID where i will insert student and employee id's .
Can anyone guide me to fix the above or any suggestion to implement the above scenario ?
I am using EF 6.0 version.

Related

EF 6 Code First relationships using Fluent API. How to set relation between first and third tables or get grouped collection

I have three related entities, here is a structure and relations declared using Fluent API
An event, participating many persons (actually a group of persons) so there is a possibility to collect them all by GroupID. So how to do this?
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> GroupPerson {get; set}
}
Person entity, here I have all information about person, such as name, surname, birthdate...
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
Group entity, here is an information about the group
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> EventGroup { get; set; }
}
Now I describe relations using Fluent API. Primary keys first of all:
modelBuilder.Entity<Event>().HasKey(e => e.EventID);
modelBuilder.Entity<Person>().HasKey(e => e.PersonID);
modelBuilder.Entity<Group>().HasKey(e => e.GroupID);
Here I will have person related to event
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk=> fk.PersonID);
Here I will have PersonGroup
modelBuilder.Entity<Group>()
.HasKey(e => e.GroupID)
.HasMany(e => e.EventGroup)
.WithOptional()
.HasForeignKey(f => f.GroupID);
And my question is how to set a relation to get that list of persons in group?
PersonGroup is an Event type and I need list of persons type: Person => ICollection<Person> GroupPerson in Event class.
Given that your relationships are like this:
One event has (is related to) exactly one group (required)
One group has (is related to) zero to many events
One group has (is related to) zero to many people
One person has (is related to) zero to many groups
That is, your relationship Events-Groups is one-to-many, and your relationship Groups-People is many-to-many (I'm assuming that the same person can be in more than one group). There is no direct relationship between Events and People, but a transitive relationship Event -> Group -> People.
Then it can be modelled like this:
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> People { get { return Group.People; } }
}
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual ICollection<Person> People { get; set; }
}
With these DbSets in the DbContext:
public DbSet<Person> People { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Event> Events { get; set; }
And this EF configuration:
modelBuilder.Entity<Event>()
.HasKey(e => e.EventID)
.Ignore(e => e.People)
.HasRequired(e => e.Group)
.WithMany(g => g.Events);
modelBuilder.Entity<Group>()
.HasKey(g => g.GroupID)
.HasMany(g => g.People)
.WithMany();
modelBuilder.Entity<Person>()
.HasKey(p => p.PersonID);
Note that there is an explicit Ignore() for Event.People. This is because the relationship between Event and Person is transitive, you don't need extra columns in your database for it. If you don't see why, try commenting out the Ignore() line and regenerating the migration, and see that an extra column for the Event ID is generated in the People table (this column doesn't make much sense).
As a consequence the People property in Events is not populated by EF, you have to do it yourself:
public virtual ICollection<Person> People { get { return Group.People; } }
To add people to an Event you should use the Group navigation property, something like this:
public class Event
{
...
public void AddPerson(Person p)
{
this.Group.People.Add(p);
}
}
With this code the migration is generated like this, with four tables: Events, Groups, People and and extra table PeopleGroups for the many-to-many relationship between Person and Group.
public override void Up()
{
CreateTable(
"dbo.Events",
c => new
{
EventID = c.Int(nullable: false, identity: true),
DocID = c.String(),
GroupID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.EventID)
.ForeignKey("dbo.Groups", t => t.GroupID, cascadeDelete: true)
.Index(t => t.GroupID);
CreateTable(
"dbo.Groups",
c => new
{
GroupID = c.String(nullable: false, maxLength: 128),
GroupName = c.String(),
})
.PrimaryKey(t => t.GroupID);
CreateTable(
"dbo.People",
c => new
{
PersonID = c.String(nullable: false, maxLength: 128),
PersonName = c.String(),
PersonSurName = c.String(),
PersonCode = c.String(),
})
.PrimaryKey(t => t.PersonID);
CreateTable(
"dbo.GroupPersons",
c => new
{
Group_GroupID = c.String(nullable: false, maxLength: 128),
Person_PersonID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => new { t.Group_GroupID, t.Person_PersonID })
.ForeignKey("dbo.Groups", t => t.Group_GroupID, cascadeDelete: true)
.ForeignKey("dbo.People", t => t.Person_PersonID, cascadeDelete: true)
.Index(t => t.Group_GroupID)
.Index(t => t.Person_PersonID);
}
If you don't like the names of the columns in the relationship table GroupPersons you can add a .Map() configuration (but you don't really need to do this, as this table isn't directly used, there is no model entity for it, and it doesn't even have a DbSet property in the DbContext).

WithRequiredPrincipal, why still have to define Required or ForeignKey to be able to compile?

public class Admin : EntityTypeConfiguration<Admin>
{
//[ForeignKey("Blog")] -- If I enable this, it compiles
public int AdminId { get; set; }
public string AdminName { get; set; }
public string AdminPicture { get; set; }
//[Required] -- Or If I enable this, it compiles
public virtual Blog Blog { get; set; }
}
public class Blog : EntityTypeConfiguration<Blog>
{
public int BlogId { get; set; }
public string BlogName { get; set; }
public string BlogUrl { get; set; }
public virtual Admin Admin { get; set; }
public Blog()
{
HasRequired(a => a.Admin).WithRequiredPrincipal(b=>b.Blog);
}
}
As long as I am defining HasRequired and WithRequiredPrincipal keys, why VS still creates below error.
Unable to determine the principal end of an association between the types 'Dummy.Models.Blog' and 'Dummy.Models.Admin'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Second thing is, even I enable [Required] or [ForeingKey] attr., in edmx designer, I only see 1 - 0..1 But I must see 1 - 1 (both end required)
1-1 relationship is not possible at database level, because you can't insert two rows at the same time. 1-1 is only possible at class validation level.
To make a 1-1 relationship, the primary key of the dependant entity must be the foreign key of the principal entity; that's the only way to make a 1-1 relationship. So, you have to make the following changes (considering that you are using EF Code First):
public class Admin
{
public int AdminId { get; set; }
public string AdminName { get; set; }
public string AdminPicture { get; set; }
public virtual Blog Blog { get; set; }
}
Blog should not have its own BlogId, because a blog belongs to an admin, and the admin can have only one blog (1-1 relationship). If you create a BlogId, with an AdminId FK, you would be making a 1-n relationship. Furthermore, do not mix the entity class with the mapping class, they should be different things. See the example below:
public class Blog
{
public int AdminId { get; set; } //PK AND FK
public string BlogName { get; set; }
public string BlogUrl { get; set; }
public virtual Admin Admin { get; set; }
}
Creating the relationship with a mapping class:
public class BlogMapping : EntityTypeConfiguration<Blog>
{
public BlogMapping()
{
HasKey(i => i.AdminId);
HasRequired(a => a.Admin)
.WithRequiredDependent(i => i.Blog);
}
}
Register the mapping inside the dbContext class:
public class MyDbContext : DbContext
{
public DbSet<Admin> Admins { get; set; }
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BlogMapping());
}
}
This will generate the following migration:
CreateTable(
"dbo.Admins",
c => new
{
AdminId = c.Int(nullable: false, identity: true),
AdminName = c.String(),
AdminPicture = c.String(),
})
.PrimaryKey(t => t.AdminId);
CreateTable(
"dbo.Blogs",
c => new
{
AdminId = c.Int(nullable: false),
BlogName = c.String(),
BlogUrl = c.String(),
})
.PrimaryKey(t => t.AdminId)
.ForeignKey("dbo.Admins", t => t.AdminId)
.Index(t => t.AdminId);
Hope this helps!

creating 1-1 relationship between two class using code first and migration

Well, it is 1st time i am trying to create 1-1 relationship between two tables using code first. I took some help online and come across the following classes mapping.
Than I ran migration and found something wrong. E.g. The migration says that primary key for StudentDetails is Id from Student table whereas I am looking to have primary key StudentId. Also, the foreign key is being created in opposite way.
Please can someone highlight what is wrong here or is it me who perceived it wrong.
I need to use Id from student class as Foreign key in StudentDetails class.
public class Student
{
public bool isPass{get;set;}
public virtual StudentReport Report { get; set; }
}
public class StudentReport
{
[Key, ForeignKey("Student")]
public Guid Id { get; set; }
public Guid? StudentReportId { get; set; }
public string RollNumber { get; set; }
public string StudentType { get; set; }
public virtual Student Student { get; set; }
}
When i run my migration, i get the following outcome which looks not good.
public partial class StudentReport : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.StudentReport",
c => new
{
Id = c.Guid(nullable: false, identity: true),
StudentReportId = c.Guid(),
RollNumber = c.String(),
StudentType = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Student", t => t.Id)
.Index(t => t.Id);
}
In an one to one relationship one end must be the principal and the another one is the dependent. If you are going to declare a FK property in the dependent entity, EF requires that property should be PK too:
public class Principal
{
[Key]
public int Id{get;set;}
public virtual Dependent Dependent{get;set;}
}
public class Dependent
{
[Key, ForeignKey("Principal")]
public int PrincipalId{get;set;}
public virtual Principal Principal{get;set;}
}
If you want to have both entities with their own PKs, and also use Id from Student entity as FK in StudentReport class, then you can try with this model:
public class Student
{
[Key]
public Guid Id { get; set; }
public bool isPass{get;set;}
}
public class StudentReport
{
[Key]
public Guid StudentReportId{ get; set; }
[ForeignKey("Student")]
public Guid StudentId { get; set; }
public string RollNumber { get; set; }
public string StudentType { get; set; }
public virtual Student Student { get; set; }
}
I guess what you really need is an one to many relationship because an student could have 0 or many reports.
Check this link. It could help you understand better how to use the FK properties and the name conventions that have by default Code First.
Update 1
If you want to create an one to one relationship and both entities have their owns PKs, then you can't define a FK property in the dependent entity due to the restriction I explain at the begin of my answer. A solution for what you need could be using the Required attribute and deleting the FK property:
public class Student
{
[Key]
public Guid Id { get; set; }
public bool isPass{get;set;}
public virtual StudentReport StudentReport { get; set; }
}
public class StudentReport
{
[Key]
public Guid StudentReportId{ get; set; }
public string RollNumber { get; set; }
public string StudentType { get; set; }
[Required]
public virtual Student Student { get; set; }
}
Update 2
Are you sure? The migration code that I get is this:
AddForeignKey("dbo.StudentReports", "StudentReportId", "dbo.Students", "Id");
Which is not ok yet because Code First is still configuring by convention the PK of StudentReport as FK. To avoid that you can add this Fluent Api configuration to your context:
modelBuilder.Entity<StudentReport>()
.HasRequired(sr => sr.Student)
.WithOptional(s => s.StudentReport)
.Map(c=>c.MapKey("Student_Id"));
This way Code First will generate this migration code:
AddColumn("dbo.StudentReports", "Student_Id", c => c.Guid(nullable: false));
CreateIndex("dbo.StudentReports", "Student_Id");
AddForeignKey("dbo.StudentReports", "Student_Id", "dbo.Students", "Id");

Convert EF Model Property to Navigation Property

What are the step to convert/migrate a model property into a navigation property (create a new class and create a foreign key relationship, using EF Code First Migration.
In the example below, I want to convert the Student class property Country into a navigational property, without losing in data.
Current Model
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
Proposed Model
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int CountryID { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Country { get; set; }
}
Add-Migration NavigationProperty
public override void Up()
{
CreateTable(
"dbo.Countries",
c => new
{
ID = c.Int(nullable: false, identity: true),
CountryName = c.String(),
})
.PrimaryKey(t => t.ID);
AddColumn("dbo.Students", "CountryID", c => c.Int(nullable: false));
CreateIndex("dbo.Students", "CountryID");
AddForeignKey("dbo.Students", "CountryID", "dbo.Countries", "ID", cascadeDelete: true);
DropColumn("dbo.Students", "Country");
}
Update-Database Error
System.Data.SqlClient.SqlException (0x80131904): The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Students_dbo.Countries_CountryID". The conflict occurred in database "aspnet-navprop-20141009041805", table "dbo.Countries", column 'ID'.

Add an entity to your initial migration

I am using code first migrations in an asp.net mvc program.
I am using the default authenticion and roles which is provided in the project.
Now when I enabled migrations it automatically generated a migration class which generates all of the tables etc.
Here is an excample of the specific table which I wqish to edit.
CreateTable(
"dbo.AspNetUserRoles",
c => new
{
UserId = c.String(nullable: false, maxLength: 128),
RoleId = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => new { t.UserId, t.RoleId })
.ForeignKey("dbo.AspNetRoles", t => t.RoleId, cascadeDelete: true)
.ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
.Index(t => t.RoleId)
.Index(t => t.UserId);
Now I would like to add a description field to this table. It would be really easy if i just added it in the database but then I will loose my code first migrations.
1> Where does Entity framework get all its commands for the initial migration? Because there are no models in my project that I can see which specify the tables it creates.
2> How can I modify or edit some of the original tables which are generated? I have tried just editing the initial migrations folder but that does not work?
(Just my thinking) Is it not that maybe the Roles and users models are stored in the framework and that is where it gets the structure of the tables from? If so Can i not extend the default model to add more attributes? Cause I know you can do it for the ApplicationUser, I have done so before, here is an example of it:
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
[Required]
[MaxLength(50)]
[Display(Name = "Email Address")]
public string email { get; set; }
}
Thats how i can add a email address field to the default user. Can I not maybe do this with the roles as well.
You dit it perfectly for the ApplicationUser, just continue like that for the other stuff. If you want to add description to the table of the users. Just add it to the ApplicationUser model. Don't mind the foreign keys and virtual properties.
public class ApplicationUser : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string GroupName { get; set; }
[Required]
public string Email { get; set; }
[Required]
[StringLength(15)]
public string Phone { get; set; }
public string Remark { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidUntil { get; set; }
// Foreign keys
[ForeignKey("Bank")]
public string AccountNumber { get; set; }
[ForeignKey("Address")]
public int? AddressId { get; set; }
public virtual Bank Bank { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
For editing the role class, you should inherit IdentityRole on your class and add the properties:
public class ApplicationRole : IdentityRole
{
public string Description { get; set; }
}
The framework will generate new migration classes which will be ran when you use the Update-Database command.
You have to change your identityManager (Use ApplicationRole here):
public class IdentityManager
{
public bool RoleExists(string name)
{
var rm = new RoleManager<ApplicationRole>(new RoleStore<ApplicationRole>(new ApplicationDbContext()));
return rm.RoleExists(name);
}
public bool CreateRole(string name)
{
var rm = new RoleManager<ApplicationRole>(new RoleStore<ApplicationRole>(new ApplicationDbContext()));
var idResult = rm.Create(new ApplicationRole(name));
return idResult.Succeeded;
}
}
You have to overwrite the Role in ApplicationDbContext doing like following (don't forget the new):
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
...
public DbSet<Product> Products { get; set; }
new public DbSet<ApplicationRole> Roles { get; set; }
}