I am teaching myself asp .net mvc3. I have researched a lot but the more I read the more confused I become. I want to create a page where users can register their property for sale or rent.
I have created a database which looks like this:
public class Property
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
ยทยทยท
public int Furnished { get; set; }
...
}
Now, I want dropdownlistfor = PropertyType and Furnished.
Property type would be
1 Flat
2 House
3 Detached House
...
Furnished would be:
1 Furnished
2 UnFurnished
3 PartFurnished
...
Now, I am really not sure where to keep this information in my code. Should I have 2 tables in my database which store this lookup? Or should I have 1 table which has all lookups? Or should I just keep this information in the model?
How will the model bind to PropertyType and Furnished in the Property entity?
Thanks!
By storing property types and furnished types in the database, you could enforce data integrity with a foreign key, rather than just storing an integer id, so I would definitely recommend this.
It also means it is future proofed for if you want to add new types. I know the values don't change often/will never change but if you wanted to add bungalow/maisonette in the future you don't have to rebuild and deploy your project, you can simply add a new row in the database.
In terms of how this would work, I'd recommend using a ViewModel that gets passed to the view, rather than passing the database model directly. That way you separate your database model from the view, and the view only sees what it needs to. It also means your drop down lists etc are strongly typed and are directly in your view model rather than just thrown into the ViewBag. Your view model could look like:
public class PropertyViewModel
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
public IEnumerable<SelectListItem> PropertyTypes { get; set; }
public int Furnished { get; set; }
public IEnumerable<SelectListItem> FurnishedTypes { get; set; }
}
So then your controller action would look like:
public class PropertiesController : Controller
{
[HttpGet]
public ViewResult Edit(int id)
{
Property property = db.Properties.Single(p => p.Id == id);
PropertyViewModel viewModel = new PropertyViewModel
{
PropertyId = property.Id,
PropertyType = property.PropertyType,
PropertyTypes = from p in db.PropertyTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.PropertyTypeId.ToString()
}
Furnished = property.Furnished,
FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
}
};
return View();
}
[HttpGet]
public ViewResult Edit(int id, PropertyViewModel propertyViewModel)
{
if(ModelState.IsValid)
{
// TODO: Store stuff in the database here
}
// TODO: Repopulate the view model drop lists here e.g.:
propertyViewModel.FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
};
return View(propertyViewModel);
}
}
And your view would have things like:
#Html.LabelFor(m => m.PropertyType)
#Html.DropDownListFor(m => m.PropertyType, Model.PropertyTypes)
I usually handle this sort of situation by using an enumeration in code:
public enum PropertyType {
Flat = 1,
House = 2,
Detached House = 3
}
Then in your view:
<select>
#foreach(var val in Enum.GetNames(typeof(PropertyType)){
<option>val</option>
}
</select>
You can set the id of the option equal to the value of each item in the enum, and pass it to the controller.
EDIT: To directly answer your questions:
You can store them as lookups in the db, but for small unlikely to change things, I usually just use an enum, and save a round trip.
Also look at this approach, as it looks better than mine:
Converting HTML.EditorFor into a drop down (html.dropdownfor?)
Related
I am using complicated SQL queries, i have to use SqlQuery ... in simple way:
MODEL:
public class C
{
public int ID { get; set; }
[NotMapped]
public float Value { get; set; }
}
CONTROLLER:
IEnumerable<C> results = db.C.SqlQuery(#"SELECT ID, ATAN(-45.01) as Value from C);
return View(results.ToList());
VIEW:
#model IEnumerable<C>
#foreach (var item in Model) {
#Html.DisplayFor(modelItem => item.Value)
}
and the result for item.Value is NULL.
So my question is , how can i print the computed value from SQL Query ?
Thank you for help.
I would conclude from the fact that Value is 0 that EF doesn't map returned columns to properties that are not mapped in the model.
What you could try as an alternative is to define a helper type...
public class CHelper
{
public int ID { get; set; }
public float Value { get; set; }
}
Then query into this type and copy the values to your entity afterwards:
IEnumerable<C> results = db.Database.SqlQuery<CHelper>(
#"SELECT ID, ATAN(-45.01) as Value from C")
.Select(ch => new C
{
ID = ch.ID,
Value = ch.Value
});
(Normally in a LINQ-to-Entities query you cannot project into an entity with Select. But I believe that the Select in the example above does not affect the database query and is LINQ-to-Objects in memory, so it should be allowed. I am not sure, though.)
Note that the results collection is not attached to and tracked by the context, but I guess you don't need it anyway for a GET request to render a view.
Of course you could create your view directly based on the CHelper class as view model and omit the conversion into the C entity.
I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.
I have a complex Model that I'm using on the page:
public class SpaceModels : List<SpaceModel> {
public bool HideValidation { get; set; }
[Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
public DateTime EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
}
In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).
// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
return View("CombineSpaces", models);
Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:
#model Data.SpaceModels
#using (Html.BeginForm("CombineSpaces", "Space")) {
<div class="EditLine">
<span class="EditLabel LongText">
New Space Open Date
</span>
#Html.TextBoxFor(m => m.EffectiveDate, new {
size = "20",
#class = "datecontrol",
// Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
#Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
})
#Html.ValidationMessageFor(m => m.EffectiveDate)
</div>
<hr />
Html.RenderPartial("_SpaceEntry", Model);
}
The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)
Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:
[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}
I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.
I did also try using the #Html.EditorFor(m => m.EffectiveDate) in place of the #Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the #Html.EditorForModel by the way.)
There has to be something simple that I'm missing. Please let me know if you need anything else.
Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.
What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.
The view model
public class SpacesViewModel
{
public DateTime? EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
public List<SpaceModel> SpaceModels { get; set; }
}
The GET action method
[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
var spaceModels = new List<SpaceModel>();
spaceModels.Add(
new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
var spacesVm = new SpacesViewModel
{
EffectiveDate = DateTime.Now,
DisplayEffectiveDate = true,
SpaceModels = spaceModels
};
return PartialView("_SpaceEntry", spacesVm);
}
The POST action method
[HttpPost]
public ActionResult CombineSpaces()
{
var spacesVm = new SpacesViewModel();
// this forces model binding and calls ModelState.IsValid
// and returns true if the model is Valid
if (TryUpdateModel(spacesVm))
{
// process your data here
}
return RedirectToAction("Index", "Home");
}
And the view
<label>Effective date: </label>
#Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty,
new { #class = "datecontrol" })
Sometimes you need to explicitly bind form data using hidden fields such as
#Html.HiddenField("EffectiveDate", Model.EfectiveDate.)
In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.
I hope this helps :)
I don't know if I'm missing something obvious, but I really want to grab names of clients associated with a composite key.
Controller Code:
Job job = db.Jobs.Find(id);
ViewBag.jobClientsList = new SelectList(job.JobClients.ToList(), "ClientNumber", "ClientNumber");
View Code:
<%: Html.DropDownList("ClientNumber", ViewData["JobClientsList"] as SelectList)%>
Model:
namespace Sample.CustomerService.Domain {
using System.ComponentModel.DataAnnotations;
public class JobClient {
public JobClient() { }
[Key]
[Column(Order = 1)]
public virtual int JobNumber { get; set; }
[Key]
[Column(Order = 1)]
public virtual int ClientNumber { get; set; }
public virtual Client Client { get; set; }
public virtual Job Job { get; set; }
}
}
This code works, but all I get in the dropdownlist is a bunch of numbers. What I would really like is the client names associated with the numbers but I'm really not sure how to do it! I've been looking around for ages!
After re-reading your question your answer seems simpler then expected.
Check out the Select list class http://msdn.microsoft.com/en-us/library/system.web.mvc.selectlist.aspx
The constructor your using in your controller is wrong, it should be:
ViewBag.jobClientsList = new SelectList(job.JobClients.ToList(), "ClientNumber", "Client");
You were setting the text value of the selectList to be "ClientNumber" which is why you had a list of numbers and not names!
By default the select list is showing you the property that is marked [Key]
<%: Html.DropDownList("ClientNumber",
ViewData["JobClientsList"].Client as SelectList)%>
Should print the client name (assuming the primary Key on the Client object is their name, otherwise You'd need something like ViewData["JobClientsList"].Client.FullName
The best solution would be to use a ViewModel instead of using ViewBag or ViewData for this, it'll help avoid a lot of headaches both now and in the future.
What I have done in the past to get DropDownLists working is save the List to the Session Variable, and then create my SelectList in the actual DropDownList.
Controller:
Job job = db.Jobs.Find(id).ToList();
ViewBag.jobClientList = job;
View:
<%: Html.DropDownList("ClientNumber", new SelectList((If you view is strongly typed, put that here) ViewData["JobClientsList"],"ClientNumber","ClientNumber")%>
This may be poorly worded, so I think I can clarify if need be
Anyone looking for a solution, try the following:
In your controller:
1) Get the list:
var allCountries = countryRepository.GetAllCountries();
2) define a variable:
var items = new List<SelectListItem>();
3)loop each item:
foreach(var country in allCountries)
{
items.Add(new SelectListItem() {
Text = coutry.Name,
Value = Country.Id.ToString(),
// Put all sorts of business logic in here
Selected = country.Id == 13 ? true : false
});
}
model.Countries = items;
In your view:
#Html.ActionLink("StringToDisplay", "actionName", "controllerName", new SelectList(Model.Countries, "Value","Text"),"--Please Select--", new{#class="form-control"})
Not to mention Model should have a property with
public IEnumerable<Country> Countries {get; set;}
How can I return return a collection in a method from a LINQ query that has a one to many relationship?
For instance, I have the following code where I can have many Projects to a TimeTracking object. Will the type that I have defined for the return type (IEnumerable) work? It is set up in my EF model as this specific kind of relationship.
public IEnumerable<TimeTracking> GetTimeTrackings()
{
YeagerTechEntities DbContext = new YeagerTechEntities();
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
var timeTrackings = (from timeTrackingProjects in DbContext.TimeTrackings.Include("TimeTrackings.Projects")
select timeTrackingProjects).Where(p => p.TimeTrackingID > 0);
CloseConnection(DbContext);
return timeTrackings;
}
If so, when I display it in my MVC 3 View, and my View contains an IEnumerable<YeagerTech.YeagerTechWcfService.TimeTracking> model, will the model variable have records in it for the TimeTracking and Project objects? I don't think it will. My TimeTracking object is set up as follows unless I need to inherit the Project class with it (which would then have the Project properties):
public partial class TimeTracking
{
[DataMember]
public int TimeTrackingID { get; set; }
[DataMember]
public short ProjectID { get; set; }
[DataMember]
public byte[] Attachment { get; set; }
[Required]
[DataType(DataType.Date)]
[DataMember]
public System.DateTime StartDate { get; set; }
[Required]
[DataType(DataType.Date)]
[DataMember]
public System.DateTime EndDate { get; set; }
[DataMember]
public string Notes { get; set; }
[DataMember]
public System.DateTime CreatedDate { get; set; }
[DataMember]
public Nullable<System.DateTime> UpdatedDate { get; set; }
[DataMember]
public virtual Project Project { get; set; }
}
I also want my View to display the Project text that is associated with the TimeTracking and not the Project value. How can I do this?Can someone please help?
I got the following msg from invoking a method on my WCF client.
'cannot be serialized if reference tracking is disabled'
After getting the msg, I then modified my DataContracts to include references ([DataContract(IsReference = true)]).
namespace YeagerTechModel
{
[Serializable]
[DataContract(IsReference = true)]
public partial class Customer
{
public Customer()
{
this.Projects = new HashSet<Project>();
}
[DataMember]
public short CustomerID { get; set; }
[Required]
[StringLength(50)]
[DataType(DataType.EmailAddress)]
[DataMember]
public string Email { get; set; }
I am executing the following server side code to successfully get data from my database in a parent/child relationship. The Include method explicity invokes getting the related Project data for the specific Customer. I had to do it this way because you must turn LazyLoading off if you want to get your parent/child data across the wire.
If I look at the WCF messagelog, I can see the actual data coming across in a Customer object and it has the Project object inside of the Customer object.
However, after the call is made and I actually inspect the contents of the "customer" variable, I don't see any refernces to any Project data.
public IEnumerable<Customer> GetCustomers()
{
YeagerTechEntities DbContext = new YeagerTechEntities();
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
IQueryable<Customer> customer = DbContext.Customers.Include("Projects").Where(p => p.CustomerID > 0);
CloseConnection(DbContext);
return customer;
}
The thing I want to do now, is reference the Project data coming back from the call. However, I don't get any Customer object intellisense after typing "customer.". It's all pertains to an IQueryable object.
I'm passing it back into my MVC Controller as the following type:
IEnumerable<YeagerTechWcfService.Customer> customerList = db.GetCustomers();
and into my View as the following model:
IEnumerable<YeagerTech.YeagerTechWcfService.Customer>
Now, the big question is "How can I reference the Project data coming back in my View?
The below is my code for the View, but there is no intellisense for "item.Project". Note that "Email" is a property inside my Customer object.
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
Looks like your Linq query should be closer to this (NOTE: did not test the query, might need tweaking):
var query = (from tt in DbContext.TimeTrackings.Include("Projects")
where tt.TimeTrackingID > 0
select tt).ToList();
Linq query as you have written is deferred execution, you are closing your connection before retrieving the data, so that would probably cause a runtime error.
.Include() statement should specify the property on the entity (TimeTracking in your case) that need to be loaded, so in this case that would be Project property
Once you have retrieved your enumerable collection of TimeTracking entities you can access the properties of the Project entity associated with a particular TimeTracking entity like so:
foreach(var tracking in GetTimeTrackings())
{
foreach(var project in tracking.Projects)
{
// Assuming your Project entity has a Name property
Response.Write(project.Name);
}
}
I'm not sure what you mean by
I also want my View to display the Project text that is associated
with the TimeTracking and not the Project value.
can you clarify what property from which entity you want to see? What is the Project Entity definition?
In response to your comment about closing connection after retrieving the data:
The statement IQueryable<Customer> customer = DbContext.Customers.Include("Projects").Where(p => p.CustomerID > 0); does not actually execute a query against the database until you start to iterate it (most likely in your view with a foreach statement). If you add a .ToList() at the end of that statement, it will execute it and return a List<Customer> (which is also IEnumerable) which contains all the records that are result of your query.
When you try to type customer. to get intellisense for the Customer entity, you're not seeing it because customer is a list of Customer entities (or rather an IQueryiable of them) so you would need to do something like customer[0]. to access the properties of the first Customer entity in that list (or iterate over it).
I'm not 100% sure how entity references come through in ASP.NET MVC on a model entity but a really simple way you can get this done is create a model class you want to use in your view, say something like this:
public class TimeTrackingModel {
public int TimeTrackingID { get; set; }
public string ProjectName { get; set; }
}
then in your query do this:
var customers = (from tt in DbContext.TimeTrackings.Include("Projects")
where tt.TimeTrackingID > 0
select new TimeTrackingModel { TimeTrackingID = tt.TimeTrackingID, ProjectName = tt.Project.ProjectName }).ToList();
then in your view specify IEnumberable<TimeTrackingModel> as the model and then access the properties like this:
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProjectName)
</td>
Actually, after further review, I can now see the Project collection in my Customer collection all the way back up to my client after adding a QucikWatch on the object.
The correct answer is the last part of my post where the LazyLoadingEnabled = false appears.
I am using ASP.NET MVC 3 with Entity Framework 4 using POCOs and want to query a set and select some properties to put into my viewModel. I will sketch a simplified version of my situation:
Situation:
I have an entity BananaTree containing a collection of Banana
public class Banana
{
public int Id { get; set; }
public int Size { get; set; }
public TimeSpan Age { get; set }
public string Description { get; set; }
}
public class BananaTree
{
public int Id { get; set; }
public ICollection<Banana> Bananas { get; set; }
}
I also have a view model BananaListItemViewModel used in the view showing a list of bananas for a certain banana tree. This view is managed by the BananaTreeController
public class BananaListItemViewModel
{
public int Id { get; set; }
public TimeSpan Age { get; set }
}
I have a Details action on the controller like so:
public ActionResult Details(int bananaTreeId)
{
var viewModel = from bananaTree in bananaTreeRepository.BananaTrees
where bananaTree.Id == bananaTreeId
from banana in bananaTree.Bananas
select new BananaListItemViewModel
{
Id = banana.Id,
Age = banana.Age
};
return View(viewModel);
}
What I want to change
This works fine and now I only select the items from the database that I need for my view model. However, I want to take out some more logic from my controller and am trying to do this as much as possible.
I would like to have a function in my repository like so:
IQueryable<Banana> GetBananas(int bananaTreeId)
{
return (from bananaTree in BananaTrees
where bananaTree.Id == bananaTreeId
select bananaTree.Bananas).Single().AsQueryable();
}
and use it like so:
public ActionResult Details(int bananaTreeId)
{
var viewModel = from banana in bananaTreeRepository.GetBananas(bananaTreeId)
select new BananaListItemViewModel
{
Id = banana.Id,
Age = banana.Age
};
return View(viewModel);
}
Question
My question is, in this case, will the two queries be combined and go to the database in one go like in my first example or will this first get all the bananas from the tree completely out of the database and perform the second query on that list? I would prefer the first case. If not, could I rewrite the GetBananas query to get that behaviour (for example like the query below)?
IQueryable<Banana> GetBananas(int bananaTreeId)
{
return from bananaTree in BananaTrees
where bananaTree.Id == bananaTreeId
from banana in bananaTree.Bananas
select banana;
}
Thanks very much in advance.
In your specific case, it will be only one query, if the call to Single() doesn't lead to the query to be executed. Unfortunately, I couldn't find any info on whether it does or does not. The call to AsQueryable does not trigger the execution as long, as the Bananas property really is an IQueryable.
According to http://msdn.microsoft.com/en-us/library/bb156472.aspx, the call to Single doesn't execute your query.
Conclusion:
You code should result in only one query.
In general:
You can pass an IQueryable from one method to another without it being implicitly executed.
The following code will result in only one SQL statement executed at the end, when the call to ToList happens:
IQueryable<Banana> GetBananasByWeight(int weight)
{
return from banana in Bananas where banana.Weight = weight;
}
IQueryable<Banana> FilterByQuality(IQueryable<Banana> bananaQuery, int quality)
{
return bananaQuery.Where(b => b.Quality == quality);
}
public List<Banana> GetBananas(int weight, int quality)
{
var query = GetBananasByWeight(weight);
var filteredBananas = FilterByQuality(query, quality);
return filteredBananas.ToList();
}