I develop websites using web-forms, now I have a project where I am using MVC3 Framework with Rzor. My question is about some basic design patterns in MVC. I have a Webpage, where on left side i will Pull Categories from SQL Table, In Center I will Query another Sql Table, and few more all over the page.
So my question is...whats the best way to bring data into one webpage, all these queries are totally independant, do I need to create new MODEL for every Query? or there is a better way of doing it?
in WebForms I used user controls, where every user control had its own design & Sql Queries. I have heard about using Partial Views in MVC, but i am not sure, I guess i am having hard time understanding how to bring data into one webpage using different queries & show output on webpage.
Thanks
You should create a ViewModel. Look at Update below
This is a model that represents your page. The elements you want to show in your view should exist in your ViewModel. You will populate the ViewModel in your controller and display them on the page.
I've written an example of a shopping site page, with categories on the left and Products in the centre. Both entities would exist in different tables.
Example:
class MainPageViewModel
{
//this data is from a different table.
//and goes on the left of the page
public string Categories {get; set;}
//this data is also from a different table.
//and goes on the center of the page
public List<Products> Products {get; set;}
}
In your controller:
public class HomeController : Controller
{
// GET: /Home/
public ActionResult Index()
{
MainPageViewModel vm = new MainPageViewModel();
vm.Categories = GetCategories();
//use the GetProducts() to get your products and add them.
vm.Products.Add(...);
return View(vm); //pass it into the page
}
string[] GetCategories()
{
DataTable data = GetDataFromQuery("SELECT * FROM Categories WHERE..");
//convert the data into a string[] and return it..
}
//maybe it has to return something else instead of string[]?
string[] GetProducts()
{
DataTable data = GetDataFromQuery("SELECT * FROM Products WHERE..");
//convert the data into a string[] and return it..
}
DataTable GetDataFromQuery(string query)
{
SqlDataAdapter adap =
new SqlDataAdapter(query, "<your connection string>");
DataTable data = new DataTable();
adap.Fill(data);
return data;
}
}
Then in your view you display it appropriately:
#model MainPageViewModel
#{ ViewBag.Title = "MainPage"; }
<div id="left-bar">
<ul>
#foreach (var category in Model.Categories)
{
<li>#category</li>
}
</ul>
</div>
<div id="center-content">
<ul>
#foreach (var product in Model.Products)
{
<li>#product.Name</li>
<li>#product.Price..</li>
.....
}
</ul>
</div>
Update
This is about your comment where you mentioned that your database tables and columns change regularly.
I can't say for sure but maybe you shouldn't be making tables like that everyday, maybe there is a better database design you could have, or maybe an RDBMS isn't the right thing for you and you should look into a NoSql database (like MongoDB )
Nevertheless if you continue with the above code I suggest putting this into a data layer class of its own.
Also take a look at Dapper it's a very thin data access layer that just gets objects from the database with sql queries or stored procedures. (Just exactly what you need) It's made and used by stackoverflow.
Related
I'm working on a simple project which is based on the popular ContosoUniveristy tutorial. I want to extend some of the functionalities present in this tutorial.
I have created a table named School where I keep each schools properties like address, phone number, courses, students and so on. Later I added a foreignKey property named SchoolID to student, course and few other tables.
I have SchoolIndex page with basic layout view where all the schools are listed and user can click one to go to the details page. This details page has a _DitLayout layout with additional menu on the left where one can find links to appropriate informations e.g. contact, courses, students (like the ones stored in School table). _DitLayout is shared by all the contact, courses and students views.
Here I have a problem. When I click school on the SchoolIndex page I want the links in the menu on the left to point to this particular school properties. To do that I would have to somehow pass SchoolID to the layout page (not a good idea?). The other way is to somehow store the SchoolID when running trough views, controllers and actions. So that in the controller i could write
public class SchoolController : Controller
{
private SchoolContext db = new SchoolContext();
public ActionResult Contact()
{
int ID = //here I pass SchoolID;
// I fetch school from the database
SchoolModel school = db.School.Find(ID);
//and I map the properties to the ViewModel
ContactViewModel contact = new ContactViewModel();
contact.Address = school.Address;
contact.Phone = school.Phone;
//etc.
return View(contact);
}
}
Question is: How can I pass or store SchoolID between controllers? YES I need to pass ID not only between actions but also between controllers.
I thought that maybe i could store this ID in the cookie. Is it a good idea? Is there a better way to do it?
If you want to store data, without keeping it inside the URL (or HTTP request), then you would have to save it either in Session or Cookie.
Personally I would modify the MVC routing to incorporate the school id.
eg: /{schoolId}/{controller}/{action}.
This way schoolId will be available in any action, regardless of the controller.
You can use Session object or TempData to pass data between controllers.
public class SchoolController : Controller
{
private SchoolContext db = new SchoolContext();
public ActionResult Contact()
{
int ID = TempData["SchoolID"];
// I fetch school from the database
SchoolModel school = db.School.Find(ID);
//and I map the properties to the ViewModel
ContactViewModel contact = new ContactViewModel();
contact.Address = school.Address;
contact.Phone = school.Phone;
//etc.
return View(contact);
}
}
public class OtherController : Controller
{
public ActionResult School(int id)
{
TempData["SchoolID"] = id;
return RedirectToAction("/School/Contact");
}
}
I need to load multiple entity types in my View page. I am using ViewModel for this purpose. However, I need to make around 5-6 database calls to load each set of data and assign them to the relevant property of the ViewModel. I wonder if this is a recommended approach since it requires multiple database calls. Or, am I being over-concerned about this? Here is a snapshot from my code:
var model = new EntryListVM();
string userid = "";
if (ViewBag.CurrentUserId == null)
userid = User.Identity.GetUserId();
else
userid = ViewBag.CurrentUserId;
ViewBag.CurrentUserId = userid;
//First database call
model.DiscussionWall = db.DiscussionWalls.Find(wallId);
//Second database call to learn if the current students has any entry
model.DiscussionWall.DoesStudentHasEntry = db.Entries.Any(ent => ent.DiscussionWallId == wallId && ent.UserId == userid);
model.PageIndex = pageIndex;
//Third database call
model.TeacherBadges = db.Badges.Where(b => b.CourseId == model.DiscussionWall.CourseId && b.IsSystemBadge == false && b.IsEnabled == true).ToList();
//Fourth database call
model.Reactions = db.Reactions.Where(re => re.CourseId == model.DiscussionWall.CourseId).ToList();
int entryPageSize = Convert.ToInt32(ConfigurationManager.AppSettings["EntryPageSize"]);
int firstChildSize = Convert.ToInt32(ConfigurationManager.AppSettings["FirstChildSize"]);
List<ViewEntryRecord> entryviews = new List<ViewEntryRecord>();
bool constrainedToGroup = false;
if (!User.IsInRole("Instructor") && model.DiscussionWall.ConstrainedToGroups)
{
constrainedToGroup = true;
}
//Fifth database call USING VIEWS
//I used views here because of paginating also to bring the first
//two descendants of every entry
entryviews = db.Database.SqlQuery<ViewEntryRecord>("DECLARE #return_value int;EXEC #return_value = [dbo].[FetchMainEntries] #PageIndex = {0}, #PageSize = {1}, #DiscussionWallId = {2}, #ChildSize={3}, #UserId={4}, #ConstrainedToGroup={5};SELECT 'Return Value' = #return_value;", pageIndex, entryPageSize, wallId, firstChildSize, userid, constrainedToGroup).ToList();
model.Entries = new List<Entry>();
//THIS FUNCTION MAP entryviews to POCO classes
model.Entries = ControllerUtility.ConvertQueryResultsToEntryList(entryviews);
//Sixth database call
var user = db.Users.Single(u => u.Id == userid);
model.User = user;
I wonder if this is too much of a burden for the initial page load?
I could use SQL-View to read all data at once, but I guess I would get a too complicated data set to manage.
Another option could be using Ajax to load the additional results after the page loading (with the main data) is completed. For example, I could load TeacherBadges with AJAX after the page is being loaded.
I wonder which strategy is more effective and recommended? Are there specific cases when a particular strategy could be more useful?
Thanks!
It all depends on your scenario - different scenarios have different ways of doing things. There is no single right way of doing things that are similar in nature. What might work for me might not work for you. Ever heard that saying: there are many ways to kill a cat? Well this certainly applies to programming.
I am going to answer based on what I think you are asking. Your questions are very broad and not that specific.
However, I am not sure if this is a recommended approach since it
requires multiple database calls.
Sometimes you need to do one database call to get data, and sometimes you need to do more than one database call to get the data. For example:
User details with addresses: one call for user and one call for addresses
User details: one call
I am using ViewModel for this purpose.
Using view models for your views is a good thing. If you want to read up more on what I had to say about view models then you can go and read an answer that I gave on the topic:
What is ViewModel in MVC?
View models are ideal for when you have data that is coming from multiple datasets. View models can also be used to display data coming from one dataset, for example:
Displaying user details with multiple addresses
Displaying only user details
I read the data in the controller in separate linq statements, and
assign them to the relevant List property of the ViewModel.
I would not always return a list - it all depends on what you need.
If I have a single object to return then I will populate a single object:
User user = userRepository.GetById(userId);
If I have a list of objects to return then I will return a list of objects:
List<User> users = userRepository.GetAll();
It is of no use to return a single object and then to populate a list for this object:
List<User> user = userRepository.GetByUserId(userId).ToList();
Second option could be using SQL-View to read all data with one
database call, and then map them to the entities properly in
controller.
This is similar to your first question, how you return your data on the database level is up to you. It can be stored procedures or views. I personally prefer stored procedures. I have never used views before. Irrespective of what you choose your above mentioned repository methods should still look the same.
Third option could be using Ajax to load the additional results after
the page loading (with the main data) is completed.
You can do this if you want to. I would not do it if it is not really needed. I try to load data on page load. I try to get as much data on the screen before the page is fully loaded. There have been times that I had to go the AJAX route after the page was loaded. After the page was loaded I had to do an AJAX call to load my HTML table.
If you really just need to have data displayed then do just that. You do not need any fancy ways of doing this. Maybe later you need to change on screen data, then AJAX is cool to use.
I wonder which strategy is more effective and recommended? Are there
specific cases when a particular strategy could be more useful?
Let us say you want to display a list of users. We do a database call and return the list to the view. I do not normally use view models if I only return a list:
public class UserController : Controller
{
private IUserRepository userRepository;
private IAddressRepository addressRepository;
public UserController(IUserRepository userRepository, IAddressRepository addressRepository)
{
this.userRepository = userRepository;
this.addressRepository = addressRepository;
}
public ActionResult Index()
{
List<User> users = userRepository.GetAll();
return View(users);
}
}
And your view could look like this:
#model List<YourProject.Models.User>
#if (Model.Count > 0)
{
foreach (var user in Model)
{
<div>#user.Name</div>
}
}
If you need to get a single user's details and a list of addresses, then I will make use of a view model because now I need to display data coming from multiple datasets. So a user view model can look something like this:
public class UserViewModel
{
public UserViewModel()
{
Addresses = new List<Address>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
The your details action method could look like this:
public ActionResult Details(int id)
{
User user = userRepository.GetById(id);
UserViewModel model = new UserViewModel();
model.Name = user.Name;
model.Addresses = addressRepository.GetByUserId(id);
return View(model);
}
And then you need to display the user details and addresses in the view:
#model YourProject.ViewModels.UserViewModel
<div>First Name: #Model.Name</div>
<div>
#if (Model.Addresses.Count > 0)
{
foreach (var address in Model.Address)
{
<div>#address.Line1</div>
<div>#address.Line2</div>
<div>#address.Line3</div>
<div>#address.PostalCode</div>
}
}
</div>
I hope this helps. It might be to broad of an answer but it can guide you on the correct path.
Includes for linked data
For linked data it's simple (you probably know this way):
var users = context.Users.Include(user => user.Settings).ToList();
It queries all users and pre-loads Settings for each user.
Use anonymous class for different data sets
Here is an example:
context.Users.Select(user => new
{
User = user,
Settings = context.Settings
.Where(setting => setting.UserId == user.Id)
.ToList()
}).ToList();
You still kinda need to choose your main query collection (Users in this case), but it's an option. Hope it helps.
I am in the process of building a "get to know NHibernate project).
My unit tests (nunit) provide the repository layer the same configuration as the normal mvc site but using "SQLiteConfiguration.Standard.InMemory()" instead of "MsSqlConfiguration.MsSql2008". My hope is that I can use fluent nhibernate and automapping exactly the same way but have a quick in memory db for tests and a real db for the app.
In the repository constructor it creates the Session object for all queries to use and also implements IDisposable which will ensure the session is dealt with correctly. For example a save article method would look like:
public void SaveArticle(Article article)
{
Session.SaveOrUpdate(article);
Session.Flush();
}
So far the MVC site all seems to work correctly, and my tests are passing. I think I have come across an issue that must be a problem with my design.
So far I have two objects:
class Article
{
public virtual int Id {get; set; }
public virtual IList<Tag> Tags {get; set; }
}
class Tag
{
public virtual int Id {get; set; }
public virtual string Text {get; set; }
}
I only want the DB to store ONE instance of each tag. So many articles could use the same tag. So I wrote this test:
[Test]
public void TestDeletingTagWillNotDeleteOthersTagV2()
{
Article article = new Article();
article.Tags.Add(new Tag { Text = "C#" });
article.Tags.Add(new Tag { Text = "VB" });
Service.SaveArticle(article);
Article article2 = new Article();
article2.Tags.Add(new Tag { Text = "C#" });
Service.SaveArticle(article2);
Guid article1Id = article.Id;
Guid article2Id = article2.Id;
article = null;
article2 = null;
Article article3 = Service.GetArticle(article1Id);
Article article4 = Service.GetArticle(article2Id);
Assert.AreEqual(2, article3.Tags.Count);
Assert.AreEqual(1, article4.Tags.Count);
Assert.AreEqual(2, Service.Tags.Count);
article3.Tags.RemoveAt(0);
Service.SaveArticle(article3);
Article article5 = Service.GetArticle(article1Id);
Article article6 = Service.GetArticle(article2Id);
Assert.AreEqual(1, article5.Tags.Count);
Assert.AreEqual(1, article6.Tags.Count);
Assert.AreEqual(2, Service.Tags.Count);
}
This test passes, but I believe it should fail. (it does when you run the MVC site i.e. the first article only has one tag, "VB" not "C#" and "VB".
I believe its because as the session is still open nhibernate is still holding onto everything and remembers what there was.
My main question is how do I test this with the in memory db? How do I ensure that after the saves and removal of a shared tag the articles still have what they should have?
The table structure it has created is, (which I feel is wrong, should have a link table):
Article:
Id (PK, uniqueid, not null)
...........
Tag
Id (PK, uniqueid, not null)
Text (nvarchar, null)
Article_id (FK, uniqueid, null)
Not sure how to use the fluent automapping to setup a link table so that one tag can be shared across multiple Articles and one article can have many tags
Believe I found how to ensure that the Tags and articles have a link table:
configuration.Mappings(m => m.AutoMappings.Add((AutoMap.AssemblyOf<Article>())
.Override<Article>(map => map.IgnoreProperty(x => x.IsPublished))
.Override<Article>(map => map.HasManyToMany(a => a.Tags).Cascade.All())
.Conventions.Add(DefaultCascade.All()))
if you only want unique tags (Text is unique) then make the text unique and use the session to get the unique instances. For NHibernate tags with the same text but different Ids are different.
// do
article.Tags.Add(session.Get<Tag>(cSharpId));
// or
article.Tags.Add(session.Query<Tag>().Where(t => t.Text == "C#"));
// instead of
article.Tags.Add(new Tag { Text = "C#" });
also micromanaging the session (Flush) will degrade performance. Your test method already does 9 - 13 db calls (depending on lazyloading settings).
Instead of Session.Flush(); in the service method at least introduce a SaveChanges() which calls Session.Flush(); so Inserts/Updates/Deletes can be batched properly.
I'm new to ASP.NET MVC and want to create a small order management tool. My database contains the tables Orders and Articles (and a few other ones), and I generated an EF Model from my database, so I can use the full power of the EF mappings (e.g. db.Orders.Articles)
My two main relations which I'm concerned about are Orders and Articles.
An order can have many articles
An article can only belong to one order.
I've created an OrdersController with an Create action to create an order:
//
// GET: /Orders/Create
public ActionResult Create()
{
Order order = new Order()
{
// filling some order columns, e.g. date
};
Article article = new Article()
{
// ... article columns
};
order.Articles.Add(article);
return View(order);
}
//
// POST: /Orders/Create
[HttpPost]
public ActionResult Create(Order order)
{
// i know i should care more about error handling, but now ommit it
db.Orders.AddObject(order);
db.SaveChanges();
return RedirectToAction("index");
}
So I'm directly binding an EF Object to a view (read somewhere not to do that and use a view model instead, but don't really know what that view model should look like)
My view contains the Order form as well as the article form (because i want to create a order and articles at the same time and not seperate). I used these greate EditorFor Methods to do that.
And now to my problem: If i hit the submit button, the app crashes as soon as it comes to the HttpPost Create Method (when mapping the order) with this error message:
Error Message: The EntityCollection
has already been initialized. The
InitializeRelatedCollection method
should only be called to initialize a
new EntityCollection during
deserialization of an object graph.
If i hit continue in VS2010, it will complete saving the order - so my question is how to solve this problem in a reliable way.
Thanks in advance and sorry for that long story :)
I solved my problem now by using a separate ViewModel like #Yakimych advised me. However I did not copy all the attributes from the EF models, instead I just refer to them. My ViewModel looks like this:
public class NewOrderViewModel {
public Order { get; set; }
public List<Article> { get; set; }
}
I'm using NH Criteria to retrieve an entity and project selective fields onto a custom class (a bit like projecting data onto a ViewModel for display on an MVC view).
This is easy enough using ProjectionList:
var emailCriteria = mSession.CreateCriteria<Email>();
emailCriteria.SetProjection(
Projections.ProjectionList()
.Add(Projections.Property("Subject"), "Subject")
);
emailCriteria.SetResultTransformer(Transformers.AliasToBean<EmailDataModel>());
var result = emailCriteria.List<EmailDataModel>();
However, my entity contains a collection, and I want to bring that back too, and project it as a collection onto my custom class.
My domain model looks (in simplified form) like this:
public class Email {
public string Subject
public List<EmailAttachment> Attachments
etc...
}
public class EmailAttachment {
public UploadedFile File
}
public class UploadedFile {
public string Filename
public UploadedFileData Data
}
public class UploadedFileData {
public byte[] Data
}
Here's the "data model" classes I want to project onto:
public class EmailDataModel {
public string Subject
public List<EmailAttachmentDataModel> Attachments
}
public class EmailAttachmentDataModel {
public string Filename
public byte[] Data
}
Now I know these models look very similar, and you'd be forgiven for thinking "what's the point?", but that's because I've simplified them. It's nice to be able to flatten my domain objects into handy data models.
My big problem is figuring out how to access the necessary fields from deep down in my child objects (in this case, UploadedFile.Filename and UploadedFileData.Data), and project them as an EmailAttachmentDataModel collection onto my EmailDataModel.
I've read a lot of articles online which discuss accessing child collections - using either EmailCriteria.CreateAlias or EmailCriteria.CreateQuery - but I haven't found anything which explains how to project a child collection AS a collection.
I hope this will be a useful exercise for anyone interested in tinkering with NH Criteria queries.
Ok, I think I've resolved this upgrading to NHibernate 3 and using QueryOver. Here's what my code looks like now:
//Declare entities
Email email = null;
EmailAttachment attachment = null;
UploadedFile file = null;
Byte[] fileData = null;
//Select data from parent and child objects
var results = mSession.QueryOver<QueuedEmail>(() => email)
.JoinAlias(() => email.Attachments, () => attachment, JoinType.LeftOuterJoin)
.JoinAlias(() => attachment.File, () => file, JoinType.LeftOuterJoin)
.JoinAlias(() => file.Data, () => fileData, JoinType.LeftOuterJoin)
.TransformUsing(Transformers.DistinctRootEntity)
.List<Email>()
//Loop through results projecting fields onto POCO
.Select(x => new EmailDataModel()
{
Id = x.Id,
Body = x.Body,
AttachmentCount = x.Attachments.Count(),
FromAddress = x.FromAddress,
//Loop through child collection projecting fields onto POCO
Attachments = x.Attachments.Select(attach => new EmailAttachmentDataModel()
{
Data = attach.File.Data.Data,
Filename = attach.File.Filename,
Id = attach.Id
}).ToArray() //NB Now projecting this collection as an array, not a list
}).ToArray();
So there it is. Our result is a flattened class which contains the data we need, plus a collection of attachments (which each contain just two fields from our data structure - nicely flattened).
Why should you do this?
It simplifies the result by flattening into only the fields I really want.
My data is now safely encapsulated in a class which can be passed around without fear of accidentally updating my data (which could happen if you just pass back NH data entities).
Finally (and most importantly), because the code above only generates one SELECT statement. Had I stuck with my original Criteria query, it would have generated one SELECT for each row, plus more for the children further down the chain. That's fine if you're dealing with small numbers, but not if you're potentially returning thousands of rows (as I will in this instance - it's a web service for an email engine).
I hope this has been useful for anybody wishing to push a bit further into NHibernate querying. Personally I'm just happy I can now get on with my life!