How can I initialize one model in another model's controller action? - asp.net

I created a web-app in Asp.net MVC and it has an order action. I have these two models for Order
public class Order
{
public int Id { get; set; }
public DateTimeOffset OrderTime { get; set; }
[InverseProperty("Order")]
public ICollection<OrderDetail> OrderDetails { get; set; }
}
and for OrderDetail
public class OrderDetail
{
public int Id { get; set; }
public int OrderId { get; set; }
public ICollection<Order> Order { get; set; }
public int MenuId { get; set; }
public int RestaurantId { get; set; }
public Menu Menu { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
And I created tables for them.
Also I created a controller for Order. It contains Index and Details actions. Index acction shows the list of order and every order has its own Detail link which should contain information of Order and related OrderDetail
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Order order = db.Orders.Find(id);
if (order == null)
{
return HttpNotFound();
}
return View(order);
}
And the problem is that OrderDetails is null. Can you suggest me how I can initialize OrderDetail in Details action?

You have to tell EntityFramework which navigation properties you want to include.
Order order = db.Orders
.Where( o => o.Id == id )
.Include( o => o.OrderDetails )
.SingleOrDefault();
But you cannot use Find method any more

Related

Post JSON Array in Asp.Net Web API

Hi every one I am new to ASP.Net Web API and I want to Post JSON array data any get there response.
My JSON POST Array format is
{
"User_Id":"admi12n#1234","Key_Code":"3F-47-AB-84-9F-EB-D6-6B-9C-62-CC-85-98-4D-28-6B",
"ProductDetails": [
{"Product_Id":"ELT-7035","Price":"999","Quantity":"5"},
{"Product_Id":"ELT-1254","Price":"1024","Quantity":"3"}
]
}
And I want response as follows
{
"User_Id":"admi12n#1234","Key_Code":"3F-47-AB-84-9F-EB-D6-6B-9C-62-CC-85-98-4D-28-6B",
"OrderID":"Ord-021","Name":"Sabyasachi"
"ProductDetails": [
{"Product_Id":"ELT-7035","Price":"999","Quantity":"5"},
{"Product_Id":"ELT-1254","Price":"1024","Quantity":"3"}
]
}
I generate OrderID as Random and Name from posted User_Id. Here I want to post multiple product in one order.
My Order class is as follows
public class Order
{
[Key]
public long ID { get; set; }
public string Order_Id { get; set; }
public string Product_Id { get; set; }
public long Quantity { get; set; }
public long Amount { get; set; }
public string User_Id { get; set; }
public string Key_Code { get; set; }
public DateTime Order_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
And my Product class as follows
public class Product
{
[Key]
public int Id { get; set; }
public string Product_Code { get; set; }
public string Product_Name { get; set; }
public string Product_Category { get; set; }
public string Product_Description { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
public string Image { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
I am not able to ind the best way to post the order
public Order Add(Order odrerDetails) //This will not give array of data for products
{
using (var context = new EcommerceDBContext())
{
odrerDetails.Order_Id = Helper.Random(7); //Generate random orderID from my class
odrerDetails.Created_Date = DateTime.Now;
odrerDetails.Modified_Date = DateTime.Now;
//How to Save other details
context.objOrderListing.Add(odrerDetails);
context.SaveChanges();
return odrerDetails;
}
}
In API controllers my code is as follows
public HttpResponseMessage PostOrder([FromBody] Order_Listing orderData)
{
orderData = repository.Add(orderData);
var response = Request.CreateResponse<Order_Listing>(HttpStatusCode.Created, orderData);
string uri = Url.Link("DefaultApi", new { customerID = orderData.ID });
response.Headers.Location = new Uri(uri);
return response;
}
Please help me how to achieve this.
There are several issues with your code:
Your Order and Product classes do not reflect the structure of
your JSON.
The Order class contains product details in a 1:1
relationship. Based on the JSON I assume you want a 1:n relationship.
Properties in your JSON need to have the same name as
in your classes or they won't be mapped.
Change your classes to the following and it should work.
Of course you could also change the property names in your JSON.
If you can't or don't want to change your property names, consider using DTOs
public class Order
{
public string User_Id { get; set; }
public string Key_Code { get; set; }
public string OrderID { get; set; }
public string Name { get; set; }
public List<Product> ProductDetails { get; set; }
// add the rest of your properties
}
public class Product
{
public string Product_Id { get; set; }
public string Price { get; set; }
public string Prd_Qty { get; set; }
// add the rest of your properties
}
Update: added code for Add method and Api method
Your Add method would look like this:
public Order Add(Order orderWithDetails)
{
using (var context = new EcommerceDBContext())
{
orderWithDetails.Order_Id = Helper.Random(7); //Generate random orderID from my class
orderWithDetails.Created_Date = DateTime.Now;
orderWithDetails.Modified_Date = DateTime.Now;
context.objOrderListing.Add(orderWithDetails);
// Save each Product
foreach (var detail in orderWithDetails.ProductDetails)
{
//whatever you need to do in your db-context
context.objOrderDetails.Add(detail); // just an example
}
context.SaveChanges();
return orderWithDetails;
}
}
The signature of your Api method looks wrong. What is Order_Listing? This should be Order, unless it's a DTO, in wich case you need a method to get an Order from Order_Listing.
public HttpResponseMessage PostOrder([FromBody] Order orderData)
{
orderData = repository.Add(orderData);
var response = Request.CreateResponse<Order_Listing>(HttpStatusCode.Created, orderData);
string uri = Url.Link("DefaultApi", new { customerID = orderData.ID });
response.Headers.Location = new Uri(uri);
return response;
}
A few more remarks:
If it is indeed a 1:n relationship, you probably need a property Product.OrderId.
The Order class should not have any reference to Product except for the list.
Quantity and Price should most likely not be String but numerical values, e.g. decimal.
If Order.ID is your primary key, then having Order.Order_ID is really confusing. Consider renaming it to Order.Order_Number.
public class Order
{
public string User_Id { get; set; }
public string Key_Code { get; set; }
public string OrderID { get; set; }
public string Name { get; set; }
public Product[] ProductDetails { get; set; }
}

Retrieve list from object in Database

I want to send a list of names contained in a Database using asp.net
These are my two objects:
public class Shop
{
public int ID { get; set; }
public string Country { get; set; }
public List<Item> Items{ get; set; }
}
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
}
I want to set a get controller in order to retrieve a list of items.
I did something like this:
public IEnumerable<Item> Get(int id)
{
var items= new List<Item>();
var shop= new Shop();
using (var systemDB = new ShopsDB())
{
it = systemDB.Shops.Where(s => s.ID == id).FirstOrDefault<Shop>();
items = it.Items;
}
return items;
}
This return <ArrayOfItem i:nil="true"/>.
I want to get the complete list of Items for one shop (e.g. shop with ID=1)
This will return you the list
using (var systemDB = new ShopsDB())
{
lab = systemDB.Shops.Where(s => s.ID == id).ToList();
items = lab.Items;
}
I solved modifying the Item object:
public class Shop
{
public int ID { get; set; }
public string Country { get; set; }
public List<Item> Items{ get; set; }
}
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public int ShopID { get; set; }
}
And set a query to select the ShopID

MVC ASP.NET Entity Framework Not Saving a List of Assocciated Objects

This question is in reference to the project discussed here. After resolving the previous problem I have run into a new one. When The Student object is saved, the list of courses associated with it is not saved. I can see the collection of course objects when I mouse over the student object after setting a breakpoint:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified; //breakpoint here
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = stu.CoursesTaken.ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourse.ID);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
But when I pass the object to the next method, the list of courses taken comes back null. The courses are being saved to the database, I can see them when I go into the SQL Server explorer, but for some reason they are not being attached to the student object. The code for the objects:
public class Student
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string WNumber { get; set; }
public int HoursCompleted { get; set; }
public double GPA { get; set; }
public Concentration StudentConcentration { get; set; }
public virtual ICollection<Course> CoursesTaken { get; set; }
public virtual ICollection<Course> CoursesRecommended { get; set; }
}
and:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
Something that may be important, but that I don't really understand: when I look at the table for the Course object in the database, there are three columns, called Student_ID, Student_ID1, and Student_ID2. I assume they relate to the student associated with the object and the two ways it can be associated (recommended or taken), but the odd thing is that Student_ID is always null, while the other two sometimes have a value and sometimes do not. I have not even begun to implement the recommendation process, so there is no way that list is being filled.
I reworked the classes and now it seems to be working. I changed the Course object to:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public int BaseCourseID { get; set; }
public int StudentID { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
and the controller methods to:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
db.Entry(c).State = EntityState.Added;
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == stu.ID).ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourseID);
c.Student = stu;
stu.CoursesTaken.Add(c);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
And it is now displaying the courses I add on the next page, but it seems odd that I have to save the child objects separately from the parent and that I have to get the list from the database manually instead of being able to use the object structure. Is this intended behavior, or is there a better way of doing what I'm trying to do (add a list of child objects (courses) to a student object, save the relationship to the database, and then display the list of added objects)?
You are not "passing the object to the next method". You are serializing the object and passing it on the URL, then deserializing it on the other end with this method:
return RedirectToAction("ListTakenCourses", stu);
This is not the way to go about things. What you should be doing is passing a single id, such as the student id. Then, in ListTakenCourses you look up the student again in the database, which if you are doing your query correctly will fully populate the objects.
return RedirectToAction("ListTakenCourses", new { id = stu.StudentID });
public ActionResult ListTakenCourses (int id)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == id).ToList();
//...
}

Join tables and get data from both of them

I have been trying to get data from the table I have joined to the main user table, the second table is to hold images. My current code posted below, only return the ImageID from the table when I want to be retrieving the ImagePath field, just to note this is a separate table as the user can add many images.
These are the models:
[Table("accountInfo")] // Table name
public class accountInfo
{
[Key]
public int AccountID { get; set; }
public int UserId { get; set; }
public int UserIdent { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<UserImages > UserImages { get; set; }
}
[Table("UserImages")] // Table name
public class UserImages
{
[Key]
public int ImageID { get; set; }
public int AccountID { get; set; }
public string ImagePath { get; set; }
public string ImageDesc { get; set; }
public int ProfileImage { get; set; }
}
Controller:
public ActionResult Index()
{
int id = (int)WebSecurity.CurrentUserId;
var users = db.AccountInformation.Include(c => c.UserImages).Where(c => c.UserId == id);
return View(users.ToList());
}
I am assuming I have gone wrong in the models set up. Can anyone help?
var a = db.AccountInformation.Include(c => c.UserImages.Select(x => x.AccountId)).Where(c => c.UserId == id);

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Resources