Update child entities from parent entity - c#

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

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);
}

Can I take advantage of List<T> functions Remove(T) and Insert(Int32, T) when using UpdateRange(IEnumerable<T>)?

I am attempting to implement a controller method to reorder image indexes that need to be saved in the database using EF Core.
I have the following controller method:
[HttpPost]
public async Task<JsonResult> ReorderImage(int p_iImageID, int p_iNewOrderIndex)
{
if (p_iImageID <= 0)
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
ItemImage l_oItemImage = await _Context.ItemImages.FirstOrDefaultAsync(l_oImage => l_oImage.ID == p_iImageID);
if (l_oItemImage.IsNull())
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
List<ItemImage> l_oItemImages = await _Context.ItemImages.Where(l_oImage => l_oImage.ItemID == l_oItemImage.ItemID)
.OrderBy(l_oImage => l_oImage.Order)
.ToListAsync();
l_oItemImages.Remove(l_oItemImage);
l_oItemImages.Insert(p_iNewOrderIndex, l_oItemImage);
foreach(ItemImage l_oImage in l_oItemImages)
{
l_oImage.Order = l_oItemImages.IndexOf(l_oImage);
if (l_oItemImages.IndexOf(l_oImage) == 0)
l_oImage.IsPrimary = true;
else
l_oImage.IsPrimary = false;
l_oImage.Uri = _AzureBlobStorage.GetBlobUri(_ItemImageAzureBlobContainerName, l_oImage.GetFileName());
}
_Context.ItemImages.UpdateRange(l_oItemImages);
await _Context.SaveChangesAsync();
return Json(l_oItemImages)
}
The order and data of l_oItemImages when calling UpdateRange() and subsequently SaveChangesAsync() appears correct to me.
I've been looking at this question which mentions not creating new classes and using UpdateRange(). This seems a bit different but I can see how this might be my issue.
Am I having this issue because I'm manipulating the objects of the list using Remove(l_oItemImage) and then Insert(p_iNewOrderIndex, l_oItemImage)? Or is it because I'm using ToListAsync() to begin with when I grab the item images?
EDIT: Tried Update(l_oItemImage) in place of UpdateRange(l_oItemImages) with same results. Added image of QuickWatch showing tacked entities both are correctly showing State = Modified as well as the expected changed values for int Order and bool IsPrimary properties.
EDIT 2: Added image of QuickWatch data with highlighted changed properties on entities.
Yes, you should be able to take advantage of the List methods however I think UpdateRange is unnecessary for this common task, here is an alternative implementation.
You may want to consider something like the following instead where the Sequence is reassigned for a subset of sequenced entities:
public async Task SetSequenceAsync(int forPageComponentId, int newSequence)
{
var infoAboutItemWereChangingSequenceFor = await context.PageComponents
.Where(x => x.Id == forPageComponentId)
.Select(x => new {
OriginalSequence = x.Sequence, // I need to know it's current sequence.
x.PageId // I need to only adjust sequences for items that have the same PageId, so I need to know what the pageId is for the item we're targeting.
}).FirstOrDefaultAsync();
// Get just the data we want to modify, we're going to include the item we're targeting so this list is inclusive of it.
// Including the item we're changing to make logic below a little mor consise instead of managing the list and the item we're targeting
// seperately.
var allItemsWithSequenceThatWillChange = await context.PageComponents
.Where(x =>
x.PageId == infoAboutItemWereChangingSequenceFor.PageId // Only those items sharing the same page Id.
// Only those items we need to change the sequence for.
&& x.Sequence >= Math.Min(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
&& x.Sequence <= Math.Max(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
)
.Select(x =>
new PageComponent() // The type of object EF knows about.
{
// The Primary key, so Entity Framework knows what record to change the sequence on.
Id = x.Id,
// The sequence value we need to change.
Sequence = x.Sequence
}
).ToListAsync();
// Set the sequence of the item we're targeting.
allItemsWithSequenceThatWillChange
.Where(x => x.Id == forPageComponentId)
.First()
.Sequence = newSequence;
// Now update the sequence on the other items (excluding the target item)
foreach (var item in allItemsWithSequenceThatWillChange.Where(x => x.Id != forPageComponentId))
{
// Either increment or decrement the sequence depending on how the original item was moved.
item.Sequence += infoAboutItemWereChangingSequenceFor.OriginalSequence > newSequence ? 1 : -1;
// Add any other property changes here.
}
// Save changes.
await context.SaveChangesAsync();
}
Also, as a matter of simplification on your ItemImage object, I notice you have an apparently DB persisted property "IsPrimary" - you may want to change this to be calculated on the entity and even at the db level instead, eg:
public class ItemImage {
// ... Other Properties ...
public int Order { get; set; }
public bool IsPrimary {
get => Order == 0;
set {}
}
}
For a calculated column in your MSSQL Database you can query against, add to your DbContext OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity(typeof(ImageObject)).Property("IsPrimary").HasComputedColumnSql("CASE WHEN [Order] = 0 THEN 1 ELSE 0 END");
}

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

Entity Framework: Removing child entity

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

Entity framework savechanges() not working EF5, .Net4

I have the following code to update some inventory values...
private static void UpdateInventory(int prodId, int qty)
{
using (var context = new uStore7_1Entities())
{
//Get the catalogNo and ProductUnitID of the product passed in so we can find all identical products that might just be boxed differently
var currProdItem = context.Products.Where(c => c.ProductID.Equals(prodId))
.Select(c => new {c.CatalogNo, c.ProductUnitID}).FirstOrDefault();
//Get the units per box factor for calculating total product ordered
var prodIdAmount =
context.ProductUnits.Where(pa => pa.ProductUnitID.Equals(currProdItem.ProductUnitID))
.Select(pa => pa.Amount)
.FirstOrDefault();
//Calculate the total number of units for this item
var prodUnits = qty*prodIdAmount;
//Get the entire list of products with the specified catalog number excluding the product passed in
var uStoreProducts =
context.Products.Where(p => p.CatalogNo.Equals(currProdItem.CatalogNo) && !p.ProductID.Equals(prodId))
.Select(p => p.ProductID);
//Loop through each product in the uStoreProductsList
foreach (var uStoreProduct in uStoreProducts)
{
var currentProduct = uStoreProduct;
//Get the current product's ProductUnitId to get the 'pieces' per "box"
var currentUnitId =
context.Products.Where(u => u.ProductID.Equals(currentProduct))
.Select(u => u.ProductUnitID)
.FirstOrDefault();
//Use the ProductUnitId to get the "Amount" from the ProductUnits table.
var inventoryFactor =
context.ProductUnits.Where(i => i.ProductUnitID.Equals(currentUnitId))
.Select(i => i.Amount)
.FirstOrDefault();
//Divide the quantity passed
var qtyInUnits = prodUnits/inventoryFactor;
var inventory =
context.ProductInventories.Where(pi => pi.ProductID.Equals(currentProduct))
.Select(pi => pi.InventoryQuantity)
.FirstOrDefault();
/*var inventory = (from i in context.ProductInventories
where i.ProductID == currentProduct
select i).FirstOrDefault();
*/
if (inventory != null)
{
var newinv = inventory - qtyInUnits;
inventory = newinv;
//context.SaveChanges();
}
}
context.SaveChanges();
}
}
The SaveChanges() does not seem to be updating anything. I have debugged it and the inventory value gets changed to the value needed, but for some reason it isn't updating. I've tried it inside the loop and outside the loop, both with no change. Any ideas? What am I missing here?
Your problem is object replacement, rather than re-assignment. When "newinv" is created, it's detached from your context, whereas "inventory" is the attached object. When "newinv" is assigned to "inventory", then it loses the association with the context.
Try this:
var prodInventory =
context.ProductInventories
.Where(pi => pi.ProductID.Equals(currentProduct))
.FirstOrDefault();
if (prodInventory != null)
{
var newinv = prodInventory.InventoryQuantity - qtyInUnits;
prodInventory.InventoryQuantity = newinv; // This updates the actual context object now.
}
Your code does not update anything. All you do is gather entities and assign calculated values in local variables. But you never actually change a property of one of your entities.
Note that selecting properties of your entities, storing them in variables and replacing the value of this variable does not work. You need to select the entity if you want to modify it.

Categories