I am battling with EF code first and trying to map a 1:1 relationship with no joy.
Basically a user can have a userdetail.
I am setting primary keys on both tables.On my UserDetail table has field UserId I am trying to use as the FK.
public class User:BaseModel
{
public virtual UserDetail UserDetail { get; set; }
public string UserName { get; set; }
}
public class UserDetail:BaseModel
{
public virtual User User { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public string UserDetailName { get; set; }
}
public class BaseModel{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
When I try using the add-migration command I get this error
UserDetail_User_Source: : Multiplicity is not valid in Role 'UserDetail_User_Source' in relationship 'UserDetail_User'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.
What do I need to do to get this working? Surely this shouldnt be that difficult?
Update below based on comment from #steve-green
I configured the fluentapi like steve suggested
modelBuilder.Entity<User>()
.HasRequired(t => t.UserDetail)
.WithRequiredPrincipal(t => t.User);
however the generated migration step looks wrong to me
CreateTable(
"dbo.Users",
c => new
{
UserId = c.Int(nullable: false, identity: true),
UserName = c.String(),
})
.PrimaryKey(t => t.UserId);
CreateTable(
"dbo.UserDetails",
c => new
{
UserDetailId = c.Int(nullable: false, identity: true),
UserId = c.Int(nullable: false),
UserDetailName = c.String(),
})
.PrimaryKey(t => t.UserDetailId)
.ForeignKey("dbo.Users", t => t.UserDetailId)
.Index(t => t.UserDetailId);
The FK is configured as UserDetailId shouldnt it be UserId?
Slightly modified model
public class UserDetail
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserDetailId { get; set; }
public virtual User User { get; set; }
public int UserId { get; set; }
public string UserDetailName { get; set; }
}
Get rid of the attribute
[ForeignKey("User")
in UserDetail. Also, i would add
int UserDetailId
to User
That should then work.
If you are going to use a base table, then you can't do it with annotations because (as you mention) the key on the required dependent needs to be a foreign key. You will need fluent api code:
modelBuilder.Entity<User>()
.HasRequired(t => t.UserDetail)
.WithRequiredPrincipal(t => t.User);
https://msdn.microsoft.com/en-us/data/jj591620.aspx#RequiredToRequired
Related
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).
My entity AppUser has an optional UserProfile, and UserProfile as a required AppUser. I would like to have a foreign key to each other.
public class AppUser
{
public int Id { get; set; }
public string Name { get; set; }
public UserProfile UserProfile { get; set; }
public int? UserProfileId { get; set; }
}
public class UserProfile
{
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
I got this mapping:
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
This generate the following migration. I notice there is no foreign key from AppUser to UserProfile. Also the foreignkey in UserProfile is defined on UserProfile.Id ... I want it on UserProfile.AppUserId.
public override void Up()
{
CreateTable(
"dbo.AppUsers",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
UserProfileId = c.Int(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.UserProfiles",
c => new
{
Id = c.Int(nullable: false),
SomeUserProfileValue = c.String(),
AppUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AppUsers", t => t.Id)
.Index(t => t.Id);
}
So I tried to change the mapping configuration as follow
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
.Map(c => c.MapKey("AppUserId"));
But now when I try to add the migration i get the error:
AppUserId: Name: Each property name in a type must be unique. Property name 'AppUserId' is already defined.
This seems to complain that I have a field AppUserId already defined in my model.
This is how we define our entities, we always include both the class and the id fields, gives more flexibility as to which to use under different circumstances.
So I'm a bit stuck here... is there any way to have this 1:1 bidirectional relation while having both class and the id fields defined in the model ?
And why there is no nullable foreign key generated in the AppUser table ?
I've generally found better results with DataAnnotations, myself. So:
public class AppUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? UserProfileId { get; set; }
[ForeignKey = "UserProfileId"]
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key]
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public int AppUserId { get; set; }
[ForeignKey = "AppUserId"]
public AppUser AppUser { get; set; }
}
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!
I have City entity:
public class City
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<CityDistance> CityDistances { get; set; }
}
and i would like to keep distances between cities.
1. How can achieve this in code first entity framework 6?
Here are my other classes:
public class CityDistance
{
[Key, Column(Order = 0)]
public int CityAID { get; set; }
[Key, Column(Order = 1)]
public int CityBID { get; set; }
public virtual City CityA { get; set; }
public virtual City CityB { get; set; }
public double Distance { get; set; }
}
Is this correct design?
When i run "add-migration Distances" here is the result.
3. Why is it adding another foreign key column named "City_ID"?
CreateTable(
"dbo.CityDistances",
c => new
{
CityAID = c.Int(nullable: false),
CityBID = c.Int(nullable: false),
Distance = c.Double(nullable: false),
City_ID = c.Int(),
})
.PrimaryKey(t => new { t.CityAID, t.CityBID })
.ForeignKey("dbo.Cities", t => t.City_ID)
.ForeignKey("dbo.Cities", t => t.CityAID, cascadeDelete: true)
.ForeignKey("dbo.Cities", t => t.CityBID, cascadeDelete: true)
.Index(t => t.CityAID)
.Index(t => t.CityBID)
.Index(t => t.City_ID);
That's because EF is acting like a simple bookkeeper: CityA, CityB, that's two foreign keys, CityDistances, that's another foreign key, that makes three foreign keys.
EF doesn't know that you intend CityDistances to be the other (inverse) end of the association for either CityA or CityB. You have to indicate this explicitly, either by data annotations:
public class City
{
...
[InverseProperty("CityA"]
public virtual ICollection<CityDistance> CityDistances { get; set; }
}
or by fluent mapping:
modelBuilder.Entity<City>()
.HasMany(c => c.CityDistances)
.WithRequired(cd => cd.CityA)
.HasForeignKey(cd => cd.CityAID);
(By which I implicitly say that, yes, this is correct design, at least technically).
Often we might need to use Entity Framework Code First with an existing database.
The existing database may have a structure the allows "Table Per Hierarchy" inheritance.
Or we might start with an object model that looks like:
public partial class Person {
public int Id { get; set; }
public string Discriminator { get; set; }
public string Name { get; set; }
public Nullable<int> StudentTypeId { get; set; }
public virtual StudentType StudentType { get; set; }
}
public partial class StudentType {
public StudentType() {
this.People = new List<Person>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
We create the initial migration:
enable-migrations
add-migration Initial
The migration looks like:
public override void Up()
{
CreateTable(
"dbo.Person",
c => new
{
Id = c.Int(nullable: false, identity: true),
Discriminator = c.String(maxLength: 4000),
Name = c.String(maxLength: 4000),
StudentTypeId = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StudentType", t => t.StudentTypeId)
.Index(t => t.StudentTypeId);
CreateTable(
"dbo.StudentType",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(maxLength: 4000),
})
.PrimaryKey(t => t.Id);
}
To generate this database we:
update-database
This results in a database that we could have generated like this.
create table Person(
Id int Identity(1,1) Primary key,
Discriminator nvarchar(4000) null,
StudentTypeId int null,
)
create table StudentType(
Id int Identity(1,1) Primary key,
Name nvarchar(4000) not null
)
alter table Person
add constraint StudentType_Person
foreign key (StudentTypeId)
references StudentType(Id)
We use this database in production for a while...
Now we want to add the concept of students that are different from just regular people.
Entity Framework provides three approaches for representing inheritance. In this case we choose the "Table Per Hierarchy" approach.
To implement this approach we modify our POCOs as follows:
public class Person {
public int Id { Get; set; }
public string Name { get; set }
}
public class Student : Person {
public virtual StudentType StudentType { get; set; }
public int? StudentTypeId { get; set; }
}
public class StudentType {
public StudentType() {
Students = new List<Student>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Note:
Only Students have access to the StudentType property.
We don't specify the Discriminator property in our Person class. EF Code First sees that Student inherits from Person and will add a Discriminator column to the Person table for us.
Now we run:
add-migration Person_TPH
And we get this unexpected output.
public override void Up()
{
AddColumn("dbo.Person", "StudentType_Id", c => c.Int());
AlterColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128));
AddForeignKey("dbo.Person", "StudentType_Id", "dbo.StudentType", "Id");
CreateIndex("dbo.Person", "StudentType_Id");
}
It should not be adding the StudentType_Id column or index.
We can be explicit by adding the 'StudentMap' class:
public class StudentMap : EntityTypeConfiguration<Student> {
public StudentMap() {
this.HasOptional(x => x.StudentType)
.WithMany()
.HasForeignKey(x => x.StudentTypeId);
}
}
But no joy..
Indeed, if we delete the database and all the migrations.
Then run add-migration Initial against our new model we get:
public override void Up()
{
CreateTable(
"dbo.Person",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(maxLength: 4000),
StudentTypeId = c.Int(),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StudentType", t => t.StudentTypeId)
.Index(t => t.StudentTypeId);
CreateTable(
"dbo.StudentType",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(nullable: false, maxLength: 100),
})
.PrimaryKey(t => t.Id);
}
In this "correct" version we see that EF Code First migrations uses the StudentTypeId column as expected.
Question
Given that the database already exists, is there a way to tell EF Code First migrations to use the existing StudentTypeId column.
The GitHub repo that demonstrates the problem is here:
https://github.com/paulyk/ef_code_first_proof_of_tph_bug.git
Git tags
1_add_migration_Initial
2_add_migration_person_TPH
3_add_studentMap
There are 3 conventions that I found that relate to the discovery of explicit foreign keys in the class:
System.Data.Entity.ModelConfiguration.Conventions.NavigationPropertyNameForeignKeyDiscoveryConvention
System.Data.Entity.ModelConfiguration.Conventions.PrimaryKeyNameForeignKeyDiscoveryConvention
System.Data.Entity.ModelConfiguration.Conventions.TypeNameForeignKeyDiscoveryConvention
The PrimaryKeyNameForeignKeyDiscoveryConvention would not help here since the primary key on StudentType is just Id. The other two would both match on StudentTypeId though, so as long as you aren't removing both of those, the conventions should pick it up.
According to this question (Foreign key navigation property naming convention alternatives) though, you can also add [ForeignKey("StudentTypeId")] to the StudentType property on Student and [InverseProperty("StudentType")] to the Students property on StudentType.
Hope that helps. :)