.NET List not saved to db? - c#

I want an item to have several sales linked to it:
Sale.cs
public class Sale
{
public int Id { get; set; }
public int Amount { get; set; }
public decimal Price { get; set; }
}
Item.cs
public class Item
{
[Key]
public string Name { get; set; }
public string Market { get; set; }
public string Market_api { get; set; }
public List<Sale> Sales { get; set; }
}
I save Sales like this:
[HttpPost]
public async Task<IActionResult> Sale(SaleViewModel vm, string name)
{
Item item = _repo.GetItem(name);
item.Sales = item.Sales ?? new List<Sale>();
item.Sales.Add(new Sale
{
Amount = vm.Amount,
Price = vm.Price
});
_repo.UpdateItem(item);
await _repo.SaveChangesAsync();
return RedirectToAction("Index");
}
_repo:
public void UpdateItem(Item item)
{
_ctx.Items.Update(item);
}
public async Task<bool> SaveChangesAsync()
{
if(await _ctx.SaveChangesAsync() > 0)
{
return true;
}
return false;
}
and when I debug this it all looks good (List is in item)
Debug information
Peek into db
but when I try to access it like:
item.Sales it always returns null. I honestly don't know what's going on, I can see that the correct foreign key is saved in the Sales table but as to why I can't access it I have no clue.

Include Sales in your repo get items method
_repo.GetItem(name);
_ctx.Items.Include(i => i.Sales).FirstOrDefault(i => i.Name == name);

Related

Why am I getting new records created in table a when I edit a record of another table?

I have an Invoice object that has a list of items and other properties. Whenever I execute the service that edits an invoice, each item get doubled for some reason.
For example, here's a snapshot of the items table after creating an invoice with two items:
Items table
And here's a snapshot of it after executing EditInvoice service: Items table after editing
Data Model
Invoice
public class Invoice
{
public string InvoiceId { get; set; } = GenerateID.GenerateInvoiceID();
public string Description { get; set; }
public List<InvoiceItem> Items { get; set; }
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public int TotalPrice { get; set; }
public string Status { get; set; } = "pending";
public Client Client { get; set; }
public string ClientId { get; set; }
public string BillFromAddress { get; set; }
public string BillFromCity { get; set; }
public string BillFromCountry { get; set; }
public string BillFromPostal { get; set; }
}
InvoiceItem
public class InvoiceItem
{
public string InvoiceItemId { get; set; } = GenerateID.GenerateItemID();
public string InvoiceId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
public int Price { get; set; }
public InvoiceItem()
{
}
public InvoiceItem(string itemName, int quantity, int price)
{
Name = itemName;
Quantity = quantity;
Price = price;
}
}
My InputModel
public class InputModel
{
[Required]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public Client Client { get; set; }
public List<InvoiceItem> Items { get; set; } = new List<InvoiceItem>(16);
[Required]
public string BillFromAddress { get; set; }
[Required]
public string BillFromCity { get; set; }
[Required]
public string BillFromCountry { get; set; }
[Required]
public string BillFromPostal { get; set; }
public void PopulateItems()
{
for (int i = 0; i < Items.Capacity; i++)
{
Items.Add(new InvoiceItem());
}
}
}
My Page Models
CreateInvoiceModel
public class CreateInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public CreateInvoiceModel(InvoiceService service)
{
_service = service;
}
public void OnGet()
{
Input = new InputModel();
Input.PopulateItems();
}
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
_service.AddInvoice(Input);
return RedirectToPage("/Index");
}
return Page();
}
}
EditInvoiceModel
public class EditInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public string InvoiceId { get; set; }
public EditInvoiceModel(InvoiceService service)
{
_service = service;
}
public async void OnGet(string id)
{
Invoice invoice = await _service.GetInvoice(id);
InvoiceId = invoice.InvoiceId;
Input = new InputModel();
Input.Items = invoice.Items;
Input.BillFromAddress = invoice.BillFromAddress;
Input.BillFromCity = invoice.BillFromCity;
Input.BillFromPostal = invoice.BillFromPostal;
Input.BillFromCountry = invoice.BillFromCountry;
Input.Client = invoice.Client;
Input.InvoiceDate = invoice.InvoiceDate;
Input.PaymentTerms = invoice.PaymentTerms;
Input.Description = invoice.Description;
}
public async Task<IActionResult> OnPost(string id)
{
if(ModelState.IsValid)
{
_service.EditInvoice(Input, id);
return RedirectToPage("/ViewInvoice", new { id = id });
}
return Page();
}
}
Services
AddInvoice
public async void AddInvoice(InputModel input)
{
Invoice invoice = new Invoice();
invoice.Description = input.Description;
invoice.Items = input.Items;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
//Attaching the invoice id to each item in the invoice
foreach (var item in invoice.Items)
{
item.InvoiceId = invoice.InvoiceId;
}
//IndexModel.invoices.Add(invoice);
_context.Add(invoice);
await _context.SaveChangesAsync();
}
EditInvoice
public async void EditInvoice(InputModel input, string id)
{
var invoice = await _context.Invoices.FindAsync(id);
if (invoice == null) { throw new Exception("Unable to find the invoice"); }
invoice.Items = input.Items;
invoice.Description = input.Description;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
await _context.SaveChangesAsync();
}
Your code has multiple problems:
First of all in your InvoiceItem you have the following line
public string InvoiceItemId { get; set; } = GenerateID.GenerateItemID();
which means that whenever n InvoiceItem is instanciated a new Id is generated which is not correct because there is a difference between actually creating an InvoiceItem and just creating an instance of the class InvoiceItem. A new Id should only be generated if a new InvoiceItem is created but not for example if an existing InvoiceItem is loaded from the database (in both cases an instance of the class InvoiceItem is created but only in the first one an actual InvoiceItem shall be created). So removing the id generation from the property declaration and only performing is when an InvoiceItem shall actually be created will fix this part of the problem.
The second problem is in EditInvoice where you call
var invoice = await _context.Invoices.FindAsync(id);
// ...
invoice.Items = input.Items;
in the first line you are loading the invoice from the database but you are not including the invocies and therefore they do not get loaded and EF does not even know they exist.
So when you are calling invoice.Items = input.Items you are assigning the list with the new InvoiceItems with the newly generated ids (as explained above) and they will therefore be added to the database and hence duplicated.
So instead of replacing the whole list you should be editing existing items and only adding InvoiceItems which have actually been created to the list.
Instead of
invoice.Items = input.Items;
You can work with:
For adding a new InvoiceItem record, presume the invoice item doesn't have InvoiceId (foreign key value), use .Add() as adding a new record.
For the existing InvoiceItem record, presume the invoice item has InvoiceId (foreign key value), set the entity, and modify its state as Modified. Make sure that the entity's Id (primary key (value)) matches with the record in the database table. Reference: Attaching an existing but modified entity to the context
public async void EditInvoice(InputModel input, string id)
{
var invoice = await _context.Invoices.FindAsync(id);
if (invoice == null) { throw new Exception("Unable to find the invoice"); }
foreach (var item in input.Items)
{
if (String.IsNullOrEmpty(item.InvoiceId))
{
invoice.Add(item);
}
else
{
context.Entry(item).State = EntityState.Modified;
}
}
invoice.Description = input.Description;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
await _context.SaveChangesAsync();
}
Or you can work as:
foreach (var item in input.Items)
{
if (String.IsNullOrEmpty(item.InvoiceId))
{
item.InvoiceId = invoice .InvoiceId;
context.Entry(item).State = EntityState.Added;
}
else
{
context.Entry(item).State = EntityState.Modified;
}
}
Reference: Insert or update pattern
Although the provided documentation is regarding Entity Framework 6, it supports in Entity Framework Core.

How to display data from two separate tables in MVC?

So, i am new to programming and i am trying to make my first .net project, and i'm stuck.
I have a database that contains table Product, it looks like this:
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
[Range(1, int.MaxValue)]
public double ProductPrice { get; set; }
public string ProductDescription { get; set; }
public string ProductImagePath { get; set; }
public int ProductColorId { get; set; }
[ForeignKey("ProductColorId")]
public virtual ProductColor ProductColor { get; set; }
public int ProductShippingOptionId { get; set; }
[ForeignKey("ProductShippingOptionId")]
public virtual ProductShippingOption ProductShippingOption { get; set; }
public int ProductConditionId { get; set; }
[ForeignKey("ProductConditionId")]
public virtual ProductCondition ProductCondition { get; set; }
Columns ProductShippingOption, ProductColor and ProductCondition are a separate tables that each contain columns for Id and Name.
When i add a product to database, i want to show details of just one product in a view, but i need to display ProductConditionName instead of ProductConditionId (for example).
What should i include in my ViewModel and my Controller so i can use it in my View?
My action in a ProductController looks like this
public IActionResult ProductTemplate(int? id)
{
ProductVM productVM = new ProductVM()
{
Product = _db.Product.Find(id),
};
if (id == 0)
{
return NotFound();
}
if (id == 0)
{
return NotFound();
}
if(productVM==null)
{
return NotFound();
}
return View(productVM);
}
Thanks!
Best and easiest way to do it is including the classes in Product:
For your ProductTemplate action :
ProductVM productVM = new ProductVM()
{
Product = _db.Product.Where(s=>s.Id == id)
.Include(s=>s.ProductColor)
.Include(s=>s.ProductShippingOption)
.Include(s=>s.ProductCondition)
.FirstOrDefault();
};
And you can call them in your .cshtml with (Let say you want ProductColor name) :
#Model.Product.ProductColor.Name
Alternatively you can add Include() to your context to take all includes defaultly.

How to update existing child class by using the parent id

Currently I have somes idea where we get the child data from its parent Id, and update the child data with hardcoded text. Parent Class:
`
public class Ride
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime RideStartTime { get; set; }
public string DestinationLongitude { get; set; }
public string DestinationLatitude { get; set; }
public int SeatAvailable { get; set; }
public Double TotalCost { get; set; } = 0;
public Double TotalDistance { get; set; } = 0;
//Ride has Many Request
public ICollection<Request> Requests { get; set; }
}
`
Child Class
public class Request : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string PickupLongitude { get; set; }
[Required]
public string PickupLatitude { get; set; }
public Double? EstimatedCost { get; set; } = 0;
public Double? Rating { get; set; } = 0;
public int RideId { get; set; }
public Ride Ride { get; set; }
}
The situation is when the when i need to update all child status column to "Confirm", i need to find it parent class first by search the rideId and if the ride found, it will update their child class attribute. Im using EF core to save the data.
// PUT api/<controller>/5
[HttpPut("{id}/confirm")]
public IActionResult ConfirmRide(int id, [FromBody]Ride ride)
{
try
{
if (ride.IsObjectNull())
{
_logger.LogError("Ride object sent from client is null.");
return BadRequest("Ride object is null");
}
if (!ModelState.IsValid)
{
_logger.LogError("Invalid ride object sent from client.");
return BadRequest("Invalid model object");
}
var dbRide = _repository.Ride.GetRideById(id);
if (dbRide == null)
{
_logger.LogError($"Ride with id: {id}, hasn't been found in db.");
return NotFound();
}
_repository.Ride.ConfirmRide(dbRide, ride, id, "Confirmed");
//_repository.Ride.
_repository.Save();
return NoContent();
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside UpdateRide action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}
Currently this is my logic to save or update the data, can you guys help me how to update the child class base on parent Id.
How to add/update child entities when updating a parent entity in EF
I got this solution and modify it with other resource.
public void ConfirmRide(Ride dbRide, int id, string status)
{
dbRide.MapStatus(status);
Update(dbRide);
var existingParent = RepositoryContext.Rides
.Where(p => p.Id == id)
.Include(p => p.Requests).Where(r => r.Requests.Any( request => request.Status == "Approved"))
.SingleOrDefault();
if (existingParent != null)
{
foreach (var existingChild in existingParent.Requests.ToList())
{
existingChild.Status = "Confirmed";
}
}
RepositoryContext.SaveChanges();
}

How to save tags in database c#

In frontend in Angular I created the possibility of adding tags and memes. In the back-end in the web api I would like to save the tags in the database. Using entity framework code first, I created the structure of three tables:
public class Memes
{
public int Id { get; set; }
public string MemName { get; set; }
public string UserId { get; set; }
public string Image { get; set; }
public List<Memes_tags> MemesIteam { get; set; }
}
public class HashTag
{
public int Id { get; set; }
public int MemesId { get; set; }
public string Name { get; set; }
}
public class Memes_tags
{
public int Id { get; set; }
public int MemesId { get; set; }
public int HashTagId { get; set; }
public virtual Memes Memes { get; set; }
public virtual HashTag HashTags { get; set; }
}
Then I created a method that should save memes and tags in the database:
[HttpPost]
[Route("api/Memes/AddMemes")]
public IHttpActionResult CreateMemes([FromBody] MemHashTagsViewModel createMem)
{
ApplicationDbContext db = new ApplicationDbContext();
if (createMem != null)
{
Memes mem = new Memes()
{
MemName = createMem.MemName,
Image = createMem.Image,
UserId = createMem.UserId
};
db.MemesModel.Add(mem);
foreach (var item in createMem.HashTags)
{
var hashTag = new HashTag()
{
MemesId = mem.Id,
Name = item
};
db.HashTags.Add(hashTag);
}
db.SaveChanges();
return Ok();
}
else
{
return NotFound();
}
}
Incoming data:
I have problem with the correct Memes Id record. For example, I created a mem that has Id = 4 and in the table HashTags should be 4 and in my case is 0.
Is there any other better solution for saving tags in the database? Is my solution is good?
Yeah, the thing is: since you didn't save the mem first, it doesn't have an ID when you add it to the hashtag.
If you want to do it that way, you should make HashTag a member, in form of a list (property) on the mem. Then, when creating the HashTag objects, not add a member ID. The merely add the Mem to the database, and EF will take care of the object structure.
(On my phone, will make a code example in the morning if no one beats me to it)
EDIT: Here's how i would do it:
Respectfully: Drop the Memes_tags class as their seems to be no point in having it at all. It merely works as a relation between Memes and HashTags, but that already exists.
For purposes of Best practice, at least according to MS's own EF 'get start' doc the id of the class should be named: <class_name>Id, so that has been 'corrected' as well.
public class Memes
{
public int MemesId { get; set; }
public string MemName { get; set; }
public string UserId { get; set; }
public string Image { get; set; }
public List<HashTag> HashTags { get; set; }
}
public class HashTag
{
public int HashTagId { get; set; }
public int MemesId { get; set; }
public string Name { get; set; }
public virtual Memes { get; set; }
}
Below is the modified 'CreateMemes'. The idea is, that instead of adding the ID of the 'Memes' to hashtag, we merely add the HashTags to the meme object, thus they are add to EF as well, and when the 'Memes' record is add to the database, EF will make certain to create the hashtags too.
[HttpPost]
[Route("api/Memes/AddMemes")]
public IHttpActionResult CreateMemes([FromBody] MemHashTagsViewModel createMem)
{
ApplicationDbContext db = new ApplicationDbContext();
if (createMem != null)
{
Memes mem = new Memes()
{
MemName = createMem.MemName,
Image = createMem.Image,
UserId = createMem.UserId
};
foreach (var item in createMem.HashTags)
{
var hashTag = new HashTag()
{
Name = item
};
mem.HashTags.add(hashTag);
}
db.add(mem);
db.SaveChanges();
return Ok();
}
else
{
return NotFound();
}
}
Just adding the created instance to the context entity model isn't enough db.MemesModel.Add(mem);. Id value doesn't gets generated unless you call SaveChanges() on it. This in your below code there is no Id value yet and so what you observe
var hashTag = new HashTag()
{
MemesId = mem.Id,
Name = item
};

Best way to update an object containing a list of objects in Entity Framework

I have the following models in my API:
namespace API.Models
{
public class StudentDetailsViewModel
{
[Key]
public int StudentId { get; set; }
public AddressViewModel Address { get; set; }
public List<CoursesViewModel> Courses { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public int StudentId { get; set; }
public string Address { set; set; }
}
public CoursesViewModel
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Grade { get; set; }
}
}
I am writing a PUT method for StudentDetailsViewModel. The list in this model could have a number of records removed or added or a number of fields in one of the records updated. For example, grade for one of the courses updated or a course added or dropped.
What is the best approach in updating a model containing an object list like the above? Is it best to delete the entire list and re-add them?
I have the following thus far:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutStudenDetailsViewModel(StudentDetailsViewModel studentDetailsViewModel)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var address = new DataAccess.Address
{
AddressID = studentDetailsViewModel.Address.AddessId,
StudentID = studentDetailsViewModel.Address.StudentId,
Address = studentDetailsViewModel.Address.Address
};
_context.Entry(address).State = EntityState.Modified;
// TODO: This is where the list Course entity needs to be updated
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!AddressViewModelExists(address.AddressID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
Just an example from MS documentation for EF Core
public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = context.Blogs
.Include(b => b.Posts)
.FirstOrDefault(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog); //or 404 response, or custom exception, etc...
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
context.SaveChanges();
}

Categories