Now, I realize that this question has been answered before, but I just can't warp my head around it and make it work in my example:
View
should contain 2 foreach loops fx
foreach(var item in Model.ListA)
foreach(var item in Model.ListB)
Model should contain a class with a LinqDataContext object and two properties: (ListA and ListB)
Controller should pass the Model through the View.
How would the Model and Controller look to achieve this?
Some simple code examples would be really helpful :)
You've got it a bit backward. Your data context should be in the controller (preferably in a layer even lower that the controller uses). You always want your controller responsible for getting/updating data, and populating the model. Then, the model is delivered to the view with everything needed for the presentation of that data.
public class MyModel
{
public List<ListAEntity> ListA {get;set;}
public List<ListBEntity> ListB {get;set;}
}
public class HomeController : Controller
{
private readonly MyDataContext _context = new MyDataContext();
public ActionResult Index()
{
var model = new MyModel()
{
ListA = _context.Get<ListAEntity>().ToList(),
ListB = _context.Get<ListBEntity>().ToList()
};
return View(model);
}
}
Whoa, padding a LinqDataContext to a View smells pretty bad. Why would you do that?
The controller should get all the data that it needs either from said LinqDataContext or from a backend service and then create a simple ViewModel that only contains an IList or IEnumerable.
public class YourViewModel
{
public List<A> ListA {get; set;}
public List<B> ListB {get; set;}
}
public ActionResult YourControllerAction()
{
var context = yourDataContext;
var model = new YourViewModel
{
ListA = context.TableA.Where(x => x.Something)
.Select(x => x.ConvertSqlToBusinessObject()).ToList(),
ListB = context.TableB.Where(x => x.Something)
.Select(x => x.ConvertSqlToBusinessObject()).ToList()
};
return View("Index",model);
}
I would add a small addition to the previous answers: The controller should implement the logic necessary to select the proper view and view model, however it should NOT populate the View Model. The view model is perfectly capable of populating itself.
This pattern improves the encapsulation of both the controller and view model as well as creating a cleaner demarcation between concerns. Thus, if I steal Michael's code snippet:
In the controller
public ActionResult YourControllerAction(){
MyDbContext context = new MyDbContext();
return View("YourControllerAction", new YourViewModel(context));
}
In the view model
public class YourControllerAction {
public YourControllerAction(MyDbContext context) {
ListA = context.TableA.Where(x => x.Something).Select(x => x.ConvertSqlToBusinessObject()).ToList();
ListB = context.TableB.Where(x => x.Something).Select(x => x.ConvertSqlToBusinessObject()).ToList();
}
}
Related
I have this model that joints two table cars and model. I can only see the data within the model. So if it returns two models it will on only display one record. I am trying to have to return a list of objects?
Model
public class CarsJoinModel
{
public Cars Cars { get; set; }
public Model Model { get; set; }
}
IQueryable<CarsJoinModel> GetCarsAndModel();
The name Select does not exist in the current context
Model = t.Model
.Select(entry => new
{
DisplayName = entry,
Model = entry.
}).ToList(),
This is working but will I will need this to be a list
Model = t.Model.DisplayName
Updated
var CarsModel = (from c in UnitOfWork.GetRepository<Cars>().Get()
join m in UnitOfWork.GetRepository<Model>().Get() on c.Id equals m.CarId
select new CarsModel
{
Car = c,
Model = m
}
);
public class CarsModel
{
public CarsCars{ get; set; }
public IEnumerable<Model> Model{ get; set; }
}
I assume you are using EntityFramework here. Normally you would somehow relate those two things together and if one was a child of the other, you could just .Include() it and both would be hooked up. If not, then you have to go manually load each instance and build up this Model you are returning yourself. EF won't do it for you. It won't be able to project those two separate entities the way you want. The only way I think you could make it work would be a painful/chatty experience. Also, if you leave it as IEnumerable or don't do .ToList(), the query doesn't run so if the context is killed below before it has a chance to run, you will also be in trouble. Hopefully you can do this, but I don't know your model:
var result = UnitOfWork.GetRepository<Cars>().Get().Include(a => a.Models).ToList();
return result.Select(a => new CarsModel { Car = a, Models = a.Models }).ToList();
If that doesn't work, then you need the more painful/slow way:
var cars = UnitOfWork.GetRepository<Cars>().Get().ToList();
var result = new List<CarsModel>();
foreach (var car in cars)
{
result.Add(new CarsModel { Cars = car, Model = UnitOfWork.GetRepository<Models>().Get(a => a.CardId == car.Id).ToList() }); // Assuming .Get() can take a filter
return result;
}
Let say that i have a Books table and a Chapters. Those 2 tables are related in one-to-many relationship. And I want to make a website with ASP.NET MVC5 to display the Book Name and its Chapters. So, I can perform atleast 2 query scenarios to display needed data:
1. Query all data in a Controller
BookViewModel.cs
class BookViewModel{
List<Books> books{set;get;}
List<ChapterList> chapterLists{set;get;}
}
ChapterList.cs
class ChapterList{
List<Chapters> chapters{set;get;}
}
Book.cs (the Controller)
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
List<ChapterList> chapterList = new List<ChapterList>();
foreach(Books m in books)
{
chapterLists.Add(db.Chapters.Where(m => m.book_id = m.id).ToList());
}
BookViewModel bvm = new BookViewModel();
bvm.books = books;
bvm.chapterLists = chapterLists
return view();
}
Index.cshtml (the View) --I think I'll skip this code, because I assume you know how to display it
2. Query in Razor
Book.cs (the Controller)
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
return view(books);
}
Index.cshtml (the View)
#foreach(Books book in model)
{
#book.Name
foreach(Chapters chapter in book.Chapters.ToList())
{
#chapter.Name
}
}
The second query scenario is the easiest for me, but I am not sure which one is faster. So, my question is which one is the best performance (or the fastest displaying the view) if there are million Books data and the databases is in a different server? And is there any other way to display the view faster?
You can use Include method on DbSet.
I tried to simpler and faster your query. You can check below.
In controller
public class HomeController : Controller
{
public ActionResult Index()
{
var db = new YourDbContext();
var booksWithChapterList =
db.Books
.Include(s => s.chapterLists) //I add chapterList to query, so I don't fetch another query.
.Select(s => new BookDto// I convert them to smaller data transfer object.
{
Name = s.Name,
Chapters = s.chapterLists.Select(w => new BookChapterDto
{
Name = w.Name
}).ToList()
})
.ToList();
return View(booksWithChapterList);
}
}
You can find used classes here:
public class BookDto
{
public string Name { get; set; }
public IList<BookChapterDto> Chapters { get; set; }
}
public class BookChapterDto
{
public string Name { get; set; }
}
Hope this helps.
Yes, there are other faster way to display data in entity Framework, one of them is by using LINQ Queries.
In Your Controller you could do something like this:
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
List<ChapterList> chapterList = (from a in db.Books
join b in db.Charpters on a.id equals b.BookId
//where a.id==3 you can also query for specific book
select new ChapterList
{
chapter=b.chapter
}).ToList();
BookViewModel bvm = new BookViewModel();
bvm.books = books;
bvm.chapterLists = chapterLists
return view();
}
This will be faster than looping through each item using foreach.
It is always a good practice to keep all your logic in controller level instead of view.
I have read that the repository layer should not deal with ViewModels because of separation of concerns and should instead deal only with Models. This is also true for the service layer (in my case this is where my business logic is). So then the controller is left to deal with the population of the ViewModels.
I have a Model Category:
public class Category
{
public int ID { get; set; }
public int? ParentCategoryID { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public virtual ICollection<CategoryName> CategoryNames{ get; set; }
}
I have a ViewModel CategoryListViewModel used when displaying all Categories
public class CategoryListViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string ParentName { get; set; }
}
My view takes IEnumerable<...CategoryListViewModel>
This is how I populate the ViewModel from the controller:
public ActionResult Index()
{
IEnumerable<CategoryListViewModel> model;
List<CategoryListViewModel> list = new List<CategoryListViewModel>();
IEnumerable<Category> categoryList = categoryService.GetAllCategoriesList(RouteData);
foreach (var item in categoryList)
{
CategoryListViewModel temp = new CategoryListViewModel()
{
ID = item.ID,
Name = categoryService.GetCategoryName(RouteData, item.ID)
};
if (item.ParentCategoryID != null)
{
temp.ParentName = categoryService.GetCategoryName(RouteData, (int)item.ParentCategoryID);
}
list.Add(temp);
}
model = list;
return View(model);
}
My service methods:
public IEnumerable<Category> GetAllCategoriesList(RouteData data)
{
LanguageService languageService = new LanguageService();
Languages langEnum = languageService.LanguageStringToEnum(languageService.DetermineSelectedLanguage(data));
IEnumerable<Category> allCategories = repository.getAllCategoriesTest();
return allCategories;
}
public string GetCategoryName(RouteData data, int categoryId)
{
LanguageService languageService = new LanguageService();
Languages langEnum = languageService.LanguageStringToEnum(languageService.DetermineSelectedLanguage(data));
return repository.GetCategoryName(langEnum, categoryId);
}
And finally my repository methods:
public IEnumerable<Category> getAllCategoriesTest()
{
return db.Category.ToList();
}
public string GetCategoryName(Languages lang, int categoryId)
{
return db.CategoryName.Where(cn => cn.CategoryID == categoryId && cn.Language == lang).Select(cn => cn.Name).FirstOrDefault();
}
This approach looks very bad to me. My Controller is not thin anymore and I am running a lot of queries for something that simple.
If I allow ViewModels in my repository I get a much cleaner solution.
My Controller method:
public ActionResult Index()
{
return View(categoryService.GetAllCategories(RouteData));
}
Service method:
public IEnumerable<CategoryListViewModel> GetAllCategories(RouteData data)
{
LanguageService languageService = new LanguageService();
Languages langEnum = languageService.LanguageStringToEnum(languageService.DetermineSelectedLanguage(data));
return repository.SelectAllCategories(langEnum);
}
And repository method:
public IEnumerable<CategoryListViewModel> SelectAllCategories(Languages lang)
{
var categories = db.Category.Include(c => c.CategoryNames).Select(names => new CategoryListViewModel
{
ID = names.ID,
Name = names.CategoryNames.Where(cn => cn.Language == lang).Select(cn => cn.Name).FirstOrDefault(),
ParentName = db.CategoryName.Where(cn => cn.Language == lang && cn.CategoryID == names.ParentCategoryID)
.Select(cn => cn.Name).FirstOrDefault()
}).ToList();
return categories;
}
This approach, although violating separation of concerns seems to be "cleaner" to me.
My question is isn't the other approach more efficient in term of queries? Also is there any other way that this could be done so as not to write heavy controller methods and not execute that many queries? It seems to me that I am missing something.
First, bear in mind that even though it has "MVC" in the name, ASP.NET MVC only very loosely implements the MVC pattern. MVC tells you to have thin controllers because the Model is an active record, which handles all the business logic, including that around querying itself. This does not apply to ASP.NET MVC. There, your Model is actually a combination of your DAL, service layer, entity and one or more view models. This means the controller inevitably must do at least a little more work than a controller in something like Ruby on Rails, if only to wire all this stuff together.
As #Liam suggested in the comments above, your best bet is factories. That way, the controller does not actually own the logic for how to map an entity to a view model. You'll of course still need to actually call the factory in your controller, but the logic remains abstracted.
Also, a proper service layer should roll up logic that would otherwise be in your controller. If you need the localized name for the category, your service should have a method that returns all the categories with their localized name already. If you're having to hit your service multiple times, that's a clear indication that you haven't provided a necessary endpoint for your application. You may need to introduce a DTO to handle this data, since the entity class may not have the appropriate properties. You'd then have a factory that maps your DTO to a view model.
Finally, for what it's worth, your repository is completely unnecessary. Just have your service interact directly with your Entity Framework context. Having a repository buys you nothing but just an additional thing you have to maintain.
Bare in mind, I'm quite new to ASP.NET MVC. So using EF code first method, I created 2 models, Movie and Producer:
public class Movie {
public int ID {get;set;}
public string Name {get;set;}
public string Genre {get;set;}
public Producer Producer {get;set;}
}
public class Producer {
public int ID {get;set;}
public string Name {get;set;}
public DateTime DOB {get;set;}
public List<Movie> Movies {get;set;}
}
And in the controller class "Movies", I called a view:
public class MoviesController : Controller
{
//context just has DbSet< .. > of both classes.
MoviesContext db = new MoviesContext();
public ActionResult Index()
{
var movies = from m in db.Movies
select m;
return View(movies.ToList());
}
}
But if I call the producer within the view
#foreach(var item in Model)
{
<p>#item.Producer.Name</p>
}
MVC crashes with "Object reference not set to an instance of an object." error, even though when I look at the database, the Producer_ID field (which Code first made) was filled in and all of the producers with the according ID's exist in the database as well.
I did put the values inside the database manually, is that what would be causing it?
Any kind of help is more than appreciated! :)
Try loading Producer data explicitly, by calling function Include()
public ActionResult Index()
{
var movies = from m in db.Movies.Include(b => b.Producer)
select m;
return View(movies.ToList());
}
If lazy loading is enabled, your property Producer would be loaded first time it is needed. However, if lazy loading is disabled, you need to explicitly specify that you want to load other related entities.
The method Include that accepts a lambda is an extension method, so you will need to add this using at the top of your file:
using System.Data.Entity;
Alternative is to specify include by using string:
public ActionResult Index()
{
var movies = from m in db.Movies.Include("Producer")
select m;
return View(movies.ToList());
}
The result is the same, but first approach does not contain "magic strings", which is often preferable.
This is wrong, just put a break point before the return and check if the movies.Producer is not null, i beat yes..
You need to Include producer like this:
var movies = from m in db.Movies.Include(m => m.Producer)
Or maybe do a join with Producer table, see a 3 table join example (in your case is just 2) here
In my solution I have two projects. One is the main MVC4 project. The other is a DataLayer project which contains an Entity Framework 5 edmx model generated from an existing DB (and maybe some Repositories later).
The problem is that the pocos EF5 generates sits in the DataLayer project. But I need them inside the Models folder in the MVC4 project.
I want the seperate DataLayer project to increase abstraction and separation of concerns, but I can't figure out how to put those two pieces together.
(I thought to maintain another layer of pocos in the Models folder but this dose not seems right)
I have my projects separated into two as you describe.
I thought to maintain another layer of pocos in the Models folder but this dose not seems right
I think you will find you will build this layer eventually.
Here's two projects Project.Data and Project.Web. Project.Web has a project reference to Project.Data.
Project.Data.Models: Entities
Project.Web.Models: DTOs, ViewModels
My views never directly reference Entities. I will map Entities to DTOs or ViewModels using AutoMapper. This happens in my services which sits in Project.Web under its own namespace. My services never return Entity types and my views use only ViewModels.
interface IFooService
{
FooDTO GetFoo(int id);
}
public class FooService : IFooService
{
public FooDTO GetFoo(int id)
{
var foo = dbContext.Foo.Where(f => f.Id == id).Select(f => new FooDTO {
Bar = f.Bar,
Blah = f.Blah
}).FirstOrDefault();
// I let AutoMapper take care of the mapping for me
var foo = Mapper.Map<FooDTO>(dbContext.Foo.Where(f => f.Id == id).FirstOrDefault());
return foo;
}
}
Controller Action:
public ActionResult FooDetails(int id)
{
FooViewModel foo = Mapper.Map<FooViewModel>(fooService.GetFoo(id));
return View(foo);
}
Edit:
Added anther model layer to map Entity => DTO => View Model
This is the job of the repository. Create DTO classes to hold view friendly models and use the repository to call your data layer and assemble the dto. The dtos can then be built specifically for being returned to the client, including any serialization or display decorations, etc. Nothing complicated here.
I think some people's first reaction is "I'm duplicating my effort if I have to create these classes" but you're really not as these classes serve a different purpose which is exactly what you're saying, separation of concerns.
public MyViewModel // model that is bound to the view
{
private UserRepository _userRepo;
public EmployeeDto ActiveUser {get;set;}
public MyViewModel()
{
_userRepo = new UserRepository();
LoadActiveUser();
}
private void LoadActiveUser()
{
var userId = (int)HttpContext.Current.Session["activeUser"] ?? 0;
if(userId > 0)
{
ActiveUser = _userRepo.GetEmployee(userId);
}
}
}
public UserRepository
{
private SomeEntityReference1 _myDal1;
private SomeEntityReference2 _myDal2; // maybe you need to make some other data layer call in order to fill this object out
public UserRepository()
{
_myDal1 = new SomeEntityReference1 ();
_myDal2 = new SomeEntityReference2 ();
}
public EmployeeDto GetEmployee(int id)
{
var empDto = new EmployeeDto();
// get employee
var dalEmpResult = _myDal.Employees.FirstOrDefault(e => e.EmployeeId == id);
empDto.FirstName = dalResult.FName;
empDto.LastName = dalResult.LName;
empDto.Id = dalResult.EmployeeId;
// get employee department info
var dalDeptResult = _myDal2.Departments.FirstOrDefault(d => e.DepartmentId == dalEmpResult.DeptartmentId);
empDto.DepartmentName = dalDeptResult.Name;
return empDto;
}
}
// client friendly employee object
[DataContract(Name="Employee")]
public class EmployeeDto
{
public int Id {get; internal set;}
[DataMember(Name="fname")]
[DisplayName("Employee First Name:")]
public string FirstName {get;set;}
[DataMember(Name="lname")]
[DisplayName("Employee Last Name:")]
public string LastName {get;set;}
public int DeptId {get;set;}
[DataMember(Name="dept")]
[DisplayName("Works at:")]
public string DepartmentName {get;set;}
}
The only reason I show two different EF references here (your database entity schemas) is just to illustrate that this would be your opportunity to do any "additional" processing before returning a FINISHED dto, ready for consumption.