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

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.

Related

Cascade Delete with 2 parents, one child Entity Framework

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.

How to add multiple entity with childs at once

I'm having trouble adding multiple entities with multiple children at once in foreach loop.
Its obvious ef cannot track ids in foreach loop. But maybe there are other solutions that you can guide me.
The error when I tried to add multiple entities with a child is:
The instance of entity type cannot be tracked because of another instance
with the same key value for {'Id'} is already being tracked. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached.
For example:
public class Order
{
public Order()
{
OrderDetails = new HashSet<OrderDetail>();
}
[Key]
public int Id { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int? CompanyId { get; set; }
public int? PartnerId { get; set; }
public decimal TotalNetPrice { get; set; }
public decimal TotalPrice { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[ForeignKey("PartnerId")]
public virtual Partner Partner { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
[Key]
public int Id { get; set; }
public int OrderId { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int LineNumber { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
}
Here is my code in the method:
foreach (var order in orderList)
{
// consider we create/cast object to Order class.
_orderService.Add(order);
// in here we don't have id, because we didn't call savechanges.
foreach(var orderDetail in order.orderDetailList)
{
// consider we create/cast object to OrderDetail class.
orderDetail.orderId = order.Id;
// in here, order id is always 0 as expected.
_order.OrderDetails.Add(orderDetail);
}
}
try
{
await uow.SaveChangesAsync();
}
catch(Exception exception)
{
var msg = exception.Message;
}
I tried to use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute for the identity columns.
According to documentation (https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations):
Depending on the database provider being used, values may be generated
client side by EF or in the database. If the value is generated by the
database, then EF may assign a temporary value when you add the entity
to the context. This temporary value will then be replaced by the
database generated value during SaveChanges().
So it should give at least temp id to track it. But it didn't work with my case.
I also tried the same approach on model creating a part in context. But again the same result. No success.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.Property(x => x.Id)
.ValueGeneratedOnAdd();
}
It seems like the best solution is to make a transaction and save order before details and get real id and then add details. But it has performance issues as you know.
I'm wondering if there is any other best practice for that issue?
Thank you.
Try this:
foreach (var order in orderList)
{
_orderService.Add(order);
foreach(var orderDetail in order.orderDetailList)
{
// Add reference Of Order to OrderDetails, not an id
orderDetail.Order = order;
_order.OrderDetails.Add(orderDetail);
}
}
In this case EF will know how to connect Order and OrderDetail on SaveChangesAsync

Updating parent entity without changing the children

I have a one-to-many relationship... I am working in a web environment (disconnected environment). Imagine user wanting to update only the parent entity, without having to load all the child entities, is it possible?
This is the code:
public class Parent
{
public int Id { get; set; }
public string Description { 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; }
public string Data { get; set; }
}
I want to update description of Parent with id = 5, the new description is coming from User:
Parent parent = new Parent()
{
Id = 5, // I already know the user Id
Description = "new description from User";
Children = null; // I don't want the children to be changed
}
dbContext.Parent.Attach(parent);
dbContext.Entry(parent).State = EntityState.Modified;
dbContext.SaveChanges();
I am not sure if this is the right approch? will existing Children be deleted (since the children list is null)?
is it possible?
Yes, you are doing right.
According to your sample
dbContext.Parent.Attach(parent);
dbContext.Entry(parent).State = EntityState.Modified;
dbContext.SaveChanges();
It just effects on parent table only.

Best way to edit (add/remove) rows in a 0 to many child table

I'm using EF 6.1 Code First and MVC 5.
I have a parent table with 0 to many children in a child table.
Below are my Parent / Child models my View Models, and the code in a controller that edits.
What I have below is working to make sure it removes the rows from the child table that were removed, adds new ones, and leaves the existing rows that weren't changed alone.
However, I'm wondering if there is a better, less complex way to take care of the situation.
public class Parent
{
[Key]
[Required]
public int ParentId { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
[Required]
public int ChildId { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
public class ParentViewModel
{
public ChildrenViewModel Children { get; set; }
}
public class ChildrenViewModel
{
public int[] SelectedValues { get; set; } // The Selected Values
public IEnumerable<SelectListItem> Items { get; set; } // Items in the List Box
}
if (parentViewModel.Children == null)
{
// If the collection in the parent view model is null, then clear the collection in the parent and save
parent.Children.Clear();
}
else
{
// add new items if it exists in the view model but isnt already in the database
foreach (int childId in parentViewModel.Children.SelectedValues)
{
if (!parent.Children.Any(f => f.ChildId == childId))
{
offense.Children.Add(new Child{ ChildId = childId });
}
}
// delete children from the parent that are no longer present in the view model collection
IList<Child> deleteList = new List<Child>();
foreach (Child child in parent.Children)
{
if (!parentViewModel.Children.SelectedValues.Contains(child.ChildId))
{
deleteList.Add(child);
}
}
foreach (Child child in deleteList)
{
parent.Children.Remove(child);
}
}
db.Save()

Cascade delete in EF4 CTP5 Code First performs update on child records

Using the method described here, I am attempting to delete a parent record and all the associated child records. However, what happens is the parent is deleted as expected, but child record key field is updated to NULL instead of being deleted.
I also set the child table foreign key's Delete Rule to Cascade, and deleting from the parent table in SQL Server Management performs the cascade delete as expected.
I started by following this walkthough, and modifying the code to perform a delete.
this is the code:
using (var db = new ProductContext())
{
var food = db.Categories.Find("FOOD");
((IObjectContextAdapter)db).ObjectContext.LoadProperty(food, f => f.Products);
db.Categories.Remove(food);
int recordsAffected = db.SaveChanges();
Is there something I'm missing? Or is orphaned child records the intended result?
The association between Product and Category has been configured as optional due to the fact that the foreign key property on Product class (i.e. Product.CategoryId) has a nullable type (i.e. string). To make this association required so that the child entity is get deleted as a result of deleting the parent you need to mark CategoryId as Required as I did in the following code:
public class Category
{
public string CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
[Required]
public string CategoryId { get; set; }
public virtual Category Category { get; set; }
}

Categories