Tjatte
Tjatte

Reputation: 241

.NET Core Web API Updating a model in the database context wont work

I am coding a little Service called Cart.API that i supposed to handle all Add/Update/Delete Requests on my database (I am using docker, therefore i am hosting a MSSQL Database on a docker container). Right now, i am stuck at the method DeleteCatalogItemAsync.

This is the code of the CartController.cs:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Cart.API.Models;
using Cart.API.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Cart.API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CartController : ControllerBase
    {
        private readonly CartContext _context;
        private readonly ICartService _cartService;
        private readonly ICatalogService _catalogService;

        public CartController(CartContext context, ICartService cartService, ICatalogService catalogService)
        {
            _context = context;
            _cartService = cartService;
            _catalogService = catalogService;
        }

        [HttpGet]
        [Route("cart/{id}")]
        public bool CartExists(int id)
        {
            return _context.UserCarts.Any(cart => cart.Id == id);
        }

        [HttpGet]
        [Route("entries")]
        public async Task<ActionResult<List<CartModel>>> GetAllCartAsync()
        {
            var cartList = await _context.UserCarts.Include(cart => cart.Items).ToListAsync(); //include needed to load Items List
            if (cartList == null)
            {
                return NoContent();
            }

            return cartList;
        }

        [HttpGet]
        [Route("entries/{id}")]
        public async Task<ActionResult<CartModel>> GetCartByIdAsync(int id)
        {
            var cart = await _context.UserCarts.Where(b => b.Id == id).Include(m => m.Items).SingleOrDefaultAsync();

            return cart ?? new CartModel(id);
        }

        [HttpPut]
        [Route("entries")]
        public async Task<ActionResult> AddCartItemAsync([FromBody] AddCartItemRequest requestItem)
        {
            if (requestItem == null || requestItem.Quantity == 0)
            {
                return BadRequest("Invalid payload");
            }

            //Check, if cart already exists or a new one has to be added
            var cartExists = await _cartService.CheckCartExistance(requestItem.CartId);

            //Get or create cart object by the requested Id
            var currentCart = await _cartService.GetCartByIdAsync(requestItem.CartId);

            //Get Catalog Item
            var catalogItem = await _catalogService.GetCatalogItemByIdAsync(requestItem.CatalogItemId);

            currentCart.Items.Add(new CartItem(catalogItem.Id, catalogItem.Name, catalogItem.Price, requestItem.Quantity));

            if (!cartExists)
            {
                _context.UserCarts.Add(currentCart);
            }
            else
            {
                _context.UserCarts.Update(currentCart);
            }

            await _context.SaveChangesAsync();

            return Ok(currentCart);
        }

        [HttpDelete]
        [Route("entries/remove")]
        public async Task<ActionResult> DeleteCatalogItemAsync([FromBody] DeleteCartItemRequest requestItem)
        {
            if (requestItem == null)
            {
                return BadRequest("Cannot perform action");
            }

            //Check if Basket exists
            var basketExists = await _cartService.CheckCartExistance(requestItem.CartId);

            if (basketExists)
            {
                //var currentCart = await _context.UserCarts.Include(model => model.Items).SingleOrDefaultAsync(model => model.Id == requestItem.CartId);

                var currentCart = await _cartService.GetCartByIdAsync(requestItem.CartId);
                if(currentCart != null)
                {
                    var itemToDelete = currentCart.Items.Find(model => model.ProductId == requestItem.CatalogItemId);
                    currentCart.Items.Remove(itemToDelete);
                    //currentCart.RemoveCartItem(requestItem.CatalogItemId);
                    _context.UserCarts.Update(currentCart);
                    await _context.SaveChangesAsync();
                    return Ok();
                }

                return BadRequest("Das Item mit der ID " + requestItem.CatalogItemId + " konnte nicht gelöscht werden.");
            }
            return NotFound("Es existiert kein Warenkorb mit der ID " + requestItem.CartId);
        }

        [HttpGet]
        [Route("test")]
        public async Task<ActionResult> TestCall()
        {
            var test1 = await _catalogService.GetCatalogItemByIdAsync(1);
            return Ok(test1);
        }
    }
}

CartService.cs:

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Cart.API.Models;
using Newtonsoft.Json;

namespace Cart.API.Services
{
    public class CartService : ICartService
    {
        private readonly HttpClient _client;

        public CartService(HttpClient client)
        {
            _client = client;
        }

        public async Task<IEnumerable<CartModel>> GetAllCartsAsync()
        {
            var stringContent = await _client.GetStringAsync("http://cart.api/api/cart/entries");
            return JsonConvert.DeserializeObject<List<CartModel>>(stringContent);
        }

        public async Task<CartModel> GetCartByIdAsync(int id)
        {
            var stringContent = await _client.GetStringAsync("http://cart.api/api/cart/entries/" + id);
            var cart = !string.IsNullOrEmpty(stringContent) ? JsonConvert.DeserializeObject<CartModel>(stringContent) : null;
            return cart;
        }

        public async Task<bool> CheckCartExistance(int cartId)
        {
            var stringContent = await _client.GetStringAsync("http://cart.api/api/cart/cart/" + cartId);
            return JsonConvert.DeserializeObject<bool>(stringContent);
        }
    }
}

CartModel.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace Cart.API.Models
{
    public class CartModel
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }
        public List<CartItem> Items { get; set; }

        public CartModel(int id)
        {
            Id = id;
            Items = new List<CartItem>();
        }

        public bool RemoveCartItem(int id)
        {
            var itemToDelete = Items.Find(m => m.ProductId == id);
            return Items.Remove(itemToDelete);
        }

    }
}

CartItem.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Cart.API.Models
{
    public class CartItem
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long Id { get; set; }
        public long ProductId { get; set; }
        public string ProductName { get; set; }
        public double ProductPrice { get; set; }
        public int ProductQuantity { get; set; }
        public CartItem(long productId, string productName, double productPrice, int productQuantity)
        {
            ProductId = productId;
            ProductName = productName;
            ProductPrice = productPrice;
            ProductQuantity = productQuantity;
        }
    }
}

So, in the method DeleteCatalogItemAsync of the CartController.cs, i am trying to provide the functionality to be able to delete an existing CatalogItem. Therefore, i am calling the cartService to retrieve the basket where the item should be removed from, before i actually remove the CatalogItem out of the cart's list. My problem is, that with the provided code above, the CatalogItem isnt being deleted out of the database, because somehow the changes to the currentCart object arent saved to the database. As far as i know, the currentCart object is untracked, so before saving the object to the database, i am calling the .Update method to start tracking the item and mark all properties as modified.

var itemToDelete = currentCart.Items.Find(model => model.ProductId == requestItem.CatalogItemId);
currentCart.Items.Remove(itemToDelete);
_context.UserCarts.Update(currentCart);
await _context.SaveChangesAsync();

I played around with the order of the code from above, and somehow saving the changed object to the dababase works with this order of the code:

var itemToDelete = currentCart.Items.Find(model => model.ProductId == requestItem.CatalogItemId);
_context.UserCarts.Update(currentCart);
currentCart.Items.Remove(itemToDelete);
await _context.SaveChangesAsync();

Why is this solution working but not the first one?

Upvotes: 0

Views: 84

Answers (1)

Mohsin Mehmood
Mohsin Mehmood

Reputation: 4246

This line of code below will attach the currentCart instance of entity to DBContext, start tracking of the entity, and set entity state to Modified

_context.UserCarts.Update(currentCart);

Next line of codet currentCart.Items.Remove(itemToDelete) will cause itemToDelete entity state to Deleted.

If you call currentCart.Items.Remove(itemToDelete); before calling context.UserCarts.Update(currentCart); since currentCart object is not tracked by DbContext so itemToDelete entity will not be marked as Deleted.

As a result, there will not be any way for EntityFramework to know that this entity needs to be deleted on calling the Save method

Hope it helps

Upvotes: 1

Related Questions