Cascade Delete with 2 parents, one child Entity Framework - c#

I have a parent class called Product:
public class Product
{
[Key]
public int ID { get; set; }
public ICollection<ProductValue> ProductValues { get; set; }
}
It has a child class called ProductValue
public class ProductValue
{
[Key]
public int ID { get; set; }
public int ProductID { get; set; }
public Product Product { get; set; }
public int LengthID { get; set; }
public Length Length { get; set; }
}
And I have another parent class called Length with its child also being ProductValue
public class Length
{
[Key]
public int ID { get; set; }
public ICollection<ProductValue> ProductValues { get; set; }
}
ProductValue objects will always have a Product parent and a Length parent. If a Length or Product object is deleted its child ProductValue objects also need to be deleted.
However, because ProductValue has multiple parents I get the exception:
Microsoft.Data.SqlClient.SqlException: 'Introducing FOREIGN KEY constraint 'FK_ProductValues_Products_ProductID' on table 'ProductValues' 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.'
Is there a way to get around this? I would like to not have to manually delete ProductValue objects.

Related

Error upon updating entries in two tables(Parent/Child) MVC

I have two tables, Parent and Child with a one to many relationship. What I have done is, in the Create View(Parent) I have added a few of the Child fields in. So upon posting back to the controller, the Parent fields and Child Fields are posting at once. I receive them all correctly and manage to save to the Parent table, However I am having an issue saving to the Child table.
Table structure as follows:
Parent: ID,DateFrom,DateTo,Name,Version,Status;
Child: ID,ParentID, Item;
This is the Parent Controller - Create method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,DateFrom,DateTo,Name,Version,Status")] Parent parent, Array childItems)
{
if (ModelState.IsValid)
{
db.Parent.Add(parent);
db.SaveChanges();
var id = parent.ID;
foreach (var _item in childItems)
{
var items = new Child()
{
Item = _item.ToString(),
ParentID = id,
};
db.child.Add(items);
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(parent);
}
childItems is an array containing enteries to the Item field of the Child Table.
What I am trying to achieve is as the entry to the Parent table is made(which happens successfully), I would like to filter through the array and make an entry to the Child table along with the respective ParentID. But when I am running this, I am getting the following error.
System.Data.Entity.Infrastructure.DbUpdateException: 'An error occurred while updating the entries. See the inner exception for details.'
SqlException: The INSERT statement conflicted with the FOREIGN KEY SAME TABLE constraint "FK_dbo.Child_dbo.Child_ParentID". The conflict occurred in database "aspnet-ABC_Automation-20191120105424", table "dbo.Child", column 'ID'.
The statement has been terminated.
I've debugged and noticed that in the foreach loop, The ParentID is holding its respective value, yet I cannot seem to figure out why the error is thrown.
Does anyone know how I can solve this?
Update:
Parent model:
public class Parent
{
[Key]
public int ID { get; set; }
public DateTime DateFrom { get; set; }
public DateTime DateTo { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string Status { get; set; }
public ICollection<Child> Items { get; set; }
}
Child Model:
public class Child
{
[Key]
public int ID { get; set; }
public int ParentID { get; set; }
public string Item { get; set; }
[ForeignKey("ParentID")]
public virtual Parent Items { get; set; }
}
public class Child
{
[Key]
public int ID { get; set; }
public string Item { get; set; }
public int ParentID { get; set; }
[ForeignKey("Parent")]
public Parent Items { get; set; }
}
Try changing your Child class like this. The reason here is ParentID is our actual foreign key.

Would the Entity Framework's navigational properties work if I drop foreign key constraints from the database?

As you know that developers mostly mock the relationship between tables instead of using physical relationships between table (yeah, the line drawn from one table to another if you put a foreign key constraint on the column).
But I believe that Entity Framework doesn't work properly if physical relationships aren't there for navigational properties.
So, is there any way around?
My classes:
public class Phones
{
public int ID { get; set; }
public string Model { get; set; }
public string Manufacturer { get; set; }
public List<Users> Users { get; set; }
}
public class Sims
{
public int ID { get; set; }
public string Code { get; set; }
}
This creates a 1-M relationship from User -> Sims.
But what if I drop the foreign key constraint and leave it as it is, how will the navigational properties work then?
At this case better to remove references from both classes and handle relations manually outside of these classes:
public class Sims
{
public int ID { get; set; }
public string Code { get; set; }
//public User User { get; set; }
public int UserID { get; set; }
}

Configuring 1:N relationship delete cascade fails in Code First

I'm having troubles getting this bit of a Code First model to produce a database.
Here are the two models in question:
public class Workflow : BaseEntity, IBaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public bool Published { get; set; }
public DateTime? PublishedOn { get; set; }
public int Revision { get; set; } = 0;
public Guid PermanentId { get; set; }
public virtual ICollection<WorkflowStage> WorkflowStages { get; set; }
public virtual ICollection<WorkflowDataFactor> WorkflowDataFactors { get; set; }
public virtual ICollection<WorkflowInstance> WorkflowInstances { get; set; }
}
And here's the 2nd model:
public class WorkflowStage : BaseEntity, IBaseEntity
{
public int WorkflowId { get; set; }
public virtual Workflow Workflow { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Color { get; set; }
public int Order { get; set; }
public int Revision { get; set; } = 0;
public Guid PermanentId { get; set; }
public virtual ICollection<WorkflowStageStep> WorkflowStageSteps { get; set; }
public virtual ICollection<WorkflowStageInstance> WorkflowStageInstances { get; set; }
}
BaseEntity provides all my other entities with an Id property, which is the primary key.
After getting this error:
Introducing FOREIGN KEY constraint
'FK_dbo.WorkflowStage_dbo.Workflow_WorkflowId' on table
'WorkflowStage' 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.
I implemented this in my context class:
modelBuilder.Entity<Workflow>().HasMany(x => x.WorkflowStages).WithRequired().WillCascadeOnDelete(false);
If I understand correctly, this is saying if a WorkflowStage gets deleted, the associated Workflow shouldn't be deleted.
Isn't it what EF6 is looking for according to the error at the top ?
Thanks.
Change the code in your context class for:
modelBuilder.Entity<Workflow>()
.HasOptional<WorkflowStages>(s => s.WorkflowStages)
.WithMany()
.WillCascadeOnDelete(false);
"Introducing FOREIGN KEY constraint 'FK_dbo.WorkflowStage_dbo.Workflow_WorkflowId' on table 'WorkflowStage' may cause cycles or multiple cascade paths..."
What the could mean is that you have multiple tables that lead to another table and each of them has the OnCascade setting. You can't do this in SQL Server.
Table1 ==> Table2 with Cascade
Table3 ==> Table2 with Cascade <<-- no no no!!
So how many tables interact with WorkFlowStage? Are there multiple tables that could cascade to it? Would it be possible to wind up with orphan records by removing the cascade from one table or the other? For instance if you remove the cascade delete from WorkFlow then could you wind up with WorkFlowStage that doesn't belong to anything? Perhaps removing the cascade from the other table involved would be more appropriate.
In some cases I have created Triggers for AFTER DELETE on a table which can be used to clean up child records without the constraint.
HTH,
Mike

Error adding entities to database

I'm getting the following exception message:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
And my inner exception is related to me change column names on my table as they didn't make sense before (They were given the name Customer_Customer_ID) and renamed my Sku and my Customer objects that are composed in my cart, with the following data annotations
[Table("Cart")]
public class Cart
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public Guid CartID { get; set; }
[Column("SkuID")]
public virtual Sku Sku { get; set; }
[Required]
public int Quantity { get; set; }
[Column("CustomerID")]
public virtual Customer Customer { get; set; }
public bool IsCheckedOut { get; set; }
}
This is the inner exception of the exception message
"Invalid column name 'Customer_Customer_ID'.\r\nInvalid column name
'Sku_SKU_ID'."
What is the correct way to name these composed objects and do I need to modify my OnModelCreating to reflect these changes to entity framework?
Add a SkuID and CustomerId property to your entity class.
public class Cart
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public Guid CartID { get; set; }
public int SkuID { set;get;}
public virtual Sku Sku { get; set; }
[Required]
public int Quantity { get; set; }
public int CustomerID{ set;get;}
public virtual Customer Customer { get; set; }
public bool IsCheckedOut { get; set; }
}
Assuming your Sku and Customer class has an ID property of type int and it is the primary key. EF will create Cart table with foreign keys to Sku and Customer table(s) properly because the current structure follow the convention.
Now your entity class has both integer Id (CustomerID) and the navigation property (Customer)

C# Add a column to Auto-Generated Table by Entity Framework

I'm wondering if this scenario is possible using Entity Framework.
I am using Code-First and have defined a Domain Model as follows:
public class PrintJob
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public virtual ICollection<StockItem> stockItemstoPrint { get; set; }
}
If I leave the above to Entity Framework and add the migration updating the database without adding the Foreign key in the StockItems Model (Which I don't want as I'd rather not have a two-way link) it will create a table for me named PrintJobStockItems which will hold PrintJobID and StockItemID
- This however is fine but I was wondering if I wanted to add a property to the PrintJobStockItems with a bool 'Printed' can it be done and have logic to update that bool value? The reason is, I want to be able to set for each individual stock item whether or not it has been printed - of course not against the stockItem Model as it should not know about PrintJobs.
If I can't achieve this, it means I will have to create a print job for every stock item, which to me isn't ideal.
You can't access the behind the scenes join table, but the workaround is to create 2 one to manys:
public class StockItem
{
public int Id { get; set; } // Identity, Key is default by convention so annotation not needed
public virtual ICollection<StockItemPrintJob> StockItemPrintJobs { get; set; }
}
public class PrintJob
{
public int Id { get; set; } // Identity, Key is default by convention
public virtual ICollection<StockItemPrintJob> StockItemPrintJobs { get; set; }
}
public class StockItemPrintJob
{
[Key, Column(Order = 0)]
public int StockItemId { get; set; }
[Key, Column(Order = 1)]
public int PrintJobId { get; set; }
public bool IsPrinted { get; set; }
public virtual StockItem StockItem{ get; set; }
public virtual PrintJob PrintJob { get; set; }}
}
Then you can do something like
var item = context.StockItemPrintJob.First(sp => sp.StockItemId == stockId && sp.PrintJobId == printJobId);
item.IsPrinted = true;
context.SaveChanges();

Categories