How do I manually populate ViewModel (Not using AutoMapper!) - asp.net-mvc

I know there are a lot of posts on the subject but I cannot find one that helps me do what I want. I know that I will eventually be using Automapper but before I start playing with it, I want to learn how to do things manually. I want to create a ViewModel, populate it with values from my entities by way of a repository and send it to my View. As simple as this sounds, I am stuggling to get it done. I'm using MVC 3, EF 4.3, Database First. I have auto-generated my classes. I'm posting the relevant entities (abbreviated/renamed for this post) and classes, here is what I have so far:
Aggregate Entity: Shipping Header
using System;
using System.Collections.Generic;
namespace My.Models
{
public partial class ShippingHdr
{
public ShippingHdr()
{
this.ShippingLI = new HashSet<ShippingLI>();
}
public int ID { get; set; }
public int ShipToSiteID { get; set; }
public Nullable<System.DateTime> DateShipped { get; set; }
public Nullable<System.DateTime> EstDeliveryDate { get; set; }
public string FromSitePOC { get; set; }
public Nullable<int> ShipperID { get; set; }
public string TrackingNo { get; set; }
public string Comments { get; set;}
public virtual Shippers Shippers { get; set; }
public virtual ICollection<ShippingLI> ShippingLI { get; set; }
}
}
Here is my ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace My.Models.ViewModels
{
public class ShippingHeaderSummaryVM
{
public int ID { get; set; }
public string Site { get; set; }
public Nullable<System.DateTime> DateShipped { get; set; }
public Nullable<System.DateTime> EstDeliveryDate { get; set; }
public string TrackingNo { get; set; }
public string HeaderComments { get; set; }
public string Shipper { get; set; }
public int NumOrders { get; set; }
public string Site { get; set; }
}
}
Here is a query I got to return the items I want to use to populate my Viewmodel with. I believe the best place for this is in a Repository. I verified it returns the data I want using LinqPad (hence the missing reference to my dbContxt). I just don't know how to get the values from the query to the ViewModel:
var shipments = from h in c.ShippingHdrs
where (h.ShippingLI.Count > 1)
join
e in c.vHr_Employees on h.CreatedBy equals e.ID
join
s in c.Shippers on h.ShipperID equals s.ShipperID
join
r in vAaiomsSites on h.ShipToSiteID equals r.SiteID
select new
{
h.ID,
r.Site,
h.EstDeliveryDate,
h.DateShipped,
h.TrackingNumber,
h.HeaderComments,
e.LastName,
h.ShippingLI.Count,
s.Shipper
};
So what I want to do, again without using Automapper, is to populate the ViewModel with all of the rows from the ShippingHdr entity and pass it to my view.
Here are the filelds that need to be mapped:
ShippingHeaderSummaryVM mapped from shipments
ID = h.ID
Site = r.Site
DateShipped = h.DateShipped
EstDeliveryDate = h.EstDeliveryDate
TrackingNo = h.TrackingNumber
FromSitePOC = e.LastName
NumOrders = h.ShippingLI.Count
Shipper = s.Shipper
HeaderComments = h.HeaderComments
I am stuck here.
How do I populate the ViewModel from the query?
How then do I then call that action from my controller?
I hope I have given enough information, any help would be appreciated.

In order to populate a list of shipments based on your view model object you would need to create a mapping method to map from your collection of shipments from your database to a collection of shipments based on your view model:
var model = new List<ShippingHeaderSummaryVM>();
foreach(var h in shipments)
{
var viewModel = new ShippingHeaderSummaryVM
{
ID = h.ID
Site = r.Site
DateShipped = h.DateShipped
EstDeliveryDate = h.EstDeliveryDate
TrackingNo = h.TrackingNumber
FromSitePOC = e.LastName
NumOrders = h.ShippingLI.Count
Shipper = s.Shipper
HeaderComments = h.HeaderComments
}
model.Add(viewModel);
}
return model;
As a side note, this becomes a one liner after you have AutoMapper up and running:
var model = Mapper.Map<IEnumerable<ShippingHdr>, IEnumerable<ShippingHeaderSummaryVM>>(shipments);
While, learning how to do things manually is great. Manually mapping models doesn't really benefit you in any way or form. Go with AutoMapper.

You can also use Linq to do something like this...
shipments.Select(h => new ShippingHeaderSummaryVM(){
ID = h.ID,
Site = r.Site,
DateShipped = h.DateShipped,
EstDeliveryDate = h.EstDeliveryDate,
TrackingNo = h.TrackingNumber,
FromSitePOC = e.LastName,
NumOrders = h.ShippingLI.Count,
Shipper = s.Shipper,
HeaderComments = h.HeaderComments
});
Note that while mapping view models is great for passing to a view, always do it manually when reading from a view model to update your database.
Edit: Thanks for the typo correction:-)

Related

Entity Framework SQL not optimal on navigation property

I have simple code first model (generated from db) with 3 entities:
[Table("Note")]
public partial class Note
{
public Note()
{
NoteCompanies = new HashSet<NoteCompany>();
}
public long ID { get; set; }
[Column(TypeName = "text")]
public string Content { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<NoteCompany> NoteCompanies { get; set; }
}
[Table("Company")]
public partial class Company
{
public long ID { get; set; }
[StringLength(150)]
public string Name { get; set; }
}
[Table("NoteCompany")]
public partial class NoteCompany
{
public long ID { get; set; }
public long NoteID { get; set; }
public long CompanyID { get; set; }
public virtual Company Company { get; set; }
}
When i use this Model inside ASP MVC View like:
#model Models.Note
<ul>
#for (var company in Model.NoteCompanies.Select( nc => nc.Company ))
{
#company.Name
}
</ul>
Entity framework fires single select query for each Company. I would expect that Entity would use produce JOIN query like:
SELECT {fields}
FROM
NoteCompany NC
INNER JOIN Company C ON NC.CompanyId = C.Id
WHERE
NC.NoteId = #Param
Is it possible to force EF to produce JOIN query instead of single row SELECT?
Best Regards
IT Man
It will be better to write something like this:
#{
var ids = Model.NoteCompanies.Select(nc => nc.CompanyID).ToList();
for (var company in db.Companies.Where(x => ids.Contains(x.ID)).ToList())
{
#company.Name
}
}
Or try to get NoteCompanies with corresponding Companies at controller via eager loading:
model.NoteCompanies = db.NoteCompanies.Include(x => x.Company).ToList();
return View(model);

How to pass two LINQ joined tables from Controller to View

I have three objects:
public class Part
{
[Key]
public int ID { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public int descID { get; set; }
}
public class Description
{
[Key]
public int descID { get; set; }
public string descName { get; set; }
public string description { get; set; }
}
public class GridPart
{
public string name{ get; set; }
public string number { get; set; }
public string description { get; set; }
}
I'm using LINQ to join Part and Description on the descID column:
public ActionResult Index()
{
var myParts = from p in db.Parts
join d in db.Description on p.descID equals d.DescriptorID
select new { Description = d.description, Name = p.name};
List<GridPart> partsList = new List<GridPart>();
foreach (var m in myParts)
{
GridPart gp = new GridPart();
gp.Name = m.name;
gp.description = m.description;
partsList.Add(gp);
}
return View(partsList);
}
If I was just using the Parts table, in the view I would do:
#model IEnumerable<MyApp.Models.Part>
What do I do if I'm using the joined table? This uses both Parts and Description, not to mention my List of GridParts, how do I pass this through to display all of the data I need?
Any advice is greatly appreciated.
If you pass an anonymous type to your view it won't be strongly typed. You can refer to your model like this instead
#Html.TextBox("Name")
or
#Html.Display("Name")
Although this will work I would advice against it - The better solution would be to go with a Viewmodel instead. This will make your view strongly typed.
Edit: Looking on this again I see that your actually not parsing the anonymous type to your view. Your parsing a list of GridParts.
You should be able to strongly type your view like your tried - just refer to GridParts instead of parts.
#model IEnumerable<MyApp.Models.GridPart>

Saving many to many relationship tables in Asp.Net MVC

I use Asp.Net MVC, Entity Framework. I have a form it looks like below.
Here, dropdownlist is filled from a table(types). Checkboxes is filled from another table(test). Tables are like below:
public class Types
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
}
public class Tests
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
}
public class Types_Tests
{
public int Types_TestsID{ get; set; }
public int TypesID { get; set; }
public int TestsID { get; set; }
public virtual Types Types { get; set; }
public virtual Tests Tests { get; set; }
}
Types_test table is relation table between Types and Tests. When I click Kaydet button, it shuld save type and checked tests. I made this operation using ViewBag, javascript and hdnvalue.I added checked checkboz values to a hdntext. I made saving process like below:
[HttpPost]
public ActionResult Index(string drpType, string hdntesttypes)
{
var TypeList = Types.GetAll();
ViewBag.TypesList = new SelectList(TypeList, "Id", "Name");
var testypeList = testTypes.GetAll();
ViewBag.TestTypesList = new SelectList(testypeList, "Id", "TestName");
GenericRepository<TestDisabledTypes> testDisabledRepository = new GenericRepository<TestDisabledTypes>(_context);
if (!string.IsNullOrEmpty(hdntesttypes))
{
string[] disabletypesArray = hdntesttypes.Split(',');
using (TransactionScope trns = new TransactionScope())
{
for (int i = 0; i < disabletypesArray.Length; i++)
{
Test_Types types = new Test_Types ();
types.TestTypesID = Convert.ToInt32(disabletypesArray[i]);
types.TypesID = Convert.ToInt32(drpType);
testDisabledRepository.Insert(types);
}
trns.Complete();
}
}
return View();
}
It wokrs. But I search better solution for this process. Can someone give me any idea?
Thanks.
If you don't need additional attributes for your entity class, you don't need create link table.
Just define the following class, and EF will generate the link table for you automatically.
public class Type
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
public ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
public ICollection<Type> Types {get;set;}
}
Well, in EntityFramework if you want to create a many to many relation object you need to create new object of "linking" entity. Unfortunately, it is not possible to add first object, add second object and say "Guys, you are in many to many relationships. Are you happy then?" :) You need to create relation object, set appropriate fields in it (I think these are ids of two objects itself) and add it to relation collection (entity) in your model. But before doing so you need to be sure that objects with data you are linking with are already exists in database. Otherwise you'll get an error
Also it's not necessary to create manually transaction because EF does it for you automatically each time you get/save your data

ASP MVC, mutiple models with single controller / view with EF

Can someone explain to me how to use multiple models with a single view in which each of the models represent a DB table?
What I've currently done is created a model file for each model.
Example Model:
[Table("Order")]
public class OrderModel
{
[Key, Column(Order = 0)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int OrderID { get; set; }
[Key, Column(Order = 1)]
public int UserID { get; set; }
public UserProfile Account { get; set; }
public DateTime Date { get; set; }
public int ShipLocation { get; set; }
public string PONumber { get; set; }
public int StatusID { get; set; }
public StatusModel Status { get; set; }
}
Here is the other model that encompases all the models to use in a single controller / view.
public class OrderPlacementModel
{
public OrderModel OrderChild { get; set; }
public OrderItemsModel OrderItemsChild { get; set; }
public StatusModel StatusChild { get; set; }
public MaterialsModel MaterialsChild { get; set; }
public CategoryModel CategoryChild { get; set; }
public PackModel PackChild { get; set; }
}
public ActionResult PlaceOrder()
{
var viewModel = new OrderPlacementModel
{
OrderChild = new OrderModel(),//or fetch this object from your data source
OrderItemsChild = new OrderItemsChild(),
//...etcetera
};
return View(viewModel);
}
Edit
Or, if you've strongly typed your view to a List<OrderPlacementModel> instead of a single instance, you could do something similar to this:
public ActionResult PlaceOrder()
{
var viewModel = new List<OrderPlacementModel>();
var model = new OrderPlacementModel
{
OrderChild = new OrderModel(),//or fetch this object from your data source
OrderItemsChild = new OrderItemsChild(),
//...etcetera
};
viewModel.Add(model);
//lather, rinse, repeat for however many instances you need to send to your view.
return View(viewModel);
}
Ideally, you should create a view model for the view that encompasses the fields from each model that you need to expose via the view. You can then map these in your controller. I would keep your mapping classes completely ignorant of your view models. Keep your views independent of your data model.
public class OrderViewModel
{
public int OrderId { get; set; }
public int UserId { get; set; }
public DateTime Date { get; set; }
public int ShippingLocation { get; set; }
public List<ItemViewModel> Items { get; set; }
}
public class ItemViewModel
{
public int ItemId { get; set; }
public int Title { get; set; }
}
Note how I have created a view model for the order and - to allow the order have multiple items - have separated these out into a separate model class. Now, you can type your view to OrderViewModel and use as many instances of ItemViewModel as your require.
You can then map your viewmodels to database entities from your controller:
[HttpPost]
public ActionResult ConfirmOrder (OrderViewModel model)
{
if (ModelState.IsValid)
{
foreach (ItemViewModel item in model.Items)
{
/* Create instance of OrderItemsModel (or whatever your
DB mapping class is), populate with appropriate data
from 'item' and commit to database. */
}
OrderModel order = new OrderModel();
order.OrderId = model.OrderId;
order.UserId = model.UserId;
order.Date = model.Date;
order.ShipLocation = model.ShippingLocation;
/* TODO: Commit new order to database */
}
}
Doing things this way adds a little overhead to your initial development time but allows you a great deal more flexibility as you aren't forced to mould all of your views to the shape of your entity classes.

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Resources