AutoMapper and mapping one-to-many on Create - c#

So my DB has a one-to-many association between a customer and orders.
Mapping the data to show a customer and his orders is no problem. But is there a way to map these when creating a customer?
For example:
"The very basic viewModel just to test the Mapping"
public class CVM
{
public string ContactName { get; set; } //Part of the Customer Table
public DateTime OrderDate { get; set; } //Part of the Orders Table and
//would have to be passed into the Orders List of the EF-Customer-Object
}
So the create view just has 2 inputs for Name and Date.
"The very basic controller just to test the Mapping ;)"
[HttpPost]
public ActionResult Create(CVM model)
{
Mapper.CreateMap<CVM, Customer>();
Customer customer = Mapper.Map<CVM, Customer>(model);
return View();
}
So ContactName gets mapped properly. The Problem is the OrderDate. AutoMapper would have to create an Order instance, set the value of OrderDate and pass it to the OrdersCollection of the Customer object. Is AutoMapper able to do this or am I totally wrong?
Hope you understand my explanation and someone has an Answer to me.
Thanks Folks

I think you are going about it the wrong way. What you should be doing is to instantiate a Customer instance and then map it s properties using AutoMapper.
Thus, your code would look like:
[HttpPost]
public ActionResult Create(CVM model)
{
Mapper.CreateMap<CVM, Customer>();
Customer customer = /* Construct or get a Customer instance, eg from DB. */
Mapper.Map<CVM, Customer>(model, customer);
return View();
}
BTW, you should make sure to have Mapper.CreateMap<CVM, Customer>() directives only during application startup, otherwise you are needlessly performing this (possibly costly) step on every request.
Edit
It seems I read the original question wrong. If the aim is to create a Customer with associated objects, then Automapper can help you in a few different ways (I am going forward with the Person/PhoneNumber example you gave in the comments).
Given that your Entities and View Models are:
public Person {
public string Name { get; set; }
public string List<PhoneNumber> Numbers { get; set; }
}
public PersonVM {
public string Name { get; set; }
public string IList<PhoneNumberVM> Numbers { get; set; }
}
public PhoneNumber {
public int Type { get; set; }
public string Number { get; set; }
}
public PhoneNumberVM {
public int Type { get; set; }
public string Number { get; set; }
}
then you have a few alternatives:
You can try to write a custom Mapping rule so that each PhoneNumberVM instance is mapped to a PhoneNumber instance, or
You can add a Mapper.CreateMap<PhoneNumberVM, PhoneNumber>() and just call Mapper.Map<PersonVM, Person>(model) to have your model mapped to your entity.
Of course, you would have to make sure that your model gets constructed properly, but that is not very hard as long as you use the same model to generate your HTML form.

Related

Retrieving foreign key data when using linq

I am creating a basic rating system where users will enter a movie we have watched together and review it with a rating out of 10. I am struggling with how to get all the data to be retrieve into a view model(needs more work but dev testing currently).
Controller:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
//not much here since I really am that stuck
if (#event == null)
{
return NotFound();
}
return null;
}
ViewModel:
public class EventViewModel
{
public Event Event { get; set; }
public List<Review> Reviews { get; set; }
}
I have models:
Event:
public class Event
{
public int Id { get; set; }
[ForeignKey("Event")]
public int EventTypeID { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
}
EventType:
public class EventType
{
public int Id { get; set; }
public string Type { get; set; }
}
Review:
public class Review
{
public int Id { get; set; }
[ForeignKey("ApplicationUser")]
public int UserID { get; set; }
public int Rating { get; set; }
[ForeignKey("Event")]
public int EventID { get; set; }
}
First, for a view model, this should reflect the data that the view wants to consume rather than containing the entities. Entities should always reflect the data state and that is often more information, and a relational model of the data which the view doesn't really need. As a general rule, an entity should never be passed outside of the scope of the DbContext where it was retrieved. EF does support detached entities but these need to be used with care and generally cause a lot more problems than they are worth in a project.
The goal of a view model is to compact the data that the view needs which improves over the wire performance, and also protects your system from revealing too much about it's data structure.
public class EventViewModel
{
public int Id { get; set; }
public string EventType { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
public ICollection<ReviewViewModel> Reviews { get; set; }
}
public class ReviewViewModel
{
public string UserName { get; set; }
public int Rating { get; set; }
}
Then when you go to select the event and it's reviews, you utilize something called Projection to translate the entities into the view models. This is done manually using Select() or can be automated by leveraging a library like AutoMapper which has a ProjectTo<T>() method.
Ideally your controller should have a DbContext injected, however as a starting point this example just scopes the DbContext in the request:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
using(var context = new YourAppDbContext())
{
var event = await context.Events
.Where(x => x.Id == id)
.Select(x => new EventViewModel
{
Id = x.Id,
EventType = x.EventType.Name,
DateTime = x.DateTime,
EventName = x.EventName,
Reviews = x.Reviews
.Select(r => new ReviewModel
{
UserName = r.User.Name,
Rating = r.Rating
}).ToList()
}).SingleAsync();
return View(event);
}
}
The projection here makes a few assumptions. For instance, if you have an EventType entity for the type of event, we may just want to display the event's Type as a string with the event so there's no need to create an event type view model, just Select the EventType.Name as "EventType" into the EventViewModel. ViewModels can flatten the relational data represented by the entities in this way to only pass the info the view can use rather than everything. The same thing was done to get a user's name for the review (Assuming a User navigation property on the Review) so if you wanted to list reviews you could have a user name, comment, and rating. If not needed, then you could even just use a List<int> for ratings and use x.Ratings.Select(r => r.Rating).ToList() to just get the rating #'s. You can further refine the query if you want to do things like retrieve the most recent 10 ratings or such in the query expression with OrderBy and Take. EF will compose that all down to SQL to only pull back the data necessary to populate the view model. (Faster and less memory used on the server) The advantage of projection is that you can query values from the related entities via their navigation properties as you need in the query expression without having to worry about eager loading /w Include(). EF will work out building a query to populate whatever you extract via Select.

Which one should I use for Create page : Model or ViewModel?

I have the following Entities and ViewModel (some properties removed for clarity):
Ticket:
public class Ticket
{
[Key]
public int ID { get; set; }
public string Comment { get; set; }
//Navigation Property
public virtual ICollection<Attachment> Attachments { get; set; }
}
Attachment:
public class Attachment
{
[Key]
public int ID { get; set; }
//Foreign key for Ticket
public int TicketID { get; set; }
public byte[] FileData { get; set; }
public string FileMimeType { get; set; }
//Navigation Property
public virtual Ticket Ticket { get; set; }
}
TicketViewModel:
public class TicketViewModel
{
//Default constructor
public TicketViewModel()
{
}
//Constructor with parameter
public TicketViewModel(Ticket ticket)
{
Ticket = ticket;
Attachment = new Attachment();
}
public Ticket Ticket { get; set; }
public Attachment Attachment { get; set; }
public virtual ICollection<Attachment> Attachments { get; set; }
}
In the Create a new Ticket page, there is also attachment field and multiple attachments can be added to this newly created ticket. For this reason I use TicketViewModel and pass Ticket and ICollection<Attachment> to the controller. On the other hand, I am not sure if I am wrong, because I can pass just 'Ticket' to the controller and create a new instance of TicketViewModel in the controller by passing Ticket model to the constructor of TicketViewModel. In this scenario, what approach should I follow?
Note: I pass IEnumerable<HttpPostedFileBase> to the controller for Attachment data.
Update:
I updated View and pass Model instead of ViewModel as shown below:
View:
#model Ticket
//... some other stuff
And in the controller, I pass the filled Model and new instance of the Attachment Collection to the method in the data layer as shown below.
Controller:
List<FileAttachment> fa = new List<FileAttachment>();
While the real answer is subjective and based entirely on personal preference, I will give you my answer and reasons.
Passing a so-called View Model is typically better than passing an Entity POCO, due to the fact that the page/forms will likely require more data than is used in the POCO.
In the case you provided I would flatten the classes in a View Model by merging the properties into one class for easy binding, and then create a Process() function to provide the two POCOs I needed. Usually when working complex models the Process() function will return a new Domain Model to save, or accept a Domain Model to edit.
For example, you may want to provide cheap bot protection in the form of an arithmetic problem that wouldn't need to be saved anywhere. Passing a View Model can also limit the exposing of data, in case the person doing the back end stuff is different from the person laying out the Views.
In most cases, though, the POCO can be just fine. I personally tend to pass View Models for complex data, and the actual POCO for small tables, like when the only two columns are a UID and a text field.

Problems with related objects in MVC 4

I have setup a couple of objects:
Product.cs
namespace Print_Solutions.Models
{
public class Product
{
public int ID { get; set; }
public string Manufacturer { get; set; }
public string Model { get; set; }
public string PartNumber { get; set; }
public int ProductCategoryID { get; set; }
public virtual ICollection<ProductCategory> ProductCategory { get; set; }
}
}
ProductCategory.cs
namespace Print_Solutions.Models
{
public class ProductCategory
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
The DB Context
namespace Print_Solutions.DAL
{
public class ApplicationContext : DbContext
{
public ApplicationContext() : base("DefaultConnection")
{
}
public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<ProductDetail> ProductDetails { get; set; }
public DbSet<ProductDocument> ProductDocuments { get; set; }
public DbSet<ProductImage> ProductImages { get; set; }
public DbSet<RelatedProduct> RelatedProducts { get; set; }
}
}
The Controller
namespace Print_Solutions.Controllers
{
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
public ActionResult Index()
{
ViewBag.Products = db.Products.ToList();
ViewBag.ProductCategories = db.ProductCategories.ToList();
return View();
}
}
}
The Problem
I have tried a couple of things in the view. Neither one of these seems to work. How can I access products from productcategories, and vice versa?
<ul>
#foreach(Print_Solutions.Models.ProductCategory category in #ViewBag.ProductCategories)
{
<li>#category.Name
<ul>
#foreach(var product in #category.Products)
{
<li>#product.Name</li>
}
</ul>
</li>
}
</ul>
or
<ul>
#foreach (Print_Solutions.Models.Product product in #ViewBag.Products)
{
<li>#product.Name - #product.ProductCategory.Name</li>
}
</ul>
I can access products, and product categories, but not through their related objects. What am I missing?
Your problem in the first example is that you're using the # in front of category.Products. That's going to raise a syntax error.
Your problem in the second example is that the inappropriately named ProductCategory property is actually a collection, not a single object. In other words, you can't access a property like Name directly off it. You would have to loop through this collection as well or otherwise join the values. For example, in this scenario, you'd probably just want to list all the categories the product belongs to, so you could do something like:
#string.Join(", ", product.ProductCategory.Select(m => m.Name))
Which creates an enumerable containing only the value for Name of each ProductCategory in the collection, and then joins them all together delineated by a comma and a space: Category1, Category2, Category3, ....
While I've got you, though, there's some important changes you should make:
Since these are related things, don't do two separate database queries (one for products and one for categories), instead, select one (probably the products based on your usage), and then join the other in the same query:
var products = db.Products.Include("ProductCategory").ToList();
Don't use ViewBag. Like ever. There's some times where its use may not be so bad, but until you can properly distinguish a good usage from a bad usage, it's better to just put a moratorium on the whole concept. The problem with ViewBag is that it uses dynamics; in other words, it is not evaluated at all at compile time, and either works or fails at runtime. The golden rule of software development is to always fail at compile. You want to know when you're building that something doesn't work, not when it's been deployed to your user days, weeks, months or even years after it's been developed. So, yeah, ViewBag is evil. Instead, use a strongly-typed view and pass your "model", in this case, your products into it:
Controller
var products = db.Products.Include("ProductCategory").ToList();
return View(products);
View
#model IEnumerable<Namespace.To.Product>
#foreach (var product in Model)
{
...
}
Since Product Category is a collection, you may wish to loop thru the ProductCategory List (within the Product Object) within the product loop to print them off.
Alternately, you can create a View Model which contains only what you need. (Product Name, List of ProductCategoryNames) and pass that into things.
If you intend to do a post back, you can add ProductID to this view model (or CategoryID as well for that matter) and put them into a #Html.HiddenFor() so you can reference them later as needed.

Exposing EF6 model subsets via WebAPI

For example, I have a EF6 model like this:
class User
{
public int Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<ProfileProperty> Properties { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
class Book
{
public int Id { get; set }
public int Name { get; set }
public DateTime CreationDate { get; set }
public long Size { get; set }
public string ContentPath { get; set }
}
And now I want to create a WebAPI that allows to:
Create a new user
Update user's name
Modify the list of user's books
However, here are a few tricks to it which don't let me use tutorials right off:
Some fields are either irrelevant or confidential and must not be exposed via WebAPI, for example: User.Id, User.Properties, and nested User.Books[x].ContentPath.
Only a small subset of fields is editable (in the example, User.Name).
Only a small subset of operations (CRUD) is available, therefore it's not a REST service.
The first thing that comes to mind is create extra classes for each exposed model. However, maintaining them and writing code that converts data from database models to those WebAPI-friendly classes and back is too bothersome. Is there a more simple and automated way?
The ideal approach would be one which requires writing as little redundant code as possible. Maybe there is a set of attributes to mark fields with?
You're right in thinking you should create more classes. For each exposed action (change name, create user, etc...) you should create a ViewModel that exposes only the fields you need.
public class ChangeUserNameViewModel
{
public int UserId { get; set; }
public string NewName { get; set; }
}
It's easy to convert your view model to your domain model and back again using something like AutoMapper.

How do I use EF6 with Database First and existing views?

I'm an EF noob (any version) and my Google-foo has failed me on finding out how to do this. Which makes me think I must be doing this wrong, but here is the situation:
I'm definitely in an environment that is database first and the schema won't be updated by us coders. I'm also not a fan of 'automatic' code generation, so I've stayed away from the designer or the EF powertools (though I did run through them just to see them work).
To learn I imported the Northwind DB into my LocalDB to have something to play with while creating some simple Web API 2 endpoints. This all went well as I created slimmed down models of the Employees, Shippers, & Region tables in Northwind. Region was particularly interesting as it wasn't plural and EF had issues with that. Anyway, I got by that.
My trouble now is; I want to use a view instead of a table as my source and whatever I'm doing just doesn't seem to work. What I tried was setting it up just like I did the tables. But that produces a ModelValidationException error. I tried looking at the auto-generated code from the designer, but got no insight.
My models:
//-- employee, shipper, & region work as expected
public class employee {
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public class shipper {
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
}
public class region {
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
//-- invoice is a view (actual viewname is 'Invoices')
//-- so i followed the same rules as i did for employee & shipper
//-- i have tried uppercase 'I' as well as a plural version of the model
public class invoice {
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Salesperson { get; set; }
public int OrderID { get; set; }
public int ProductID { get; set; }
public string ProductName { get; set; }
}
My Context looks like this:
public class NorthwindDBContext : DbContext {
public DbSet<Employee> Employees { get; set; }
public DbSet<shipper> Shippers { get; set; }
public DbSet<region> Regions { get; set; }
public DbSet<Invoice> Invoices { get; set; } //-- offending line of code
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//--- fix for Region being singular instead of plural
modelBuilder.Entity<region>().ToTable("Region");
}
}
If I comment out the public DbSet<Invoice> Invoices { get; set; } line in the context everything works. Just by having the line present (even if i don't reference the Invoices property) I receive the ModelValidationException error when using the context in anyway.
Can anybody tell me what I'm doing wrong here?
Thanks.
Update: I tried this in one of my controllers, but I am too noob'ish to know if this is the right path either, though it worked as far as getting records.
using (var dbContext = new NorthwindDBContext()) {
return dbContext.Database.SqlQuery<Invoice>("select * from invoices").ToList();
}
Code-first conventions will look for an ID or InvoiceID property to use as a key. Your Invoice model has neither, while the others do. This is the specific reason your code is failing.
The less-specific one is that you can't have entities in EF which lack a unique key. If you can, have the view define a key. Otherwise, you may still be able to work around the issue.

Categories