It seems that Entity Framework have some conventions to deal with one to one relationship.
I'm using Fluent API and I need my child entity to have PK and Also FK.
Is it possible without using the [ForeignKey] attribute?
Consider the following example:
public class Principal
{
public int Id { get; set; }
public Dependent Dependent { get; set; }
}
public class Dependent
{
public int Id { get; set; }
public Principal Principal { get; set; }
}
To configure Dependent's Id property to be a foreign key to Principal's Id property with Fluent API you may choose one of the following options:
1) Starting with Entity<Dependent>:
public class AppDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Dependent>().HasRequired(d => d.Principal).WithOptional(p => p.Dependent);
}
}
2) Starting with Entity<Principal>
public class AppDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Principal>().HasOptional(p => p.Dependent).WithRequired(d => d.Principal);
}
}
They both will result in the following code first migration:
CreateTable(
"dbo.Principals",
c => new
{
Id = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Dependents",
c => new
{
Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Principals", t => t.Id)
.Index(t => t.Id);
where Dependent's Id property is configured to be as a PK and a FK to Principal's Id property.
Related
My entity has a List<SecondEntityDTO>. When EF generates the table, in the table SecondEntities there's a column name FirstEntityDTO_id. I would like this column to be named "ParentEntity_id". How can I do that?
I tried annotating the List of SecondEntityDTO and a bunch of other things...
Edit1: I belive you guys missunderstood.
This is my MainEntity:
[Table("MainEntities")]
public class MainEntityDTO
{
public string Title { get; set; }
[Key]
public int id { get; set; }
public List<SubEntityDTO> SubEntities { get; set; }
}
This is SubEntityDTO:
[Table("SubEntities")]
public class SubEntityDTO
{
[Key]
public int id { get; set; }
public string Title { get; set; }
}
And this is the Migration:
public override void Up()
{
CreateTable(
"dbo.MainEntities",
c => new
{
id = c.Int(nullable: false, identity: true),
Title = c.String(),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.id);
CreateTable(
"dbo.SubEntities",
c => new
{
id = c.Int(nullable: false, identity: true),
Title = c.String(),
MainEntityDTO_id = c.Int(),
})
.PrimaryKey(t => t.id)
.ForeignKey("dbo.MainEntities", t => t.MainEntityDTO_id)
.Index(t => t.MainEntityDTO_id);
}
Note the name of the third column on the SubEntities table!
Also, you can do the same using Fluent Api, for example, overwritting the OnModelCreating method of your Context and doing this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<SecondEntity>().Property(s => s.FirstEntityDTO_id).HasColumnName("ParentEntity_id");
}
Update
Why you don't edit Func<> that specifies the columns of that table. As you can see you are creating an anonymous type, so you can change the name of the column there, eg:
CreateTable(
"dbo.SubEntities",
c => new
{
id = c.Int(nullable: false, identity: true),
Title = c.String(),
ParentEntity_id = c.Int(),
})
.PrimaryKey(t => t.id)
.ForeignKey("dbo.MainEntities", t => t.ParentEntity_id)
.Index(t => t.ParentEntity_id);
If you do this, remember change the name of that property in the Down method, but if you already executed that script, don't change the FK name yet in the Down method. Execute again the Update Database command specifying the name of that script. That will drop those tables and they will be created once again using the Up method, but now with the FK name that you want it.In that moment is when you can change the FK name in the Down method:
public override void Down()
{
DropForeignKey("dbo.SubEntities", "ParentEntity_id", "dbo.MainEntities");
DropIndex("dbo.SubEntities", new[] { "ParentEntity_id" });
DropTable("dbo.SubEntities");
DropTable("dbo.MainEntities");
}
Put the following code in your SubEntity class:
[ForeignKey("ParentEntity")]
public int ParentEntity_id { get; set; }
public virtual MainEntity ParentEntity { get; set; }
I don't know if I'm missing something obvious. We're using Entity Framework 6.0.0-rc1 in a project where the model is set up with the fluent API. Configuration of one of our entities could be:
HasMany(t => t.Entity)
.WithRequired(tc => tc.ParentEntity)
.HasForeignKey(tc => new {tc.Key1, tc.Key2})
.WillCascadeOnDelete(true);
When running this configuration, database gets created correctly, with all tables and fields. Even relationships are correctly established but not the delete cascade.
If I go to Management Studio and I inspect update/delete rules of the relationship, both are deactivated.
Thanks for your help.
With these classes:
public class Parent
{
public int ID { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public int ParentID { get; set; }
public Parent Parent { get; set; }
}
Configured like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentID)
.WillCascadeOnDelete(true);
}
Puts cascade delete on the foreign key:
public override void Up()
{
CreateTable(
"dbo.Children",
c => new
{
ID = c.Int(nullable: false, identity: true),
ParentID = c.Int(nullable: false),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Parents", t => t.ParentID, cascadeDelete: true)
.Index(t => t.ParentID);
CreateTable(
"dbo.Parents",
c => new
{
ID = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.ID);
}
Here's my POCO
public class Game
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Galaxy> Galaxies { get; set; }
}
Here's the TypeConfiguration ....
public class GameConfiguration : EntityTypeConfiguration<Game>
{
public GameConfiguration()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
HasMany(x => x.Galaxies);
Property(x => x.Name)
.IsRequired()
.HasMaxLength(50);
}
}
My question is this... why, when this is added as a migration does the migration code not set the "Name" property as a "NOT NULL"? It also ignores the MaxLength setting too. Why is this?
CreateTable(
"dbo.Games",
c => new
{
Id = c.Guid(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
At first glance the rest of your configuration matches what would happen by convention even if the configuration constructor never ran and the if the name property is missing that could explain it. The code that registers the configuration in the model builder is missing.
You can register the entity configuration, e.g. inside the OnModelCreated method like this:
modelBuilder.Configurations.Add(new GameConfiguration());
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. :)
I am building ASP MVC web site using Entity Framework 4.4 with .NET Framework 4.0
I've add to my model a many to many relation like so:
public class User {
public int UserID { get; set; }
public string Username { get; set; }
public virtual ICollection<Tenant> Tenants { get; set; }
}
public class Tenant {
public string TenantID { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
When I run Add-Migration command I get this migration class (I remove the Down method)
public partial class TenantUsersManyToManyMigration : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.UserTenants",
c => new
{
User_UserID = c.Int(nullable: false),
Tenant_TenantID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => new { t.User_UserID, t.Tenant_TenantID })
.ForeignKey("dbo.Users", t => t.User_UserID, cascadeDelete: true)
.ForeignKey("dbo.Tenants", t => t.Tenant_TenantID, cascadeDelete: true)
.Index(t => t.User_UserID)
.Index(t => t.Tenant_TenantID);
}
}
Why are the field names for TenantID and UserID are User_UserID and Tenant_TenantID and not UserID and TenantID, respectively.
How can I change the default migration scaffolding (or my model) to make cascadeDelete to be false? (currently I simply change it by hand).
You can create your mapping table the way you're wanting using fluent notation. In your DbContext class, override the OnModelCreating with this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.Tenants)
.WithMany(t => t.Users)
.Map(m =>
{
m.ToTable("UserTenants");
m.MapLeftKey("UserId");
m.MapRightKey("TenantId");
});
}
Also, using fluent, if you want to disable cascade deleting on individual tables, you can use the .WillCascadeDelete(false) when mapping properties. Here's a great post on MSDN on how to use fluent notations.
You can remove the cascade delete convention this way:
using System.Data.Entity.ModelConfiguration.Conventions;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
And then see if the scaffolding changes. I've personally never used it.
Also, Microsoft (kind of) explains the FK naming conventions in this link under the header Foreign Keys.