Updating parent entity without changing the children - c#

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.

Related

Entity Framework Core relationship tracking

I have a 0-to-many relationship between Product and Category, configured as follows:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int? CategoryId { get; set; }
public Category Category { get;set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
When I try to manipulate data (and save), I have a strange behaviour with the CategoryId
// ...
var cat1 = this.Context.Categories.Find(1);
var cat2 = this.Context.Categories.Find(2);
var product1 = new Product();
product1.Name = "Test";
product1.Category = cat1; // the CategoryId property is NOT set
this.Context.Products.Add(product1); // the CategoryId property is set
this.Context.SaveChanges();
product1.Category = cat2; // the CategoryId property is NOT updated
this.Context.SaveChanges(); // the CategoryId property is updated
Is this behaviour correct? Because I would have expected that, once in tracker, the CategoryId field to be updated when the Category field is updated...
Am I wrong or am I doing something wrong? I cannot find anything in the docs about this...
Thanks in advance
This is the correct behavior. Entity is updated only after SaveChanges.
But if you need to update CategoryId immediately
product1.CategoryId = cat2.Id;
and it is a better way to update. Sometimes you will need to add after this , before SaveChanges:
Context.Entry(product1).State = EntityState.Modified;
And by the way, to get 0-to-many you have to fix your Category class
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}

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.

Reference to objects lost afer adding a new item

I have a model that looks like this:
public class Parent
{
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public string Name { get; set; }
public Parent Parent { get; set; }
public Child ParentChild { get; set; }
public List<Child> Children { get; set; }
}
That means that I have a parent that can have children. The children can also have children. Each child has a reference to the parent it belongs to (even if it is a child of a child).
I have the following in my database:
Parent
Child_1
Child_1_1
Child_1_1_1
I now want to add Child_1_2.
var child = new Child(){ Name = "Child 1.2" };
child.ParentChild = child_1;
child.Parent = parent;
context.Children.Add(child);
context.SaveChanges();
My problem is now that this will change Child_1_1 and Child_1_1_1. The reference to their parent will be lost after this Code. They will be null in the database.
This has something to do with Entity Framework not loading all the references (especially not references that are nested).
Is there a way to do this and tell the Entity Framework to add the Parent but not change the parent or any of its children?
My guess is that you are not showing the exact model here. Your model would mean two different tables.
A quick fix would be to add parent.Children.Add(child).
Although I would severely recommend you to redo your model into something similar (although still doesn't make sense in a larger contex):
public class Person
{
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Parent : Person
{
// Probably this class would contain something useful
}
public class Child : Person
{
public Person ParentPerson { get; set; }
}

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()

Entity Framework Code First Many to Many creating duplicate rows

My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away.
I have a User which has a list of UserContact's which itself has a ContactOption. Its a fairly simple 1 to many, many to 1 with the UserContact table in the middle.
If I pull the user out of the db and create a new UserContact, but set the ContactOption to an existing item (which I've pulled out of the db), when I SaveChanges, entity framework creates a new ContactOption in the database that is essentially a duplicate of the one I added to the UserContact (with the exception that it gets a new id).
I've battled with this for several hours and can't figure this out. Any ideas?
I am using a Repository pattern for my database queries, but I have ensured they are sharing the same context.
I pull the user out of the database with this:
var user = _context.Users.Include("UserContacts.ContactOption")
.Where(id => id == 1);
And contact options are pulled out with:
var co = _context.ContactOptions.FirstOrDefault(c => c.Id == id);
And I add the ContactOption to the UserContact like so:
var contactOption = _context.ContactOptions.FirstOrDefault(c => c.Id == someId);
var contact = new UserContact { ContactOption = contactOption };
contact.Data = "someData";
user.UserContacts.Add(contact);
My model looks like this:
public class User
{
[Key]
public int Id { get; set; }
public virtual ICollection<UserContact> UserContacts { get; set; }
}
public class UserContact
{
[Key]
public int Id { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
public class ContactOption
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
I run your code and got the exact expected results: A new row in UserContacts table with the existing UserId and ContactOptionId, so I am not sure what's happing in there, but you can try to explicitly have FKs in UserContact object so that you'll have full control over how Code First inserts records for you. For that, you need to change UserContact as follows:
public class UserContact
{
public int Id { get; set; }
// By Convention these 2 properties will be picked up as the FKs:
public int UserId { get; set; }
public int ContactOptionId { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
And then you can change your code like this:
var contactOption = _context.ContactOptions.Find(someId);
var user = _context.Users.Find(1);
var contact = new UserContact
{
ContactOptionId = contactOption.Id,
UserId = user.Id,
Data = "someData"
};
user.UserContacts.Add(contact);
context.SaveChanges();
My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away. Thanks to Morteza Manavi for pointing me in the right direction.

Categories