Entity Framework: how to implement shared primary key with Fluent API? - asp.net

I have two tables and want to use PK of one of them in another one as PK.
This is my implementation with data annotation:
public class User
{
public System.Guid UserId { get; set; }
public string UserName { get; set; }
}
public class Student
{
[Key, ForeignKey("User")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public System.Guid StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// shared primary key
public virtual User User { get; set; }
}
Here Student table uses User's primary key.
How can I implement this with Fluent API?
(As a second question, if I delete a value from Student table, will accur a cascade delete?)

How can I implement this with Fluent API?
modelBuilder.Entity<Student>()
.HasRequired(s => s.User)
.WithOptional();
if I delete a value from Student table, will accur a cascade delete.
No, because Student is the dependent in the relationship (it carries the foreign key) and not the principal (which is User). Cascading delete is only in effect if you delete the principal. For a one-to-one relationship you have to enable it manually though:
modelBuilder.Entity<Student>()
.HasRequired(s => s.User)
.WithOptional()
.WillCascadeOnDelete(true);
Now, if a User gets deleted the related Student (if there is any) will be deleted as well.

Related

Cannot insert duplicate key. Composite Primary key. ASP.NET Entity Framework

I'm trying to create application using Entity Framework.
There's what I want to do (every entity has Id as well):
I want to use composite primary key in this case (PatientId + DiagnosisId).
There's a collection of Diagnoses in Patient model class:
public ICollection<Diagnosis> Diagnoses { get; set; }
public class Diagnosis
{
[Required]
[MaxLength(200)]
public String Type { get; set; }
public String Complications { get; set; }
public String Details { get; set; }
public Int32 DiagnosisId { get; set; }
public Patient Patient { get; set; }
public Int32 PatientId { get; set; }
}
Also in the database context I defined
public DbSet<Diagnosis> Diagnoses { get; set; }
and
modelBuilder.Entity<Diagnosis>().HasKey(x => new { x.DiagnosisId, x.PatientId });
in OnModelCreating method to create the composite primary key.
In an ASP.NET MVC CRUD controller I create Diagnosis and every time DiagnosisId is the same = 0. And I can't paste new data to database because it's like duplicate. That's my create post method on Pastebin if it could help
The parent key goes first:
modelBuilder.Entity<Diagnosis>().HasKey(x => new { x.PatientId, x.DiagnosisId });
Because you want all the Diagnoses for a particular Patent to be stored together.
And you need to request that the key is generated by the database. For EF Core its:
modelBuilder.Entity<Diagnosis>().Property(r => r.DiagnosisId).ValueGeneratedOnAdd();
for EF6 it's:
modelBuilder.Entity<Diagnosis>().Property(r => r.DiagnosisId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

Wrong navigation properties when using SQLite and Entity Framework 6

Lately I've been playing around with SQLite using Entity Framework but something is not very clear to me regarding the navigation properties of the generated entities after DB first approach. And more specifically, many-to-many relationships.
Note: Using ASP.NET Web Api OWIN project.
This is what I did:
I installed latest version of Entity Framework
I installed latest version of System.Data.SQLite
I used Firefox add-on to create my database. It generated my *.sqlite
Example for one of my many-to-many db definitions while creating the DB:
CREATE TABLE "Users"
(
"Id" INTEGER PRIMARY KEY NOT NULL UNIQUE ,
"IdSrvId" INTEGER NOT NULL UNIQUE ,
"FirstName" VARCHAR NOT NULL ,
"LastName" VARCHAR NOT NULL ,
"Email" VARCHAR NOT NULL ,
"About" VARCHAR NOT NULL ,
"GenderId" INTEGER NOT NULL UNIQUE ,
"BirthDate" DATETIME,
"PhoneNumber" VARCHAR
)
CREATE TABLE "UserLanguаges"
(
"Id" INTEGER PRIMARY KEY NOT NULL UNIQUE ,
"UserId" INTEGER NULL REFERENCES Users(Id),
"LanguageId" INTEGER NULL REFERENCES Languаges(Id)
)
CREATE TABLE "Langugaes"
(
"Id" INTEGER PRIMARY KEY NOT NULL UNIQUE ,
"Name" VARCHAR NOT NULL UNIQUE
)
After that, I used Visual Studio 2015 to create a Data Model using that *.sqlite file. Following this tutorial: SQLite EntityFramework 6 Tutorial
After the generation I got all of my tables as entities looking like this:
public partial class User
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public User()
{
this.GroupUsers = new HashSet<GroupUser>();
this.UserLanguаges = new HashSet<UserLanguаges>();
}
public long Id { get; set; }
public long IdSrvId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string About { get; set; }
public long GenderId { get; set; }
public Nullable<System.DateTime> BirthDate { get; set; }
public string PhoneNumber { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<UserLanguаges> UserLanguаges { get; set; }
}
public partial class Languаges
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Languаges()
{
this.UserLanguаges = new HashSet<UserLanguаges>();
}
public long Id { get; set; }
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<UserLanguаges> UserLanguаges { get; set; }
}
public partial class UserLanguаges
{
public long Id { get; set; }
public Nullable<long> UserId { get; set; }
public Nullable<long> LanguageId { get; set; }
public virtual Languаges Languаges { get; set; }
public virtual User User { get; set; }
}
What worries me here, are the navigation properties inside User and Language entities. As you can see, they make a reference to the 'bridge' table helping for the many-to-many relationship but not directly to the other entity as I expected.
I expected this:
public virtual ICollection<UserLanguаges> UserLanguаges { get; set; }
to look like this:
public virtual ICollection<Languаge> Languаges { get; set; }
inside of the User entity.
How can I fix that?
The only time Entity Framework can omit the join table is if that table consists purely of the keys of the tables being joined in a many-to-many relationship. It's the presence of the Id column on this table that is causing it to generate a new entity.
The only way around this is to remove that Id column and make that table have a composite key consisting of the UserId and LanguageId keys. If you cannot change the database schema, there's no other option but take a deep breath and accept how it works.
Some additional reading on how EF handles many-to-many relationships: https://msdn.microsoft.com/en-us/library/dd742359.aspx

How to set on delete cascade for self reference Foreign Key in Entity Framework in ASP.NET

I am developing an ASP.NET MVC project. I am using Entity Framework code first approach to interact with database. But I am having a problem with setting on cascade delete for self-reference foreign key for an entity.
This is my entity class with self reference foreign key
public class Category
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(55)]
public string MmName { get; set; }
public int? ParentId { get; set; }
[ForeignKey("ParentId")]
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
This is my context
public class StoreContext : DbContext
{
public StoreContext():base("DefaultConnection")
{
}
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasOptional(x => x.ParentCategory).WithMany(c => c.Categories).WillCascadeOnDelete();
}
}
When I run, multiple cascade path errors throw
Introducing FOREIGN KEY constraint 'FK_dbo.Categories_dbo.Categories_ParentId' on table 'Categories' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Is it possible set on delete cascade for self-referencing foreign key in entity framework code first?
i also faced the similar issue, and if i remember correctly what i found is EF doesn't support cassade delete on self reference, so we need to handle it by code. What i followed is
Remove the cascade delete from fluent api or generated migration.
Add code to delte/setnull all self-reference and then delete.

Entity Framework: how to solve "FOREIGN KEY constraint may cause cycles or multiple cascade paths"?

there are lots of questions about this problem, but I couldn't solve my case.
can some one please take a look at this:
I have an Office table which has one-many relationship with Doctor and Secretary tables. Both of the last tables, are derived from Employee table which has a shared-primary-key relationship with predefined Users table created by sqlmembershipprovider. It seems there is a many-many relationship between Users table and Roles table which I haven't any hand in it.
My problem was in creating a (zero,one) - (one) relationship between my Employee table and that Users table which I ended with a shared primary key relationship between them and the error raised, then. (Is there a better solution for that problem?)
here is the error:
Introducing FOREIGN KEY constraint
'FK_dbo.aspnet_UsersInRoles_dbo.aspnet_Users_UserId' on table
'aspnet_UsersInRoles' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints. Could not create constraint. See previous
errors.
here are my codes and membership codes after reverse engineering:
public class Office
{
public Office()
{
this.Doctors = new HashSet<Doctor>();
this.Secretaries = new HashSet<Secretary>();
}
[Key]
public System.Guid OfficeId { get; set; }
public virtual ICollection<Doctor> Doctors { get; set; }
public virtual ICollection<Secretary> Secretaries { get; set; }
}
public class Employee
{
[Key, ForeignKey("User")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public System.Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("Office")]
public System.Guid OfficeId { get; set; }
// shared primary key
public virtual aspnet_Users User { get; set; }
public virtual Office Office { get; set; }
}
public class Doctor :Employee
{
public Doctor()
{
this.Expertises = new HashSet<Expertise>();
}
//the rest..
public virtual ICollection<Expertise> Expertises { get; set; }
}
public class Secretary : Employee
{
// blah blah
}
public class aspnet_Users
{
public aspnet_Users()
{
this.aspnet_Roles = new List<aspnet_Roles>();
}
public System.Guid ApplicationId { get; set; }
public System.Guid UserId { get; set; }
//the rest..
public virtual aspnet_Applications aspnet_Applications { get; set; }
public virtual ICollection<aspnet_Roles> aspnet_Roles { get; set; }
}
public class aspnet_Roles
{
public aspnet_Roles()
{
this.aspnet_Users = new List<aspnet_Users>();
}
public System.Guid ApplicationId { get; set; }
public System.Guid RoleId { get; set; }
//the rest..
public virtual aspnet_Applications aspnet_Applications { get; set; }
public virtual ICollection<aspnet_Users> aspnet_Users { get; set; }
}
EDIT: and the relationships go deeper, there is a many-one relationship between Users table and Applications table, also between Roles and Applications too.
You can use the fluent api to specify the actions the error message suggests.
In your Context:
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<aspnet_UsersInRoles>().HasMany(i => i.Users).WithRequired().WillCascadeOnDelete(false);
}
Note that you have not included the definition for the table aspnet_UsersInRoles so this code may not work.
Another option is to remove all CASCADE DELETES by adding this
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
If you need more info about configuring relationships with the fluent api I suggest http://msdn.microsoft.com/en-US/data/jj591620
Also you can modify your migration class. In my case in the migration class was:
CreateTable(
"dbo.Spendings",
c => new
{
SpendingId = c.Int(nullable: false, identity: true),
CategoryGroupId = c.Int(nullable: false),
CategoryId = c.Int(nullable: false),
Sum = c.Single(nullable: false),
Date = c.DateTime(nullable: false),
Comment = c.String(),
UserId = c.String(),
LastUpdate = c.String(),
})
.PrimaryKey(t => t.SpendingId)
.ForeignKey("dbo.Categories", t => t.CategoryId, cascadeDelete: true)
.ForeignKey("dbo.CategoryGroups", t => t.CategoryGroupId, cascadeDelete: true)
.Index(t => t.CategoryGroupId)
.Index(t => t.CategoryId);
After removing "cascadeDelete: true" Update-Database works perfectly.
OR as false
.ForeignKey("dbo.Categories", t => t.CategoryId, cascadeDelete: false)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
Add this code in context
Just an Update:
As other answers suggest you need to NOT do any action on delete. Updated way of doing this is as follows for Entityframework Core 1.0
table.ForeignKey(name: "FK_Cities_Regions_RegionId",
column: x => x.RegionId,
principalTable: "Regions",
principalColumn: "RegionId",
onDelete: ReferentialAction.NoAction);
NOTE: Do ReferentialAction.NoAction on onDelete
Based on your comments, the problem is that you're using the same PK in both tables.
I'd change that right away, have Users PK as an extra field in Employees with a FK relationship to Users (one to one i guess). And add an independent PK to users (Another guid to respect the rest of your tables). If you want to guarantee that the new foreign key in Employees (the one that points to Users) is unique, you can always add a Unique Index.
That way you keep your structure all the same and you get rid of the cycle.

EF Code First using non-Primary key to create one-to-many relationship

I have the following two entities:
public class User
{
public int PKID { get; set; }
public string Login { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<AppAccess> AppAccessList { get; set; }
}
public class AppAccess
{
public int PKID {get; set; }
public string Login { get; set; }
public string AppCode { get; set; }
}
The PKID field is the primary key and identity column of each table but the Login field is what links them in a one-to-many relationship where the User is the principal entity.
How can I setup the navigation property AppAccessList using the fluent API, if it is even possible, to use the Login field as the relationship key? BTW, the entities are based on existing schema that cannot be refactored at this time.
I don't believe this is possible out of the box, certainly not the ability to add a navigation property. I tried using the new power tools to reverse engineer a database and it just ignored the relationship, neither could i create it using code (annotations or fluent interface).
You can create the relationship using raw sql on the on OnModelCreating method to make the constraint, but you'd need to use join's manually to navigate between the tables.

Resources