I have 2 simple models:
public class Country
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Region> Region { get; set; }
}
public partial class Region
{
public int ID { get; set; }
public string Name { get; set; }
public int CountryID { get; set; }
public virtual Country Country { get; set; }
}
Is it possible to have a single page to handle the creation of a country whereby the user inputs the country with multiple regions and then only posts to the server?
I've seen an implementation here where you create a custom ViewModel with numbered properties (Region1, Region2, Region3, etc) but it's limiting, any suggestions?
(I know AngularJS can be used to do this however I have no experience in this space as of yet.)
Thanks
Yes its very possible it just depends on how you plan to implement this.
My favourite style of implementing One to Many pages is initially creating the "one" (country) then redirecting to a page with a grid element where users can add the many (regions) to the one. It works well and its a very easy way for both the programmer to create and the user to understand.
As for creating a country with multiple regions in a single post, it could be done but you must think of how the implementation will work.
Sure, this is easy to do. You have defined your data model. Either you use that also as your View Model, or you can create a new model that is a complex object. The methods in your type:
public virtual Country Country { get; set; }
public virtual ICollection<Region> Region { get; set; }
These method being present normally indicates you're using Entity Framework and that these are "related entities" that you can traverse via this "navigation property" at run-time. You can create a Country and populate the Region collection on the fly when you try to use it.
Here is a good example of using a View Model:
What is ViewModel in MVC?
///Example of a Controller method creating a view model to display
[HttpGet]
public ActionResult Index()
{
var user = _userService.Get(User.Identity.Name);
var customerId = GlobalDataManager.GetCustomerId();
if (_error != null)
{
ModelState.AddModelError("", _error);
_error = null;
}
var model = new InboundListModel();
model.Initialize(customerId, user.CompanyId);
foreach (var campaign in model.Campaigns)
{
model.InitializeCallProggress(campaign.Id, _callInfoService.GetCallsCount(campaign.Id));
}
return View(model);
}
This View Model can be anything you want but it does need to be one type. So if you want 2 put 2 types in the ViewModel you just need a new container object:
public class ComplexViewModel
{
public Country Country { get; set; }
public ICollection<Region> Regions { get; set; }
}
Then you just need a way to populate the data like the example above where I call Initialize. This goes out to EF via a DAL project and retrieves the data for the model.
Related
I need to make sure that all View data for a specific Site element is unique, but I want to be able to reuse the value for other Site's with different id's.
Example:
Site One
View one ref: ViewOne
View two ref: ViewOne <-- error
Site Two
View one ref: ViewOne <-- no error since it's a different site
View two ref: ViewTwo
View model:
public class View : AssetsBase, IView
{
public int SiteId { get; set; }
public string Name { get; set; }
public string Ref { get; set; }
public virtual IEnumerable<MetaEntry> MetaEntries { get; set; } = new HashSet<MetaEntry>();
public virtual IEnumerable<HreflangEntry> HreflangEntries { get; set; } = new HashSet<HreflangEntry>();
}
DB Context:
builder.Entity<View>().ToTable("SiteView").HasIndex(sw => sw.Ref).IsUnique();
the current approach works as expected, but I'm limited to only use a Ref value once. is what I want possible?
Thanks to user700390 who provided the solution.
DB Context:
builder.Entity<View>().ToTable("SiteView").HasIndex(sw => new { sw.Ref, sw.SiteId }).IsUnique();
I'm trying to work on a simple application.
I have three SQL tables brought in through Entity Framework and had the models created automatically.
I want to be able to scaffold out the Create/Details/Edit etc. views automatically in Visual Studio. I can do this automatically when I scaffold from a single model (like Name alone), but can't get anywhere when using a View Model as a source.
Here are my models
Name
public partial class Name
{
public Name()
{
this.Addresses = new HashSet<Address>();
this.Emails = new HashSet<Email>();
}
public int ID { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Email> Emails { get; set; }
}
Address
public partial class Address
{
public int ADDRESS_ID { get; set; }
public int NameID { get; set; }
public string ADDRESS_1 { get; set; }
public string CITY { get; set; }
public string STATE { get; set; }
public string ZIP { get; set; }
public virtual Name Name { get; set; }
}
Email
public partial class Email
{
public int EMAIL_ID { get; set; }
public int NameID { get; set; }
public string EMAIL { get; set; }
public virtual Name Name { get; set; }
}
and a View Model I created of all three
public class MainVM
{
public Name Name { get; set; }
public Address Address { get; set; }
public Email Email { get; set; }
}
I can go through the steps of creating a controller -
Right click Controllers >> Add >> Controller >> MVC 5 Controller with views, using Entity Framework.
Next I get to this screen.
If I click Add, I will get the following error.
I've read in other answers that you need to clear out the Data context class (from the first image) if you are using a View Model, but if I do that, the Add button becomes deactivated. I can't go further than that.
Any ideas here?
I bet the original poster might not still be looking for an answer by this time. but it can help seekers like me..
Found this article be of some help. Link
It seems while taking the route Controller -> Add -> New Scaffolded Item -> MVC Controller with views, using Entity Framework does not work well with view models.
If you did not provide a DataContext class in the above mentioned scaffolding process while selecting your viewmodel, MVC scaffolding will not allow you to proceed further. As you indicated the "Add" button is disabled.
The workaround is to take a two step approach.
First create controller actions using scaffolding (Controllers -> Add -> New Scaffolded Item -> MVC Controller with read/write actions)
And then add views by right clicking on individual controller action methods and then taking advantage of scaffolding. (Controller's Action method -> Right click -> Add View -> Template -> [choose anything but Empty(without model)] -> Model class -> [choose your view model here] -> Leave Data context class empty -> Add button will now be enabled).
The linked article covers the steps in detail please take a look.
However, you will still need to add code yourself to work with the database using Entity framework in your controller action methods. (Or you can choose to introduce Busines layers, repositories etc.. YMMV) But this helps avoid writing lots of code to create your views.
PS: I found this approach work just fine for me while using ASP.Net core 1.1
I tried to use a View Model in a controller and got the same error. I then added an ID Key to solve the error and EF made a table representing the View Model in my database. With that I concluded that View Models are not to know anything about the database and are only used for presentation in the View. In the controller I handled the database operations for the different entities.
I had to create a view manually and use #model myproject.ViewModels.MyViewModel in the View to represent the View Model for the Html helpers.
This process worked for me and I was able to save the information to the proper database tables per entity model.
The following code is an example. You could use mapping tool instead of manually typing the code. Breaking the viewmodel up into the entities to save using Entity Framework.
public ActionResult EmployeeDepartment(EmployeeViewModel evm)
{
if(ModelState.IsValid)
{
try
{
Department dept = new Department();
dept.DepartmentName = evm.DepartmentName;
dept.DepartmentNumber = evm.DepartmentNumber;
db.Departments.Add(dept);
db.SaveChanges();
Employee emp = new Employee();
emp.FirstName = evm.FirstName;
emp.LastName = evm.LastName;
emp.Department = dept;
db.Employees.Add(emp);
db.SaveChanges();
return RedirectToAction("Index");
}
catch(Exception ex)
{
//write code for exception
}
}
return View();
}
Keep data context class empty and then try to add the view. It worked for me. Also, add [Key] to any id attribute.
I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies
private readonly BackendContext _context;
public CompaniesController(BackendContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
return _context.Companies;
}
I get
I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?
Additional code:
Context
public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
{
public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vendor> Vendors { get; set; }
public DbSet<VendorToProduct> VendorToProducts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
}
Model
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
UPDATE
I've added some values to the table and I got the response of the first company:
[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}
BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key.
UPDATE
Error:
Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:
I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices
While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.
Project your result into an explicitly defined shape and return only what you want to return:
var result = _context.Companies
.Select(c => new
{
c.ID,
c.Name,
c.Description,
c.CustomerID
})
.ToList();
This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.
There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.
As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:
public class CompanyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
}
and returning that:
return _context.Companies
.Select(c => new CompanyViewModel
{
ID = c.ID,
Name = c.Name,
Description = c.Description,
CustomID = c.CustomerID
})
.ToList();
But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.
Another option when that's the case could be to universally set your JSON serialization to ignore circular references:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore );
But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.
I'm building a Blog Comment and Reply section and I have these three classes mapped to my DB. The first class holds a collection of related comments to an article, the second class holds a collection of related remarks to the comments:
public class Article
{
public int ArticleID { get; set; }
public byte[] Image { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DatePublished { get; set; }
public string Author { get; set; }
public CategoryTyp Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public int UserID { get; set; }
public string Description { get; set; }
public DateTime CommentDate { get; set; }
public virtual ICollection<Remark> Remarks { get; set; }
}
public class Remark
{
public int RemarkID { get; set; }
public int CommentID { get; set; }
public int ArticleID { get; set; }
public string RemarkDetail { get; set; }
public DateTime RemarkTime { get; set; }
}
And inside my Controller:
public ActionResult GetArticle(int id)
{
var article = db.Articles.Include("Comments").Where(a => a.ArticleID == id).SingleOrDefault();
return View(article);
}
I understand the basis of eager loading but my questions are:
How do you implement it when you're pulling data from multiple related tables?
What is the best practice of populating it to the View? Once I create a View Model how do I stuff the related collections?
1) With multiple related tables you can have two scenarios:
a) Multiple top level relations: you simply add multiple Include statements (I would suggest using lambda expressions instead of strings for this, to avoid typos).
db.Articles
.Include(a=>a.Comments)
.Include(a=>a.SomethingElse)
.FirstOrDefault(a=>ArticleID==id); // Side note: I would suggest this instead of your Where plus SingleOrDefault
For these scenarios I always use a helper method like this one.
b) Multiple nested related entities:
db.Articles
.Include(a=>a.Comments.Select(c=>c.Remarks)
.FirstOrDefault(a=>ArticleID==id);
2) It's a bit up to you how you pass the data to the views. One best practice I can tell you is that you shouldn't let views lazy load any dependant entities or collections. So your use of Include is correct, but I would even suggest to remove the virtual (deactivate lazy loading) to avoid missing an Include by accident.
Regarding the ViewModels you mention, you are actually not using view models, but your data models. This is OK in most cases, unless you need to format the data somehow or add extra information. Then you would need to create a View Model and map it from the data coming from EF.
Another scenario would be if you used WebAPI or an Ajax Action. In that case, I would suggest to use a DTO (equivalent to a ViewModel) to be able to better control the data returned and its serialization.
One last comment about ViewModels is that if you have heavy entities but you only need a few properties, a good choice is to use Projections, to instruct EF to only load the required properties, instead of the full object.
db.Articles
.Include(a=>a.Comments)
.Select(a=>new ArticleDto { Id = a.ArticleID, Title = a.Title })
.ToListAsync();
This will translate to a "SELECT ArticleID, Title FROM Articles", avoiding returning the article bodies and other stuff that you might not need.
You can chain the relationships with Include. For example:
var article = db.Articles.Include("Comments.Remarks").Where(a => a.ArticleID == id).SingleOrDefault();
I'm not sure what you mean by your second question, though. By issuing this query you already have all the comments and all the remarks for those comments. Therefore, you can access them off of the article instance out of the box:
foreach (var comment in article.Comments)
{
...
foreach (var remark in comment.Remarks)
{
...
}
}
How you handle that with your view model is entirely up to you. You could map the comments/remarks to view models of their own, set them directly on the view model, etc. That's all down to what the needs of your application are, and no one but you can speak to that.
I am having trouble using AutoMapper when doing CRUD operations in MVC. I have scoured the web looking for something related to this but cant find anything related to what I am trying to do. Maybe there is something but I am having trouble understanding the more advanced functions of AutoMapper.
Basically what im trying to do is use AutoMapper on the CREATE method in an MVC application. Now I can map just the basic information perfectly fine but when I need to include another Model I get completely lost in figuring it out. Here is my example below.
This is the model in my AppDomainClasses
public class City
{
public City()
{
this.Name = string.Empty;
this.Cities = new List<City>();
}
public City(string cityName)
{
this.Name = cityName;
this.Cities = new List<City>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<City> Cities { get; set; }
}
This is my viewmodel in which im trying to automap
public class CityForList
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class CityFull : CityForList
{
public CityFull()
{
Cities = new List<CityForList>();
}
public List<CityForList> Cities { get; set; }
}
So basically when I created this Map
Mapper.CreateMap<CityFull, City>();
Then I pass a CityFull object into the mapper
Mapper.Map<CityFull, City>(cf);
So what I am doing is that I have the Create method passing in the CityFull object and a FormCollection of items which contains the ids of the cities. So all I need to have done is map the Id, the Name and the cities that were selected and have them mapped. Now I can map the Id and Name without problem but I do not know how to then add the selected cities ( comes from the form collection of a listbox) into AutoMapper to include them in the mapping.
I thank you ahead of time for any help you can offer!
The reason you don't get the selected cities is because the "Selected" attribute is not part of the "City" Class.
I would advise you to make another map with AutoMapper, that maps the controller's to a class(Lets call it CityController) that you will create , and have that CityController related to CityForList.
Hope this helps.