Entity framework code first many to many with extra key - c#

I'm trying to create a linked table that will allow me to have a many to many relationship between my product and accessory tables.
My classes are like this:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Accessory> Accessories { get; set; }
}
public class Accessory {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Machine> Machine { get; set; }
}
public class Type {
public int Id { get; set; }
public string Name { get; set; }
}
The same accessory can be on a product more than once if it is a different type, which will be determined in the link table. Something like this:
public class ProductAccessoryLink {
public int productId {get; set;}
public int accessoryId {get; set;}
public int typeId {get; set}
public int sort {get; set;}
public string notes {get; set}
}
Is this the right approach.
EDIT
This is the error I'm getting when I run update-database:
Introducing FOREIGN KEY constraint
'FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId' on table
'ProductAccessoryLinks' 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.
This is the sql causing the error: ALTER TABLE
[dbo].[ProductAccessoryLinks] ADD CONSTRAINT
[FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId] FOREIGN KEY ([TypeId])
REFERENCES [dbo].[Types] ([Id]) ON DELETE CASCADE

In your case you need to map explicitly the junction table. Your model would be like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}
public class Accessory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}
public class Type
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductAccessoryLink
{
public int ProductId { get; set; }
public int AccessoryId { get; set; }
public int TypeId { get; set; }
public int sort { get; set; }
public string notes { get; set; }
public virtual Type Type { get; set; }
public virtual Product Product { get; set; }
public virtual Accessory Accessory { get; set; }
}
And you could configure the relationships overriding the OnModelCreating method on your context this way:
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductAccessoryLink>().HasKey(i => new { i.ProductId, i.AccesoryId, i.TypeId});
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Product)
.WithMany(k => k.ProductAccessoryLinks)
.HasForeignKey(i=>i.ProductId);
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Accesory)
.WithMany(k => k.ProductAccessoryLinks)
.HasForeignKey(i=>i.AccesoryId);
modelBuilder.Entity<ProductAccessoryLink>()
.HasRequired(i => i.Type)
.WithMany()
.HasForeignKey(i=>i.TypeId);
}
EF lets you configure directly many-to-many relationships of the way you were attempting to. Thereby EF is responsible for build a join table in the database with the appropriate keys of the tables it’s joining. (The keys are both primary keys of the join table and foreign keys pointing to the joined tables). That lets you to get your data across the join table without you having to be aware of its presence. But when you want to personalize that table (adding, for example, some additional properties), you need to map it explicitly as I show above.
Update
That exception is caused when you have multiple paths of cascade deletes that could end trying to delete the same row in the Types table. To resolve that problem I recommend you check my answer in this post

Related

Entity Framework Migration Cascading Delete Warning

I have two classes that relate to one another (one-to-many) and I thought I had the properties setup correctly, but when I run the Update-Database command for my migration, I get the following error:
Introducing FOREIGN KEY constraint
'FK_dbo.ParentEnrollment_dbo.CellGroup_CellGroupID' on table
'ParentEnrollment' 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 or index. See previous
errors.
My two classes:
[Table("CellGroup")]
public class CellGroup : BaseEntity
{
public Guid AcademicYearID { get; set; }
[ForeignKey("AcademicYearID")]
public virtual AcademicYear AcademicYear { get; set; }
public Guid LeaderID { get; set; }
[ForeignKey("LeaderID")]
public virtual Parent Leader { get; set; }
public Guid PreviousGroupID { get; set; }
[ForeignKey("PreviousGroupID")]
public virtual CellGroup PreviousGroup { get; set; }
public string Name { get; set; }
public int MaximumSize { get; set; }
public virtual ICollection<ParentEnrollment> Parents { get; set; }
}
and
[Table("ParentEnrollment")]
public class ParentEnrollment : BaseEntity
{
public Guid ParentID { get; set; }
[ForeignKey("ParentID")]
public virtual Parent Parent { get; set; }
public Guid AcademicYearID { get; set; }
[ForeignKey("AcademicYearID")]
public virtual AcademicYear AcademicYear { get; set; }
public bool FirstTimeEnrolling { get; set; }
public string HSLDAAccountNumber { get; set; }
public DateTime HSLDARenewalDate { get; set; }
public string CurrentChurch { get; set; }
public string CurrentChurchContact { get; set; }
public string CurrentChurchPhone { get; set; }
public Guid CellGroupID { get; set; }
[Required]
[ForeignKey("CellGroupID")]
public virtual CellGroup CellGroup { get; set; }
public bool VolunteerBuyOut { get; set; }
public Guid VolunteerPositionID { get; set; }
[ForeignKey("VolunteerPositionID")]
public virtual VolunteerPosition VolunteerPosition { get; set; }
public string VolunteerPositionNotes { get; set; }
public virtual ICollection<StudentEnrollment> StudentEnrollments { get; set; }
}
I only have the Parents property on the CellGroup class so I can easily access the list of enrollments in that cell group. I tried to remove the property to see if it cleared up the warning/error, but it did not. Can someone spot where I have gone wrong with my model(s)?
This error says that you cannot introduce a foreign key from table ParentEnrollment to table CellGroup that has cascading delete enabled, because this will create multiple cascade paths, which is not allowed on SQL Server.
According to the code you posted both tables have relations to a table Parent as well as AcademicYear, which are on non nullable FK columns, so EF will enable cascading on delete by default. With another FK from ParentEnrollment to CellGroup there would be multiple cascade paths, e.g. Parent to CellGroup to ParentEnrollment and Parent to ParentEnrollment, and this is causing your error. Removing the Parent property won't solve this because there still is the same cascading path problem starting from table AcademicYear.
So you have to disable cascading delete for your foreign key, which has to be done using Fluent API in your DbContext like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ParentEnrollment>()
.HasRequired(m => m.CellGroup)
.WithMany(m => m.Parents)
.HasForeignKey(m => m.CellGroupID)
.WillCascadeOnDelete(false);
}

Adding Extra Column to join table

I currently have an employee model
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<StateLicenseType> Licenses { get; set; }
and a License Type Model
public class StateLicenseType
{
public int StateLicenseTypeId { get; set; }
public string State { get; set; }
public string LicenseName { get; set; }
public virtual Employee Employee { get; set; }
}
This relationship can be one to many, but I also need to add some information to the license when saved. I need to be able to store the employees unique license number and have not been able to find out how to do this while searching around. Is there a way to have Entity Framework add a column to a join table and then even if I have to, update it myself?
Is there a better/different way to model this relationship with EF?
In an old DB the table was created like this,
CREATE TABLE `nmlsstatelicenses` ( `peopleid` int(11) DEFAULT NULL, `statelicensetypeid` int(11) DEFAULT NULL, `licensenumber` varchar(25) DEFAULT NULL)
You need to create a third entity which will be a linking entity (like a linking table in many-to-many relationships in database. Here is an example: many-to-many relationships with additional information.
So you would have the following entities in your model:
public Employee
{
public string EmployeeId { get;set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<LicenseRegistration> RegisteredLicenses { get; set; }
}
public LicenseType
{
public int StateLicenseTypeId { get; set; }
public string State { get; set; }
public string LicenseName { get; set; }
public virtual ICollection<LicenseRegistration> RegisteredLicenses { get; set; }
}
public LicenseRegistration
{
//properties for the additional information go here
/////////////////////////////////////////////////////
public int EmployeeId {get;set;}
[ForeignKey("EmployeeId")]
public Employee Employee {get;set;}
public int LicenseTypeId {get;set;}
[ForeignKey("LicenseTypeId")]
public LicenseType {get;set;}
}
Then, in your DBContext file, you will need to define 1-to-many relationship between Employee and LicenseRegistration, and between LicenseType and LicenseRegistration.
Hope this helps!
UPDATE
Here is how you would set up the relationships:
modelbuilder.Entity<LicenseRegistration>()
.HasRequired(lr => lr.LicenseType)
.WithMany(lt => lt.RegisteredLicenses)
.HasForeignKey(lr => lr.LicenseTypeId);
modelbuilder.Entity<LicenseRegistration>()
.HasRequired(lr => lr.Employee)
.WithMany(e => e.RegisteredLicenses)
.HasForeignKey(lr => lr.EmployeeId);

Database Causing "Cycles" or "Multiple Cascade Paths"

I'm using ASP.NET MVC 4 Entity Framework 5 to generate a code first migration for a database structure of the below classes that have relationships between them. However I've encountered a problem where it's causing this error whenever I try to update my database from the migration:
Paste Bin of the migration file can be found here: http://pastebin.com/ngXacrKV
Error returned:
Introducing FOREIGN KEY constraint 'FK_dbo.Bookings_dbo.Rooms_RoomId' on table 'Bookings' 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.
Bunk.cs
public class Bunk
{
[Key]
public int BunkId { get; set; }
public BunkStatus BunkStatus { get; set; }
[ForeignKey("Room")]
public int RoomId { get; set; }
public virtual Room Room { get; set; }
// Added for convenience
public virtual ICollection<Booking> Bookings { get; set; }
}
Room.cs
public class Room
{
[Key]
public int RoomId { get; set; }
public string RoomName { get; set; }
public Gender RoomGender { get; set; }
public RoomStatus RoomStatus { get; set; }
public virtual ICollection<Bunk> Bunks { get; set; }
// Added for convenience
public virtual ICollection<Booking> Bookings { get; set; }
}
Bookings.cs
public class Booking
{
[Key]
public int BookingId { get; set; }
//[ForeignKey("UserProfile")]
//public int UserProfileId { get; set; }
//public UserProfile UserProfile { get; set; }
[ForeignKey("Bunk")]
public int BunkId { get; set; }
public Bunk Bunk { get; set; }
public int Duration { get; set; }
[ForeignKey("Preferred_Room")]
public int RoomId { get; set; }
public Room Preferred_Room { get; set; }
public Decimal Price { get; set; }
public BookingStatus BookingStatus { get; set; }
}
What would the best work around be to remove this issue without causing too much disturbance to the original class structure. I'm not too worried about adding new joining tables as long as I can still access the code in a Lazy loading way in my controllers/view models.
you can try to specify Fluent API to set no cascade delete
public class YOURContext: DbContext{
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// here is where fluent API goes.
// I suspected the error is EF wanting a NO cascade delete. Hence the suggestion to try
entity<Booking>.HasOptional(t => t.Bunk)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false); // <<<<<< this is the option to try.
// you may also need to try the same with Preferred_Room as well.
If no cascade delete is required then in Database context class we can set cascade delete to false for all the relationships by overriding OnModelCreating method as given below. Default EF makes it cascade delete that is why you are getting exception of cycles or multiple delete path.
public class MyContext: DbContext
{
//db sets defined
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PrimaryKeyTable>().HasMany(x => x.ChildTableCollection).WithRequired(x =>
Key).WillCascadeOnDelete(false);
//In your case
modelBuilder.Entity<Bunk>().HasMany(x => x.Bookings).WithRequired(x =>
x.BunkId).WillCascadeOnDelete(false);
// same for room if required.
}
}

Mapping a foreign key with a custom column name

I'm using Entity Framework 4.3 code-first with Oracle. I'm getting the following error:
System.InvalidOperationException : The ForeignKeyAttribute on property 'WidgetSequence' on type 'WidgetDistributor.WidgetEntity' is not valid. The foreign key name 'WIDGETSEQUENCE_ID' was not found on the dependent type 'WidgetDistributor.WidgetEntity'. The Name value should be a comma separated list of foreign key property names.
My entities are like this:
[Table("WIDGETENTITIES")]
public class WidgetEntity {
[Column("WIDGETENTITY_ID")]
public int Id { get; set; }
[ForeignKey("WIDGETSEQUENCE_ID")]
public WidgetSequence Sequence { get; set; }
// and other properties that map correctly
}
[Table("WIDGETSEQUENCES")]
public class WidgetSequence {
[Column("WIDGETSEQUENCE_ID")]
public int Id { get; set; }
[Column("NUMBER")]
public int Number { get; set; }
}
My code seems correct. What have I done wrong, here?
If you don't want to use fluent syntax, there are three other ways of implementing the reference using data annotations (Personally I prefer data annotations as they seem easier to read and are written just above the property they are affecting):
1.1)
Use ForeignKey (with an associated property) - version 1
[Table("WIDGETENTITIES")]
public class WidgetEntity {
[Column("WIDGETENTITY_ID")]
public int Id { get; set; }
[Column("WIDGETSEQUENCE_ID")]
public int WidgetSequenceId { get; set; }
[ForeignKey("WidgetSequenceId")] //Has to be a property name, not table column name
public WidgetSequence Sequence { get; set; }
// and other properties that map correctly
}
[Table("WIDGETSEQUENCES")]
public class WidgetSequence {
[Column("WIDGETSEQUENCE_ID")]
public int Id { get; set; }
[Column("NUMBER")]
public int Number { get; set; }
}
1.2)
Use ForeignKey (with an associated property) - version 2
[Table("WIDGETENTITIES")]
public class WidgetEntity {
[Column("WIDGETENTITY_ID")]
public int Id { get; set; }
[ForeignKey("Sequence")] //Has to be a property name, not table column name
[Column("WIDGETSEQUENCE_ID")]
public int WidgetSequenceId { get; set; }
public WidgetSequence Sequence { get; set; }
// and other properties that map correctly
}
[Table("WIDGETSEQUENCES")]
public class WidgetSequence {
[Column("WIDGETSEQUENCE_ID")]
public int Id { get; set; }
[Column("NUMBER")]
public int Number { get; set; }
}
2)
You can also use the InversePropertyAttribute.
[Table("WIDGETENTITIES")]
public class WidgetEntity {
[Column("WIDGETENTITY_ID")]
public int Id { get; set; }
[InverseProperty("WidgetEntities")]
public WidgetSequence Sequence { get; set; }
// and other properties that map correctly
}
[Table("WIDGETSEQUENCES")]
public class WidgetSequence {
[Column("WIDGETSEQUENCE_ID")]
public int Id { get; set; }
[Column("NUMBER")]
public int Number { get; set; }
public virtual List<WidgetEntity> WidgetEntities { get; set; }
}
ForeignKey attibute expects a property name in your class as the argument but you given the column name. Use fluent mappings.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WidgetEntity>()
.HasRequired(w => w.Sequence)
.WithMany()
.Map(m => m.MapKey("WIDGETSEQUENCE_ID"));
}
There is a table called Users and it has a primary key called UserID.
There is another table called Directory, and it has a column called UserID which is defined as a foreign key to the Users table.
I'm able to use the ForeignKey annotation to map the foreign key like this:
[ForeignKey("xyzzy")]
public int? UserID { get; set; } // This is a column in the table
public virtual User xyzzy { get; set; } // This is my instance of User

Introducing FOREIGN KEY constraint error issue

I am a bit confused as to why I am getting this error:
Introducing FOREIGN KEY constraint 'FK_QuestionTerms_Terms_TermId'
on table 'QuestionTerms' 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.
I have a class Question and a class Term, Questions may have any number of Terms associated with them, and Terms may have any number of Questions associated with them. So I am attempting to create a many to many relationship between the two. First I attempted to use convention, and I am allowing EntityFramework to create the database. This is the Question class
public class Question
{
public Guid Id { get; set; }
public int QuestionNumber { get; set; }
public string StatementHtml { get; set; }
public string AnswerHeaderHtml { get; set; }
public string NotesHtml { get; set; }
public Guid CategoryId { get; set; }
public Guid CourseId { get; set; }
public Guid QuestionTypeId { get; set; }
public Guid? SimulationId { get; set; }
public Guid? SimulationTabId { get; set; }
public ICollection<Term> Terms { get; set; }
public ICollection<ReferenceItem> ReferenceItems { get; set; }
}
And here is the Term Class
public class Term
{
public Guid Id { get; set; }
public string Name { get; set; }
public string StatementHtml { get; set; }
public string Authority { get; set; }
public Guid ProductId { get; set; }
public Product Product { get; set; }
public ICollection<Question> Questions { get; set; }
}
I have also attempted to override OnModelCreating as follows, both process result is the exact same error code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Question>()
.HasMany(q => q.Terms)
.WithMany(t => t.Questions)
.Map(x =>
{
x.MapLeftKey("QuestionId");
x.MapRightKey("TermId");
x.ToTable("QuestionTerms");
});
}
The problem is that a cacade delete would go back and forth between the tables.
For example first deleting term A which would delete question 1,2 and 3. Question 1 was also used in term B so term B must be deleted .....
It therefore stops you creating such constraints.
There is a good coverage of how to fix it here: Entity Framework 4.1 InverseProperty Attribute and ForeignKey
Edit
This could be a side effect of other problems. You should start with a much simpler model and then gradually build it up.
For example:
Why do you have both ProductId and product
Why CategoryId and not Category
...
Try adding it in your OnModelCreating() method
modelBuilder.Entity<Question>().HasRequired(oo => oo.Term).WithMany(oo => oo.Questions).WillCascadeOnDelete(false);

Categories

Resources