Query in Controller VS in Razor View - ASP.NET C# - c#

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.

Related

Linq to SQL return list objects

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;
}

ViewModels in Repository

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.

How to declare List of anonymous type

In this official ASP.NET MVC Core tutorial they have defined a ViewModel as follows where Movie is a class. Then in the Index(...) method (towards the end of the tutorial they are populating the ViewModel from a simple LINQ query as shown in the Controller below:
ViewModel:
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
Controller:
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel();
movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync());
movieGenreVM.movies = await movies.ToListAsync();
return View(movieGenreVM);
}
Question: In the above ViewModel the property movies was of type List<movie> and the query used in the Index(...) method was a simple query involving just one table (movies). But what if the query involves an INNER JOIN with two tables and hence returns an object of anonymous type as shown below. In that case how would we declare a list of anonymous type List<.?.> in a ViewModel and then populate that property of type List<.?.> in the ViewModel? In the example below I defined a ViewModel with List<dynamic> or List<object> and tried myViewModel.list = leftOuterJoinQuery.ToListAsync() but got the error as Cannot implicitly convert type 'List<<anonymous type: int CategoryId, string CategoryName, string ProdName>>>' to 'System.Collections.Generic.List<dynamic>:
LINQ INNER JOIN Query:
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
UPDATE:
As #StephenMuecke suggested below, the problem can be solved by creating another View Model containing the properties returned by my query. But I'm thinking if there is a way to avoid creating an extra View Model.
There is way to accomplish it without creating new VM, but i'm not sure it is the right way.
First, I'll turn the VM as below
public class MovieGenreViewModel
{
public ICollection movies;
public SelectList genres;
public string movieGenre { get; set; }
}
Define a helper method to compose list from anonymous objects
public static List<T> CreateList<T>(params T[] elements)
{
return new List<T>(elements);
}
Finally, change the line inside Index() method
from
movieGenreVM.movies = await movies.ToListAsync();
to
var output = await innerJoinQuery.ToListAsync();
movieGenreVM.movies = CreateList(output);
Hope this helps.

Calling a model within a model in a view. (asp.net MVC)

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

MVC dealing with multiple LINQ Queries in a single View

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();
}
}

Categories

Resources