Entity Framework: Removing child entity - c#

I am trying to remove category object from News but it doesn't work
My code looks like this:
var OriginalCategoriesIds = db.News.Where(w => w.NewsId == 1)
.SelectMany(v => v.Categories)
.ToList();
News NewsToUpdate = new News() { NewsId = 1 };
db.News.Attach(NewsToUpdate);
foreach (var category in OriginalCategoriesIds)
{
if (!model.SelectedCategoriesIds.Contains(category.CategoryId))
{
NewsToUpdate.Categories.Remove(category);
}
}
db.SaveChanges();

Here is your issue:
var OriginalCategoriesIds = db.News.Where(w => w.NewsId == 1).SelectMany(v => v.Categories).ToList();
News NewsToUpdate = new News() { NewsId = 1 };
db.News.Attach(NewsToUpdate);
foreach (var category in OriginalCategoriesIds)
{
if (!model.SelectedCategoriesIds.Contains(category.CategoryId))
{
db.News.Categories.Remove(category);// <---change like this
}
}
db.SaveChanges();

EF should be smart enough to look at your updated entity and know which ones to remove based on which ones are present. Instead of checking which ones don't belong anymore, check which ones do. In theory it should properly sync up then. Also, most likely you also want to check which ones were added that weren't there. Instead of loading existing category ids from the news, just load all the ids that are now attached and then add them all in.
News NewsToUpdate = new News() { NewsId = 1 };
var updatedCategoryIds = model.SelectedCategoriesIds;
NewsToUpdate.Categories.AddRange(db.Categories.Where(c => updatedCategoryIds.Contains(c.CategoryId));
db.News.Attach(NewsToUpdate);
db.SaveChanges();

Related

ASP.NET Core MVC : remove many-to-many entries before posting Edit method

I have a many-to-many relationship between Project and Member realized with join table ProjectMembers. On view in order to add particular project members I use multiple select which stores MemberId in IEnumerable<int> SelectedMembers.
Everything works correctly except I can only update (Edit) Project with new members (members which are not selected and were part of database before remain untouched). I need help with removing the existing members in ProjectMember which are connected to the specific ProjectId before posting the updated set of members. I have tried a lot but nothing worked so far. Really any advice would be appreciated.
This is my Edit post method in ProjectController:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int? id, CreateProjectViewModel viewmodel)
{
Project project = _context.Project
.Single(m => m.ProjectId == id);
project.Name = viewmodel.Name;
project.Budget = viewmodel.Budget;
project.BusinessCase = viewmodel.BusinessCase;
project.StartDate = viewmodel.StartDate;
project.FinishDate = viewmodel.FinishDate;
project.ClientId = viewmodel.ClientId;
// here I need the method to remove the existing instances of Member in ProjectMember
// part of code below is validation that there will be no conflict of PrimaryKeys on ProjectMember, probably can be removed once the Remove method is implemented
foreach (var selectedId in viewmodel.SelectedMembers)
{
var projectID = project.ProjectId;
var memberID = selectedId;
IList<ProjectMember> existingItems = _context.ProjectMembers
.Where(cm => cm.MemberId == memberID)
.Where(cm => cm.ProjectId == projectID).ToList();
if (existingItems.Count == 0)
{
_context.ProjectMembers.Add(new ProjectMember
{
ProjectId = project.ProjectId,
MemberId = selectedId,
});
}
}
_context.SaveChanges();
return RedirectToAction("Index");
}
UPDATE:
Based on similar threads I came up with following to be added before the new rows are being added into ProjectMembers with the POST Edit:
var project = _context.Project.Include(a => a.ProjectMembers)
.SingleOrDefault(m => m.ProjectId == id);
if (project != null)
{
foreach (var projectMember in project.ProjectMembers
.Where(at => viewmodel.SelectedMembers.Contains(at.MemberId)).ToList())
{
project.ProjectMembers.Remove(projectMember);
}
_context.SaveChanges();
}
Unfortunately the entries which should be deleted in ProjectMembers keep on staying, Can anyone advice what should be changed?
Got it. Apparently a much easier way to do it. Code below is to remove the entires before new ones can be passed:
var project = _context.Project.Include(a => a.ProjectMembers)
.SingleOrDefault(m => m.ProjectId == id);
foreach (var member in project.ProjectMembers.ToArray())
{
project.ProjectMembers.Remove(member);
}

Update child entities from parent entity

According to the scenario below, I would like to update child entities from parent entity.
I approached the problem as follows.
Child entities of parent delete from database.
New child entities of parent add from database.
Is that approach true way ?
public int SaveBasket(Addition addition)
{
var entity = ApplicationDbContext.Additions.Include(x => x.Basket).SingleOrDefault(x => x.AdditionId == addition.AdditionId);
//Remove Basket
if (entity.Basket.Count > 0)
{
foreach (var item in entity.Basket)
{
context.Entry(item).State = EntityState.Deleted;
}
ApplicationDbContext.SaveChanges();
}
//Add new basket entities from posting json data
entity.Basket = addition.Basket;
return ApplicationDbContext.SaveChanges();
}
I think you're looking for the following:
public int SaveBasket(Addition addition)
{
var entity = ApplicationDbContext.Additions.Find(addition.AdditionId); // Find by primary key
//Remove Basket
if (entity.Basket.Count > 0)
{
entity.Basket.Clear(); // Empty out the basket
ApplicationDbContext.SaveChanges();
}
//Add new basket entities from posting json data
addition.Basket.ForEach(b => entity.Basket.Add(b)); // Add the items
return ApplicationDbContext.SaveChanges();
}
Hard to tell what your data structure is, but this should help.
Update
From your comment, it sounds like you want to remove the entire basket record.
public int SaveBasket(Addition addition)
{
var baskets = ApplicationDbContext.Basket.Where(b => b.AdditionId == addition.AdditionId);
//Remove Basket
ApplicationDbContext.Basket.RemoveRange(baskets);
ApplicationDbContext.SaveChanges();
ApplicationDbContext.Basket.AddRange(addition.Basket);
return ApplicationDbContext.SaveChanges();
}
Depending on what your models look like, you might be able to just do this for deleting and adding children.
public int SaveBasket(Addition addition)
{
var dbAddition = ApplicationDbContext.Additions
.Include(x => x.Basket)
.SingleOrDefault(x => x.AdditionId == addition.AdditionId);
ApplicationDbContext.Basket.RemoveRange(dbAddition.Basket);
ApplicationDbContext.Basket.AddRange(addition.Basket);
return ApplicationDbContext.SaveChanges();
}

SQL Query to delete a comment and all its children in MySQL and Entity Framework

I have a comments table.
id
section_id
parent_comment_id
root_id
content
It supports multi-level commenting.
If the comment is at the top most position, it's root_id is NULL and it's parent_comment_id is NULL.
If the comment is underneath another comment: the parent_comment_id equals to the comment Id of the comment which is just one level underneath it. The root_id for all child comments will be the Id of the top most comment in which that comment is underneath it.
Example:
[id=1] [parent_comment_id=null] [root_id=null]
[id=2] [parent_comment_id=1] [root_id=1]
[id=3] [parent_comment_id=1] [root_id=1]
[id=4] [parent_comment_id=3] [root_id=1]
[id=5] [parent_comment_id=4] [root_id=1]
[id=12] [parent_comment_id=3] [root_id=1]
[id=6] [parent_comment_id=1] [root_id=1]
[id=2] [parent_comment_id=null] [root_id=null]
I have no control over the database Schema. So I can't make changes. There is also no foreign key constraint set up in the table for any of these columns.
What I am having problem with is to create a function, probably a recursive function, that gets a comment Id and deletes that comment and all its childs, childs in all levels. This can be a comment anywhere in the tree.
Here is something that I am trying now:
protected void DeleteChildComments(comment c)
{
// get all comments with c.id as its parent_id
List<comment> oneLevelDownSubComments = new List<comment>();
using (phEntities db = new phEntities())
{
oneLevelDownSubComments = db.comments.Where(x => x.parent_comment_id == c.id).ToList<comment>();
}
if (oneLevelDownSubComments.Count == 0)
{
// no children, just delete the comment
using (phEntities db = new (phEntities())
{
db.comments.Remove(c);
db.SaveChanges();
}
}
else
{
// has children
foreach(var item in oneLevelDownSubComments)
{
DeleteChildComments(item);
}
}
}
I am developing in ASP.NET 4.5 C# and Entity Framework against a MySQL 5 Database.
OK, I finally made it, here is the code:
protected void DeleteChildComments(comment c, phEntities db) {
if (c != null) {
// get all comments with c.id as its parent_id
List <comment> oneLevelDownSubComments = new List <comment> ();
oneLevelDownSubComments =
db.comments.Where(x => x.parent_comment_id == c.id).ToList <comment> ();
if (oneLevelDownSubComments.Count == 0) {
// no children, just delete the comment
db.comments.Remove(c);
db.SaveChanges();
} else {
// has children, delete them
foreach(var item in oneLevelDownSubComments) {
DeleteChildComments(item, db);
}
// delete itself if has no children
DeleteChildComments(c, db);
}
}
}
How about something similar to this?
protected void DeleteComment(comment c)
{
int id = c.id
//Three lists to hold the self-referencing objects
List<comment> rootCommentsToBeDeleted = new List<comment>();
List<comment> parentCommentsToBeDeleted = new List<comment>();
List<comment> commentsToBeDeleted = new List<comment>();
using (phEntities db = new phEntities())
{
//Get all comments to lists
rootCommentsToBeDeleted = db.comments.Where(x => x.root_id == id).ToList<comment>();
parentCommentsToBeDeleted = db.comments.Where(x => x.parent_comment_id == id).ToList<comment>();
commentsToBeDeleted = db.comments.Where(x => x.id == id).ToList<comment>();
//Combine lists
commentsToBeDeleted.AddRange(rootCommentsToBeDeleted);
commentsToBeDeleted.AddRange(parentCommentsToBeDeleted);
//Delete records
db.comments.RemoveRange(commentsToBeDeleted);
db.SaveChanges();
}
}

Comparing and insert/update/delete children in Entity Framework

I have a multiple selection form that needs to be saved into the database. Users can edit their selections on their subsequent visits. The items are defined in a table with unique IDs so the form only pass back a list of IDs.
It is something like:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user", fruits=apple,orange }
In DB there is a link table linking the id of people and id of fruits.
Now when I receive a request to edit, I'll need to compare with existing fruits so I know whether I need to add or delete an entry.
foreach (var fruit in existing_fruits)
{
if (post_fruits.Where(e => e.id == fruit.id).Count() == 0) user.Fruits.Remove(fruit);
}
foreach (var fruit in post_fruits)
{
if (existing_fruits.Where(e => e.id == fruit.id).Count() == 0)
{
var entity = context.Fruit.Where(e => e.id == fruit.id);
user.Fruits.Add(entity);
}
}
As you can see there are multiple loops and multiple which call on the lists, which makes me wonder if there is a cleaner way in doing this?
There are a lot of useful functions in EntityFramework.Extended. It contains Batch Update and Delete feature which can be useful in your situation, it also eliminates the need to retrieve and load an entity before modifying it, so it can increase your performance in future.
What if you use .Any method here (although it still uses two loops but it is more efficient) :
foreach (var fruit in existing_fruits)
if (!post_fruits.Any(e => e.id == fruit.id)) user.Fruits.Remove(fruit);
foreach (var fruit in post_fruits)
{
if (existing_fruits.Any(e => e.id == fruit.id)) continue;
var entity = context.FirstOrDefault(e => e.id == fruit.id);
if(entity != null) user.Fruits.Add(entity);
}
But it's better to change DB architecture to:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user" }
PeopleFruits
{ id=1, fruitId = 1, personId = 1, isSelected = 0}
All you need to update records now is to get this PeopleFruits entities of some person.
PeopleFruits[] personPplFruits = cont.PeopleFruits.Where(pf => pf.personId == 1).ToArray();
And update .isSelected properties according to what user has selected.
Check this article please : https://www.simple-talk.com/sql/database-administration/how-to-get-database-design-horribly-wrong/

How do I use LINQ-to-Entities to insert data into a specific table?

Question: what is the LINQ-to-Entity code to insert an order for a specific customer?
Update
Here is the a solution (see one of the submitted answers below for a much cleaner solution):
using (OrderDatabase ctx = new OrderDatabase())
{
// Populate the individual tables.
// Comment in this next line to create a new customer/order combination.
// Customer customer = new Customer() { FirstName = "Bobby", LastName = "Davro" };
// Comment in this line to add an order to an existing customer.
var customer = ctx.Customers.Where(c => c.FirstName == "Bobby").FirstOrDefault();
Order order = new Order() { OrderQuantity = "2", OrderDescription = "Widgets" };
// Insert the individual tables correctly into the hierarchy.
customer.Orders.Add(order);
// Add the complete object into the entity.
ctx.Customers.AddObject(customer);
// Insert into the database.
ctx.SaveChanges();
}
Your code isn't far off. Just change your second line to read as follows:
Customer customer = ctx.Customer.FirstOrDefault(c => c.FirstName == "Bobby");
if (customer != null)
{
//...
Just replace the c.FirstName == "Bobby" with something that can strongly identify the customer you're looking for (e.g. c.Id == customerID if you already know what the ID is).
Notice that Order has a Customer property. You don't have to add the Order to the Customer -- you can do it the other way around. So, instead of creating a new Customer, get the Customer using Linq, then add it to your new Order.
using (OrderDatabase ctx = new OrderDatabase())
{
ctx.AddOrder(new Order()
{
OrderQuantity = 2,
OrderDescription = "Widgets",
Customer = ctx.Customers.First<Customer>(c => c.CustomerId == yourId)
});
ctx.SaveChanges();
}
I don't get what the problem is, exactly.
var mycustomer = context.Customers.Where(x => x.id == 100).FirstOrDefault();
if(mycustomer != null)
{
mycustomer.Orders.Add(myorder);
}
context.SaveChanges();
L2E does not support set-based operations currently (update without select). See Use linq to generate direct update without select

Categories