MVC ViewModel display - asp.net-mvc

I am frustrated. I have a series of tables where I am trying to display fields from one model into another model's view. This is my table structure -
I am attempting to display Device, Status and Location fields in DeviceLog's detail view. I have created my own device model.
public class DeviceLogIndexData
{
public IEnumerable<DeviceLog> DeviceLogs { get; set; }
public IEnumerable<DeviceStatu> DeviceStatus { get; set; }
public IEnumerable<DeviceLocation> DeviceLocations { get; set; }
public IEnumerable<Location> Locations { get; set; }
public IEnumerable<Status> Status { get; set; }
public IEnumerable<Device> Devices { get; set; }
}
and have made several ways to use this. My latest attempt was -
var devicelog = new DeviceLogIndexData();
devicelog.DeviceLogs = db.DeviceLogs
.Include(d => d.Device)
.Include(d => d.Device.DeviceStatus.Select(s => s.Status))
.Include(d => d.Device.DeviceLocations.Select(x => x.Location))
.OrderBy(d => d.DeviceLogID);
devicelog = devicelog.DeviceLogs.Where(d => d.DeviceLogID == id.Value);
Now I am just totally frustrated and confused on how to use my ViewModel.

You want to project into your view model
var devicelog = db.DeviceLogs
.Include(d => d.Device)
.Include(d => d.Device.DeviceStatus.Select(s => s.Status))
.Include(d => d.Device.DeviceLocations.Select(x => x.Location))
.Where(d => d.DeviceLogID == id.Value)
.Select(d => new DeviceLogIndexData { Name = d.Device.Name, ... }).ToList();
This is all pseudo code. so you may need to play around with it a little.
Where your view model class looks something like this
public class DeviceLogIndexData {
public string Name {get;set;}
public string Status {get;set;}
public string Location {get;set;}
}
Then return an IEnumerable<DeviceLogIndexData> to your view.

Related

Entity Framework Selecting columns from multiple tables (for ThenInclude)?

I have question about select...
var applicationUser = unitOfWork.ApplicationUsers.GetAll().Include(i => i.ApplicationUserRoles).ThenInclude(i => i.ApplicationRole)
.Where(i => i.UserName.ToUpper() == userName.ToUpper())
.Select(i => new
{
i.Email,
i.FirstName,
i.LastName,
i.PhoneNumber,
i.ImageUrl,
i.JoinedDate,
i.DateOfBirth,
i.ApplicationUserRoles
})
.FirstOrDefault();
I cant get ApplicationRole how to use it in select ?
If you are using projection with Select you don't need to use Include. Just select the values. To get the Roles via the UserRoles you will need to Select to retrieve those:
var applicationUser = unitOfWork.ApplicationUsers.GetAll()
.Where(i => i.UserName.ToUpper() == userName.ToUpper())
.Select(i => new
{
i.Email,
i.FirstName,
i.LastName,
i.PhoneNumber,
i.ImageUrl,
i.JoinedDate,
i.DateOfBirth,
ApplicationRoles = i.ApplicationUserRoles.Select(x => x.ApplicationRole).ToList()
})
.FirstOrDefault();
You can further refine this by sub-selecting just the role details you need from the application role...
// ...
ApplicationRoles = i.ApplicationUserRoles.Select(x =>
x.ApplicationRole.Select(ar => new { ar.RoleId, ar.RoleName })).ToList()
I recommend using SingleOrDefault rather than FirstOrDefault if you are expecting at most 1 result. First/FirstOrDefault should always be used with an OrderBy/OrderByDescending condition to ensure predictable results.
If I can use for ApplicationRoles, it is working
.Select(i => new
{i.Email,i.FirstName,i.LastName,i.PhoneNumber,i.ImageUrl,i.JoinedDate,i.DateOfBirth,
ApplicationRoles = i.ApplicationUserRoles.Select(x => x.ApplicationRole).ToList(),
})
I cant use for ApplicationUserRoles how is it work for it ?
public class ApplicationUserSummary
{
public DateTime JoinedDate { get; set; }
public string ImageUrl { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public ICollection<ApplicationRole> ApplicationRoles { get; set; }
public ICollection<ApplicationUserRole> ApplicationUserRoles { get; set; }
}

Mapping Nested Classes using Automapper

I have 3 levels of nesting in my project. Something like this:
public class Assignment
{
public Element Element { get; set; }
}
public class Element
{
public Subject Subject { get; set; }
}
public class Subject
{
public int? Id { get; set; }
public string Subject_Title { get; set; }
}
There are many other properties in each of the class. The database follows the same structure. Now I want to map assignment from database to view model.
The mapping I wrote using automapper works for the first time but not after that. So the value of Subject is null in subsequent runs while the value of Element is fine in all runs. Issue is with Subject only.
Can anyone point me to the right direction and tell me what am I doing wrong?
Mapper.CreateMap<db_Subject, Subject>();
Mapper.CreateMap<db_element, Element>()
.ForMember(dest => dest.Subject, opt => opt.MapFrom(src => src.db_Subject));
Mapper.CreateMap<db_assignment, Assignment>()
.ForMember(dest => dest.Element, opt => opt.MapFrom(src => src.db_element));
Basically, db_subject is foreign key in db_element and similarly db_element is foreign key in db_assignment. The name of columns is different in some cases in view model.
Try this,
I'm presuming that your DB classes look like below.
public class DB_Assignment
{
public DB_Element Db_Element { get; set; }
}
public class DB_Element
{
public DB_Subject Db_Subject { get; set; }
}
public class DB_Subject
{
public int? Db_Id { get; set; }
public string Db_Subject_Title { get; set; }
}
Then create a mapping like this
Mapper.CreateMap<DB_Subject, Subject>()
.ForMember(destination => destination.Id, options => options.MapFrom(source => source.Db_Id))
.ForMember(destination => destination.Subject_Title, options => options.MapFrom(source => source.Db_Subject_Title))
.ReverseMap();
Mapper.CreateMap<DB_Element, Element>()
.ForMember(destination => destination.Subject, options => options.MapFrom(source => source.Db_Subject))
.ReverseMap();
Mapper.CreateMap<DB_Assignment, Assignment>()
.ForMember(destination => destination.Element, options => options.MapFrom(source => source.Db_Element))
.ReverseMap();
Use like below
var db_Assignment_1 = new DB_Assignment { Db_Element = new DB_Element { Db_Subject = null } };
var assignment_1 = MappingEngine.Map<DB_Assignment, Assignment>(db_Assignment_1);
var db_Assignment_2 = new DB_Assignment { Db_Element = new DB_Element { Db_Subject = new DB_Subject { Db_Id = 1, Db_Subject_Title = "Some title" } } };
var assignment_2 = MappingEngine.Map<DB_Assignment, Assignment>(db_Assignment_2);
var db_Assignment_Lst = new List<DB_Assignment> { db_Assignment_1, db_Assignment_2 };
var assignment_Lst = MappingEngine.Map<List<DB_Assignment>, List<Assignment>>(db_Assignment_Lst);
Works fine for me.

How to pass a string into MVC model with AutoMapper

I'm trying to include a csv string involvedBody in a View that is generated using AutoMapper but I can't work out where to map the string to the model.
I assumed it would be in the CreateMap call in the controller or as a .ForMember function in the mapping but I can't get it to work. I need to call the InvolvedBodies function and pass the string into the model before the model becomes an IOrderedEnumerable.
Is this possible or do I need to try something else?
ViewModel
public partial class FamilyInterventionListViewModel
{
public int Id { get; set; }
public int interventionId { get; set; }
public string interventionCategory { get; set; }
public string interventionType { get; set; }
public string outcome { get; set; }
public string achievement { get; set; }
public string involvedBody { get; set; }
public string startDate { get; set; }
public string endDate { get; set; }
public string notes { get; set; }
}
Repository
public string InvolvedBodies(int interventionId)
{
var q = (from body in context.tEntity
where body.tIntervention.Any(b => b.interventionID == interventionId)
select body.entityName
);
var ibcsv = string.Join(",", q.ToArray());
return ibcsv;
}
Controller
public ActionResult InterventionType(int Id, string achievement)
{
var model = GetDisplay(Id)
.Where(m => m.achievement == achievement)
.OrderByDescending(m => m.startDate)
;
return PartialView("_interventionType", model);
}
//Automapper for display model
private IEnumerable<FamilyInterventionListViewModel> GetDisplay(int Id)
{
var list = _repo.Get(Id);
Mapper.CreateMap<tIntervention, FamilyInterventionListViewModel>();
IEnumerable<FamilyInterventionListViewModel> viewModel = Mapper.Map <IEnumerable<tIntervention>, IEnumerable<FamilyInterventionListViewModel>>(list);
return viewModel;
}
Mapping
Mapper.CreateMap<tIntervention, FamilyInterventionListViewModel>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.interventionID))
.ForMember(d => d.interventionCategory, o => o.MapFrom(s => s.tOutcome.tInterventionCategory.InterventionCategory))
.ForMember(d => d.interventionType, o => o.MapFrom(s => s.tInterventionType.interventionType))
.ForMember(d => d.outcome, o => o.MapFrom(s => s.tOutcome.outcome))
.ForMember(d => d.achievement, o => o.MapFrom(s => s.tAchievement.achievement))
.ForMember(d => d.involvedBody, o => o.MapFrom(s => s.tEntity.Select(m => m.entityName)))
;
I've not been able to find a solution to this using Automapper so have reverted to manually mapping the csv string after the model is created using:
foreach (var item in model)
{
item.involvedBody = _repo.InvolvedBodies(item.interventionId);
}
I'd be interested to know if this is deemed to be good practice or if there is some other technique I should be using.

Asp.Net Mvc Linq issue

I know my problem is really basic. If I write /api/category/1, I wanna list all Tales with the categoryId==1. I wrote this code, but it gives an error.
[HttpGet]
public IEnumerable<Tale> GetAllTalesByCategory(int id)
{
var tales = TaleService.FindAllTale().Select(x => new Tale
{
TaleId = x.TaleId,
TaleName = x.TaleName,
Content = x.Content,
VoicePath = x.VoicePath
}).Where(x => new Tale
{
x.Categories.Select(c => c.CategoryId).First() == id
});
}
Error:
Error 1 Cannot initialize type 'MasalYuvasi.Entities.Tale' with a collection initializer because it does not implement 'System.Collections.IEnumerable' D:\MvcProject\MasalYuvasi\MasalYuvasi\Controllers\DenemeController.cs 33 13 MasalYuvasi
Models:
public class Tale
{
public int TaleId { get; set; }
public string TaleName { get; set; }
public string Content { get; set; }
public string VoicePath { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public Tale()
{
this.Categories = new List<Category>();
}
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Tale> Tales { get; set; }
public Category()
{
this.Tales = new List<Tale>();
}
}
Try this:
[HttpGet]
public IEnumerable<Tale> GetAllTalesByCategory(int id)
{
var tales = TaleService.FindAllTale().Select(x => new Tale
{
TaleId = x.TaleId,
TaleName = x.TaleName,
Content = x.Content,
VoicePath = x.VoicePath
}).Where(x => x.Categories.Select(c => c.CategoryId).First() == id).ToList();
}
Fixed the where condition, and added .ToList().
The problem is that your code is using a collection initializer here:
new Tale
{
x.Categories.Select(c => c.CategoryId).First() == id
}
I'm not sure what this code is supposed to be doing, but as x.Categories.Select(c => c.CategoryId).First() == id will return a bool, I don't think this is doing what you want it to.
Based on your comment:
I want to list in the category of tales. Forexample I have 2 tales in CategoryId is 1. If I write "/api/category/1" ı want to list this 2 tales.
I think you are looking for something simpler than what you've got. You want to select Tales (represented by x) where Any of the categories have a CategoryId of id:
.Where(x => x.Categories.Any(c => c.CategoryId == id ));
Note that you can append .ToList() to the end of the where clause, as suggested by pjobs, but this may have a subtle effect on the behavior of your application. For more detail, see LINQ and Deferred Execution.

How to use Automapper to create complex viewmodel objects from a simple object and a method call

My application needs to display a table of customer data, including data about the customer and about his most recent order from a given warehouse. The customer domain object contains a GetLatestOrder(warehouseId) method.
I have a CustomerView viewmodel and want to be able to populate it with some fields from the customer object, and a few fields from the latest order object. Can I do this using Automapper?
Try this,
In Global.asax.cs [Application_Start], public static void AppInitialize()
protected void Application_Start(object sender, EventArgs e)
{
Mapper.CreateMap<Order, CustomerViewModel>()
.ForMember(destination => destination.OrderId, options => options.MapFrom(source => source.Id))
.ForMember(destination => destination.OrderName, options => options.MapFrom(source => source.Name));
Mapper.CreateMap<Customer, CustomerViewModel>()
.ForMember(destination => destination.CustmerId, options => options.MapFrom(source => source.Id))
.ForMember(destination => destination.CustmerName, options => options.MapFrom(source => source.Name))
.ForMember(destination => destination.OrderId, options => options.MapFrom(source => Mapper.Map<IEnumerable<Order>, IEnumerable<CustomerViewModel>>(source.Orders).FirstOrDefault().OrderId))
.ForMember(destination => destination.OrderName, options => options.MapFrom(source => Mapper.Map<IEnumerable<Order>, IEnumerable<CustomerViewModel>>(source.Orders).FirstOrDefault().OrderName));
}
And in your code call the mapper like below,
var lstCustomers = new List<Customer>
{
new Customer { Id = 1, Name="Name", Orders = new List<Order> { new Order { Id = 1000, Name ="Some Name"}}},
};
var result = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerViewModel>>(lstCustomers);
I am presumed that you classes looks like below,
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CustomerViewModel
{
public int CustmerId { get; set; }
public string CustmerName { get; set; }
public int OrderId { get; set; }
public string OrderName { get; set; }
}
Also refer this post.
Yes, you can:
var vm = new CustomerViewModel();
Mapper.Map(customer, vm);
Mapper.Map(order, vm);
Each mapping will populate the properties it is configured to and leave the rest.
In the end, I took the easy approach, couldn't find another way:
In Application_start:
Mapper.CreateMap<Customer, CustomerView>();
Mapper.CreateMap<Order, CustomerView>();
In the controller method:
IEnumerable<Customer> customers = GetCustomers(false)
.OrderBy(c => c.Name);
IEnumerable<CustomerView> viewData = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerView>>(customers);
foreach (Customer customer in customers)
{
CustomerView view = viewData.Where(cv => cv.CustomerId == customer.CustomerId).First();
Mapper.Map(customer.GetLatestOrder(WarehouseId), view);
}

Resources