I want to update object and its related objects that are connected with FK. For example,
I had class called "Item"
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace RandApp.Models
{
public class Item : BaseEntity
{
[Required]
[Display(Name = "Item Photo")]
public string ItemPhoto { get; set; }
[Required]
[Display(Name = "Item Type")]
public string ItemType { get; set; }
[Required]
[Display(Name = "Item Category")]
public string ItemCategory { get; set; }
[Required]
[Display(Name = "Item Name")]
public string Name { get; set; }
[Required]
[Display(Name = "Item Colors")]
public List<ItemColors> Color { get; set; } = new List<ItemColors>();
[Required]
[Display(Name = "Item Size")]
public List<ItemSizes> Size { get; set; } = new List<ItemSizes>();
[Required]
[Display(Name = "Item Material Type")]
public string MaterialType { get; set; }
[Required]
[Display(Name = "Designed For")]
public string DesignedFor { get; set; }
[Required]
[Display(Name = "Item Price")]
public double Price { get; set; }
[Required]
[Display(Name = "Item Description")]
public string Description { get; set; }
}
}
As you can see i have lists type of "ItemColors" and "ItemSizes", these are their classes ItemColors
using System.ComponentModel.DataAnnotations.Schema;
namespace RandApp.Models
{
public class ItemColors : BaseEntity
{
public string ItemColor { get; set; }
public int? ItemId { get; set; }
[ForeignKey("ItemId")]
public Item Item { get; set; }
}
}
ItemSizes
using System.ComponentModel.DataAnnotations.Schema;
namespace RandApp.Models
{
public class ItemSizes : BaseEntity
{
public string ItemSize { get; set; }
public int? ItemId { get; set; }
[ForeignKey("ItemId")]
public Item Item { get; set; }
}
}
So my problem is next: lets say i have created some item(sweatshirt for example) and it has colors: black, gray, brown and it has sizes: S,M. So when i want to update this item and add one more color like red or blue, it doesn't get updated, it only adds this colors in already existed list and re-uploads data. (if i had black, gray, brown and i added red, now i will have: "black,gray,brown,black,gray,brown,red).
There is "Item" update view:
@model RandApp.DTOs.ItemDto
<h4>Update Item</h4>
<hr />
<div >
<div >
<form asp-action="UpdateItem">
<input asp-for="Id" hidden />
<div >
<label asp-for="@Model.ItemPhoto" ></label>
<img src="~/assets/@Model.ItemPhoto" style="width:230px; height:300px; object-fit:cover" />
<input asp-for="@Model.ItemPhoto" hidden />
<span asp-validation-for="@Model.ItemPhoto" ></span>
</div>
<div >
<label asp-for="@Model.Name" ></label>
<input asp-for="@Model.Name" />
<span asp-validation-for="@Model.Name" ></span>
</div>
<div >
<label asp-for="@Model.DesignedFor" ></label>
<select asp-for="@Model.DesignedFor" >
@foreach (var desigendFor in Enum.GetValues(typeof(RandApp.Enums.DesignedFor)))
{
<option val="@desigendFor" value="@desigendFor.ToString()">@desigendFor</option>
}
</select>
<span asp-validation-for="@Model.DesignedFor" ></span>
</div>
<div >
<label asp-for="@Model.ItemCategory" ></label>
<select asp-for="@Model.ItemCategory" >
<option>@Model.ItemCategory</option>
</select>
<span asp-validation-for="@Model.ItemCategory" ></span>
</div>
<div >
<label asp-for="@Model.ItemType" ></label>
<select asp-for="@Model.ItemType" >
<option>@Model.ItemType</option>
</select>
<span asp-validation-for="@Model.ItemType" ></span>
</div>
<div >
<label asp-for="@Model.MaterialType" ></label>
<select asp-for="@Model.MaterialType" >
<option value="" selected>Select Material Type</option>
@foreach (var materialType in Enum.GetValues(typeof(RandApp.Enums.MaterialType)))
{
<option value="@materialType.ToString()">@materialType</option>
}
<option value="Other">Other</option>
</select>
<input id="otherMaterialInp" asp-for="@Model.MaterialType" style="display:none; margin-top:8px" placeholder="Enter Material Type" />
<span asp-validation-for="@Model.MaterialType" ></span>
</div>
<div >
<label asp-for="@Model.Color" ></label>
<select asp-for="@Model.Color" >
@foreach (var color in Enum.GetValues(typeof(RandApp.Enums.ItemColor)))
{
<option value="@color">@color</option>
}
<option value="Other">Other</option>
</select>
<!-- This hidden select is used to get only selected colors of current item -->
<select hidden multiple id="SelectedColors">
@foreach (var color in Model.Color)
{
<option value="@color.ItemColor">@color.ItemColor</option>
}
</select>
<span asp-validation-for="@Model.Color" ></span>
</div>
<div >
<label asp-for="@Model.Size" ></label>
<select asp-for="@Model.Size" >
</select>
<!-- This hidden select is used to get only selected sizes of current item -->
<select hidden multiple id="SelectedSizes">
@foreach (var size in Model.Size)
{
<option value="@size.ItemSize">@size.ItemSize</option>
}
</select>
<span asp-validation-for="@Model.Size" ></span>
</div>
<div >
<label asp-for="@Model.Price" ></label>
<input asp-for="@Model.Price" />
<span asp-validation-for="@Model.Price" ></span>
</div>
<div >
<label asp-for="@Model.Description" ></label>
<textarea asp-for="@Model.Description" ></textarea>
<span asp-validation-for="@Model.Description" ></span>
</div>
<div >
<input type="submit" value="Update" />
<a asp-controller="Admin" asp-action="Index" >Go Back</a>
</div>
</form>
</div>
</div>
@section Scripts
{
<script>
var selectedItemTypeVal = $("#ItemType").val();
var selectedDesignedVal = $("#DesignedFor").val();
var selectedCategoryVal = $("#ItemCategory").val();
var itemColors = new Array();
var itemSizes = new Array();
$(document).ready(function () {
if ($("#DesignedFor").val() != "") {
$.ajax({
url: '@Url.Action("LoadItemCategories","Admin")',
type: 'GET',
dataType: 'json',
data: { designedFor: selectedDesignedVal },
success: function (res) {
$('#ItemCategory').html('');
$.each(res, function (val, text) {
$('#ItemCategory').append(
$('<option></option>').val(text).html(text)
);
});
},
error: function (err) {
console.log(err.responseText);
}
});
}
if ($("#ItemCategory").val() != "") {
$.ajax({
url: '@Url.Action("LoadItemTypes", "Admin")',
type: 'GET',
dataType: 'json',
data: { designedFor: selectedDesignedVal, category: selectedCategoryVal },
success: function (res) {
$('#ItemType').html('');
$.each(res, function (val, text) {
$('#ItemType').append(
$('<option></option>').val(text).html(text)
);
});
},
error: function (err) {
console.log(err.responseText);
}
});
}
if ($("#ItemType").val() != "") {
if (selectedCategoryVal.toLowerCase() == "clothing" || selectedCategoryVal.toLowerCase() == "shoes") {
$.ajax({
url: '@Url.Action("LoadItemSize", "Admin")',
type: 'GET',
dataType: 'json',
data: { designedFor: selectedDesignedVal, category: selectedCategoryVal },
success: function (res) {
$.each(res, function (val, text) {
$('#Size').append(
$('<option></option>').val(text).html(text)
);
itemSizes.push(text);
});
$("#SelectedSizes option").each(function () {
for (var i = 0; i < itemSizes.length; i ) {
if ($(this).val() == itemSizes[i]) {
$("[value=" itemSizes[i] "]").attr("selected", "selected");
}
}
});
},
error: function (err) {
console.log(err.responseText);
}
});
} else {
$.ajax({
url: '@Url.Action("LoadItemSizes", "Admin")',
type: 'GET',
dataType: 'json',
data: { designedFor: selectedDesignedVal, category: selectedCategoryVal, itemType: selectedItemTypeVal },
success: function (res) {
$.each(res, function (val, text) {
// This "1" means "SingleSize" enum value
if (text == 1) {
$('#Size').append(
$('<option></option>').val(text).html("Single Size")
);
} else {
$('#Size').append(
$('<option></option>').val(text).html(text " CM")
);
}
});
},
error: function (err) {
console.log(err.responseText);
}
});
}
}
if ($("#Color option").val() != "") {
$("#Color option").each(function () {
itemColors.push($(this).val());
})
$("#SelectedColors option").each(function () {
for (var i = 0; i < itemColors.length; i ) {
if ($(this).val() == itemColors[i]) {
$("[value=" itemColors[i] "]").attr("selected", "selected");
}
}
});
}
});
function SetValue(input) {
var fileName = input.files[0].name;
//asp-for will generate the id and name
//so you can get the selector by using $("#ItemPhoto")
$("#ItemPhoto").val(fileName);
};
// displays "other option" input in order to enter custom material type
// if desired is not given in the list
$('#MaterialType').on('change', function () {
var selectedMaterialTypeVal = $(this).val();
if (selectedMaterialTypeVal.toLowerCase() == "other") {
$('#MaterialType').attr('disabled', 'disabled');
$('#otherMaterialInp').show();
} else {
$('#MaterialType').removeAttr('disabled');
$('#otherMaterialInp').hide();
}
});
</script>
}
And There is "Item" Controller
// GET Update
public async Task<IActionResult> UpdateItem(int id)
{
if (id == 0)
{
return NotFound();
}
var item = await _itemRepo.Get().Include(o => o.Color).Include(o => o.Size).FirstOrDefaultAsync(o => o.Id == id);
if (item == null)
{
return NotFound();
}
var result = _mapper.Map<ItemDto>(item);
return View(result);
}
// POST Update
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateItem(Item item, List<string> Color, List<string> Size)
{
if (!ModelState.IsValid)
{
return View();
}
foreach (var color in Color)
{
item.Color.Add(new ItemColors() { ItemId = item.Id, ItemColor = color });
}
foreach (var size in Size)
{
item.Size.Add(new ItemSizes() { ItemId = item.Id, ItemSize = size });
}
await _itemRepo.UpdateAsync(item);
return RedirectToAction("Index");
}
Can it be the problem of FK? or is there any solution like Cascade Delete but for Update or what can i do?
CodePudding user response:
The behavior of your code works correct and as you described.
The problem is in implementation of your Update logic.
- Before you add a new Color or Size - you should check if it already exists in the item. Add only if doesn't exists.
- It is possible you remove the Color. In this case, you should find a color which exists in your item but doesn't exists in your parameter list of colors.
The simplest solution, but not best - is to delete all colors from your item and then add everything you have in your parameter.
My recommendation is to find what should be deleted and what should be added. Then update.