How to add two objects to database using one post request? - c#

I am still new at ASP.NET MVC. I'm trying to add two objects to two different tables via one JSON POST request.
Here's more explanation:
I have this CAR entity:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CarId { get; set; }
[Required]
public double Price { get; set; }
[Required]
public DateTime Expiry { get; set; }
[Required]
public ICollection<Owner> Owners{ get; set; } = new List<Owner>();
}
I made a DTO for my CAR class which will help me create Cars normally. This is it, called CarForCreationDTO:
[Required(ErrorMessage = "Price is a required field.")]
public double Price{ get; set; }
[Required(ErrorMessage ="Expiry is a required field.")]
public DateTime Expiry { get; set; }
[Required(ErrorMessage ="Owners is a required field.")]
public ICollection<OwnerDTO> Owners { get; set; } = new List<OwnerDTO>();
Here is the OwnerDTO:
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int CarId { get; set; }
Now, when I post a request to add a Car, I need to provide a price, an expiry date and a list of owners.
So, normally, my JSON request should like this:
{
"Price": "2020-03-28T00:00:00",
"Expiry": "2020-05-28T00:00:00",
"Owners": [
{
"Name":"Some Name",
"DateOfBirth":"2001-03-28T00:00:00"
}
]
}
In my CarController.cs, I did this to be able to add to the database both the Owner object and then the Car object :
[HttpPost]
public IActionResult CreateCar([FromBody] CarForCreationDTO car)
{
//Some validation ...
var finalCar = _mapper.Map<Entities.Car>(car);
foreach(var o in finalCar.Owners)
{
var finalOwner = _mapper.Map<Entities.Owner>(o);
o.CarId= finalCar.CarId;
_repository.AddOwner(finalOwner);
_repository.Save();
}
_repository.AddCar(finalCar);
_repository.Save();
var createdCar = _mapper.Map<Entities.Car>(finalCar);
return CreatedAtRoute("GetCar", new { id = createdCar.CarId},
createdCar);
I am sadly getting a 500 Bad Request and I know it's from the "Owners" list but I don't know why that's happening. Does anyone have any idea how I can fix this because I am not finding any solutions to my problem.
Am I doing something wrong?
Thanks a lot,
Jane

Related

ASP .NET CORE MVC save information from a JSON with multiple data

I send a JSON through $.ajax() to an mvc controller where i'd like to save those records for a given report.
reportRecords are saved in a table with fields:
id, Reportid, ItemId, CategoryId, UnitCost, Revenue, Units
I need to update only UnitCost, Revenue, Units for the given id.
The JSON is an aggregate of data from different entities collected from an HTML table.
My JSON (where id is the id in the reportRecords table):
[
{"id":"1","category":"xxx","item":"yyy","packing":"zzz","units":"35","unitCost":"45","unitRevenue":"65","unitMargin":"20","marginPercent":"44.44","totCost":"1575","totRevenue":"2275","totMargin":"700,00","marginWeight":"80.65 %"},
{"id":"2","category":"xxx","item":"yyy","packing":"zzz","units":"56","unitCost":"32","unitRevenue":"35","unitMargin":"3","marginPercent":"9.37","totCost":"1792","totRevenue":"1960","totMargin":"168,00","marginWeight":"19.35 %"}
]
My C# EF class:
public class ReportRecord
{
[Key]
public int id { get; set; }
[Required]
public Report Report { get; set; }
[Required]
public Item Item { get; set; }
[Required]
public Category Category{ get; set; }
[Required]
[DataType(DataType.Currency)]
public float? UnitCost { get; set; }
[Required]
[DataType(DataType.Currency)]
public float? Revenue { get; set; }
[Required]
public int Units { get; set; }
}
How i solved:
I had to get JSON data, pick the ones i needed and save the involved entities. I solved it this way:
// POST: Reports/Asave/5
[HttpPost]
public async Task<IActionResult> Asave(int? id)
{
var json = Request.Query["json"][0];
var jsonAsArray = JArray.Parse(json);
float reportTotalCost = 0;
float reportTotalRevenue = 0;
foreach (var item in jsonAsArray)
{
reportTotalRevenue += item.Value<float>("totRevenue");
reportTotalCost += item.Value<float>("totCost");
var reportRecord = _context.ReportRecords.Find(item.Value<int>("id"));
reportRecord.Revenue = item.Value<float>("unitRevenue");
_context.SaveChanges();
}
var report = _context.Reports.Find(id);
report.Revenue = reportTotalRevenue;
_context.SaveChanges();
return Json(new { code = 200, result = "success" });
}
It works but i think it might be prone to side effects.

Map two models with a matching property to a new model

I'll try to explain this the best that I can so it makes sense.
I have two Models - BuyerProfile and Producerprofile
BuyerProfile
public class BuyerProfile : IAuditTrack
{
[KeyProperty(Identity = true)]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int BuyerTypeId { get; set; }
[Required]
public string Address { get; set; }
[Required]
public string City { get; set; }
[Required]
public string State { get; set; }
public string Zipcode { get; set; }
public string Description { get; set; }
[NonStored]
public string BuyerTypeDisplay { get; set; }
}
ProducerProfile
public class ProducerProfile : IAuditTrack
{
[KeyProperty(Identity = true)]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Address { get; set; }
[Required]
public string City { get; set; }
[Required]
public string State { get; set; }
public string Zipcode { get; set; }
public string Description { get; set; }
}
I have a simple method on my controller that retrieves all of the profiles in the database and concatenates them together.
[HttpGet]
public JsonResult GetAllProfiles()
{
var buyerProfiles = _profileService.GetAllBuyerProfiles();
var producerProfiles = _profileService.GetAllProducerProfiles();
var profiles = buyerProfiles.Concat(producerProfiles);
return Json(profiles, JsonRequestBehavior.AllowGet);
}
Now what I would like to do is be able to find every BuyerProfile and ProducerProfile that share the same UserId and merge them together into a new model that would look like this:
public class BuyerProducerprofile
{
public string UserId { get; set; }
public string BuyerName { get; set; }
public string ProducerName { get; set; }
}
The current system that I'm building only allows users to complete 1 BuyerProfile and 1 ProducerProfile.
So for example, in the result set I might have a BuyerProfile that contains the following information:
Id -> 1543
UserId -> abc123
Name -> Bob's Buying Company
and a ProducerProfile that contains the following information:
Id -> 1678
UserId -> abc123
Name -> Bob's Produce Company
I would like to be able to combine the two into my new model so that it looks something like this:
UserId -> abc123
BuyerName -> Bob's Buying Company
ProducerName -> Bob's Produce Company
I'm not sure if this is at all possible without using some kind of Nuget package but it would be awesome if I didn't have to use one that I don't already have.
I also am currently using AutoMapper to do some of my mapping but I couldn't find any documentation that shows being able to use it to do this.
what you want to do is called a join. you can do it like this
var buyerProfiles = _profileService.GetAllBuyerProfiles();
var producerProfiles = _profileService.GetAllProducerProfiles();
var combinedProfiles =
from bp in buyerProfiles
join pp in producerProfiles on bp.UserId equals pp.UserId
select new BuyerProducerprofile()
{
UserId = pp.UserId,
BuyerName = bp.Name,
ProducerName = pp.Name
}
note: if the same user can have more than one of a type of profile, this will return a result for every combination of buyer profile and producer profile that can be made for that user.
other note: this is what is called an "inner join", and it will only give you results for users that have both profiles. You can do other kinds of joins too, but syntax for those joins doesn't feel very natural, and I don't have them committed to memory. I'm sure a google search can find the syntax for you.

MVC datetime list not saving

I have a simple MVC4 model that adds a DateTime.Now to a List<DateTime>() list.
However when I do an EntityState.Modified, the changes are not being kept.
I've debugged this by modifying another property in the model and that saves fine.
So I really am at a loss as to why this is not saving. If anyone has any ideas as to why its not saving that would be life saver material:
The Model:
public class Page
{
public int Id { get; set; }
public string PageURL { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<DateTime> Visits { get; set; }
public Page()
{
Visits = new List<DateTime>();
}
}
Here's my code:
private ApplicationDbContext db = new ApplicationDbContext();
public ActionResult CookiePolicy()
{
var page = db.Pages.FirstOrDefault(c => c.PageURL == "cookiepolicy");
page.Visits.Add(DateTime.Now); // this list of datetime objects does not get updated
page.Title = "test "; //but this property does
ViewBag.Title = page.Title;
db.Entry(page).State = EntityState.Modified;
db.SaveChanges();
return View(page);
}
Edit Fabio Luz mentioned:
"collection of primitive types (like int, DateTime, bool) are not
supported"
So the solution below seems like the right option.
Ok, so after some deliberation. I decided to create a new model called vist and have this as the list instead of datetime:
public class Visit
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public BrowserType BrowserType { get; set; }
public String Duration { get; set; }
public int PageId { get; set; }
public virtual Page Page { get; set; }
public Visit()
{
DateTime = DateTime.Now;
BrowserType = BrowserType.Other;
}
}
There are benefits to this. Now I can store more information then just the datetime.
So for anyone who had the same problem as me. Consider pushing it out into its own model for greater flexibility.
Like Fabio Luz mentioned in his comment primitive type collections aren't supported. A collection within an class retrieved from a context is generally assumed to represent a One-to-Many / Many-to-Many relationship.
When building models keep in mind how they would be represented in a SQL table, and having a column that has a collection within is not supported in such a structure. Now, if you were referencing another object (table) than the object (table record) would have certain properties, such as a primary key etc.
Hope this helps.
Edit:
Here is a sample model you might want to consider:
public class Page
{
public int Id { get; set; }
public string PageURL { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public virtual IQueriable<Visit> Visits { get; set; }
}
public class Visit
{
// ... properties related to data you wish to retain about the visit
public virtual Page Page { get; set; } // navigation property
}

Entity Framework, eager loading entites

I have 3 classes which I would like to 'talk' to each other on a ASP.NET MVC C# WEBAPI app. They are, Item, which can have only one User but the User can make multiple Comments on multiple Items and a Comment can have multiple Users but only one Item
My classes are as follows:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Comment> Comments { get; set; }
public User User { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Message { get; set; }
public bool Important { get; set; }
public Item Item { get; set; }
public User User { get; set; }
}
I'm using angularJs front end, and so that I don't get a forever repeating loop I have configured the following:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
I'm using entity framework 6 and I want to Display all items including the comments and the users who have commented
I have read and feel? that Projection using Linq is probably best?
I have the following in my dbContext. (P.S, I've disabled LazyLoading, and including the System.Data.Entity namespace)
using(var _db = new dbContext)
{
var model = _db.Items.Include(i=>i.Comments.Select(p=>p.User).Select(vm=>new ViewModelItem(){
//here I think is where I would say....
ViewModelItem.Name = x.Name,
ViewModelItem.Description = x.Description,
ViewModelItem.Comments = ///
ViewModelItem.Comments.User.Name = ///
})).ToList();
return Ok(model);
}
I'm not sure where to go from here.
So I want to display All the comments and include the User who owns the Item but also include All the comments for that Item, and all the Users who have commented on that Item.
Without causing an infinite loop.
If I'm not being clear, please ask me to clarify. Any help as always is greatly appreciated.
Thank you
Assuming your comments data is good this should do it.
var model = db.Comment.Select(p=>
new ViewModelItem {
Name = p.User.Name,
Comments=p,
Description=p.Item.Description,
});

Seed() not fully updating the database

I'm giving a go through some tutorials (here and here) on ASP.NET MVC, and decided to try a few things on my own. Now, I've got three tables, Resume, Descriptions, SubDescriptions. Here's the code for the three:
public class Resume
{
public Resume()
{
Descriptions = new List<Description>();
}
[Key]
public int ResumeId { get; set; }
[Required]
public string Employer { get; set; }
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
public DateTime EndDate { get; set; }
[Required]
public string Location { get; set; }
[Required]
public virtual ICollection<Description> Descriptions { get; set; }
}
public class Description
{
public Description()
{
SubDescriptions = new List<SubDescription>();
}
[Key]
public int DescriptionId { get; set; }
[ForeignKey("Resume")]
public int ResumeId { get; set; }
[Required]
public string Desc { get; set; }
public virtual Resume Resume { get; set; }
public virtual ICollection<SubDescription> SubDescriptions { get; set; }
}
public class SubDescription
{
[Key]
public int SubDescriptionId { get; set; }
[ForeignKey("Description")]
public int DescriptionId { get; set; }
[Required]
public string Sub { get; set; }
public virtual Description Description { get; set; }
}
And my Seed() is as follows:
protected override void Seed(ResumeDBContext context)
{
context.Resumes.AddOrUpdate(i => i.Employer,
new Resume
{
Employer = "Employer Test",
StartDate = DateTime.Parse("2012-3-26"),
EndDate = DateTime.Parse("2013-10-24"),
Location = "Houston, TX",
Descriptions = { new Description
{ Desc = "DescTest",
SubDescriptions = {new SubDescription {Sub = "SubTest"},
new SubDescription {Sub = "SubTest2"},
new SubDescription {Sub = "SubTest3"}}
},
new Description { Desc = "DescTest2" }}
}
);
}
Now, whenever I run update-database from my Package Manager Console, it says it's running Seed(). However, upon querying the database, my SubDescriptions table is still empty. Everything else populates as expected. I don't receive any errors, or anything of the sort. Am I missing something silly in my associations?
The Resume table is populated properly from the Seed(), and the Descriptions table is populated as well, with the appropriate association to the Resume table. Yet, following the same example to try to populate SubDescriptions, the table is just flat out empty. The associations and navigation properties appear to be set correctly, but as I'm new to this, I'm not 100% certain.
Okay, so I found the answer quite by accident. I dropped and recreated the database, and when it ran Seed() again, it populated all my tables as it should. Initially, I was making changes to the Seed() and updating, hoping that they'd be applied. But since the data already existed in the tables, it wasn't populating.

Categories