ASP.net MVC4 Multiselect ListBox with Many-to-Many relationship - entity-framework

I'm new to ASP.net, MVC4 and EF and having trouble making the leap from relatively simple samples to something (only slightly) more complex. I have simplified my model for the question here so consider the following. Forgive me if I have some of the terminology incorrect.
I have an Employee object and a Division object. An employee can be in many divisions and a division can have many employees.
public class Employee
{
public int EmployeeId { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public virtual ICollection<Division> Divisions { get; set; }
}
public class Division
{
public int DivisionId { get; set; }
public String DivisionName { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
I have these mapped in the context file via the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasMany(e => e.Divisions)
.WithMany(d => d.Employees)
.Map(m =>
{
m.ToTable("EmployeesDivisionsId");
m.MapLeftKey("EmployeeId");
m.MapRightKey("DivisionId");
});
}
For my View Controller I have the following. I have used a ViewBag (what a name) for many to one relationships and they have worked fine, so I'm trying to modify that to work with the many to many here:
public ActionResult Index()
{
return View(db.Employees).ToList());
}
//
// GET: /Employees/Details/5
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
//
// GET: /Employees/Create
public ActionResult Create()
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
return View();
}
//
// POST: /Employees/Create
[HttpPost]
public ActionResult Create(Employee employee)
{
ViewBag.Divisions = new MultiSelectList(db.Divisions, "DivisionId", "DivisionName");
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
Finally in the Edit View I have the following code. Again for a many to one relationship, a simple DropDownList works fine with the ViewBag in the controller, and with this many to many multiselect/ListBox method the 'Divisions' show up in the view, however when the Save button is hit, the validation of '1, 2' is not valid is displayed.
<div class="editor-label">
#Html.LabelFor(model => model.Divisions)
</div>
<div class="editor-field">
#Html.ListBox("Divisions")
#Html.ValidationMessageFor(model => model.Divisions)
</div>
So as I understand it, the list of 'Divisions' are being grabbed properly from the database and selected correctly in the view but when saving to the database the association through the mapped relationship isn't being made.
So my questions are:
How do I make this work so when I save, the correct 'Divisions' are saved to the Employee?
Also, I've heard people don't like using ViewBags, is there a (better?) alternate way?

This answer maybe a bit late. However, here's one or two things that might be helpful to others finding this question.
You are using ListBox() instead of ListBoxFor() so the data isn't being automatically bound to your model. So in your controller you need:
[HttpPost]
public ActionResult Create(Employee employee, string[] Divisions)
{
// iterate through Divisions and apply to your model
...
}

Related

Nested Tables Using a DTO

I need help getting my WebApi Controller to work.
I have a 3 table Models like this.
First Table
public class MainTable{
public int MainTableID { get; set; }
... Other Fields
public ICollection<SubTable> SubTables { get; set; }
}
Second Table
public class SubTable{
public int SubTableID { get; set; }
... Other Fields
public int MainTableID { get; set; }
[ForeignKey("MainTableID ")]
[JsonIgnore]
public virtual MainTable MainTable{ get; set; }
public ICollection<SubSubTable> SubSubTables { get; set; }
}
Third Table
public class SubSubTable{
public int SubSubTableID { get; set; }
... Other Fields
public int SubTableID { get; set; }
[ForeignKey("SubTableID")]
[JsonIgnore]
public virtual SubTable SubTable{ get; set; }
}
I need to flatten the first model because of other relationships not mentioned in this post so I am using a dto
DTO
public class TableDTO
{
public int MainTableID { get; set; }
... Other Fields (there is a lot of flattening happening here but I am going to skip it to keep this simple)
public ICollection<SubTable> SubTables { get; set; }
}
Now that I got all of that out of the way. To my question.. I am linking this all to a web api controller.
If I use the DTO and create a controller like this
Controller with DTO
public IQueryable<TableDTO> GetMainTable()
{
var mainTable = from b in db.MainTables
.Include(b => b.SubTable.Select(e => e.SubSubTable))
select new TableDTO()
{
MainTableID = b.MainTableID
eager mapping of all the fields,
SubTables = b.SubTables
};
return mainTable;
}
This works for everything except the SubSubTable which returns null. If I ditch the DTO and create a controller like this
Controller without DTO
public IQueryable<MainTable> GetMainTable()
{
return db.MainTables
.Include(c => c.SubTables)
.Include(c => c.SubTables.Select(b => b.SubSubTables));
}
This works perfect and the JSon returns everything I need, except that I lose the DTO which I desperately need for other aspects of my code. I have rewritten my code in every way I can think of but nothing works. I am pretty sure that this can be done with the DTO but I don't know what it would take to make it work, and as they say "You don't know what you don't know" so hopefully someone here knows.
In Entity Framework 6 (and lower), Include is always ignored when the query ends in a projection, because the Include path can't be applied to the end result. Stated differently, Include only works if it can be positioned at the very end of the LINQ statement. (EF-core is more versatile).
This doesn't help you, because you explicitly want to return DTOs. One way to achieve this is to do the projection after you materialize the entities into memory:
var mainTable = from b in db.MainTables
.Include(b => b.SubTable.Select(e => e.SubSubTable))
.AsEnumerable()
select new MessageDTO()
{
MainTableID = b.MainTableID ,
// eager mapping of all the fields,
SubTables = b.SubTables
};
The phrase, "eager mapping of all the fields" suggests that the projection isn't going to narrow down the SELECT clause anyway, so it won't make much of a difference.
Another way could be to load all SubSubTable objects into the context that you know will be in the MainTables you fetch from the database. EF will populate all SubTable.SubSubTables collections by relationship fixup.
If this works:
public IQueryable<MainTable> GetMainTable()
{
return db.MainTables
.Include(c => c.SubTables)
.Include(c => c.SubTables.Select(b => b.SubSubTables));
}
Then use this one and just add a Select() to the end with a ToList(). Note the IEnumerable in the return type:
public IEnumerable<MainTableDto> GetMainTable()
{
return db.MainTables
.Include(c => c.SubTables)
.Include(c => c.SubTables.Select(b => b.SubSubTables))
.Select(c=> new MainTableDto { SubTables=c.SubTables /* map your properties here */ })
.ToList();
}
Not sure about the types though (at one place you have MainTableDto, at another you mention MessageDto?).

Submitting a ViewModel Derived Form in ASP.NET MVC 4

I want to create a form that is comprised of fields that come from separate models and then, when submitted, updates the corresponding tables in the database.
I couldn't find the answer anywhere so I tinkered with things until I made something work. Being new to all of this, however, I doubt the efficacy of my solution. I'm hoping that by stating my problem and showing my solution you can better understand what I need and be able to help me find the best solution for my problem.
I like simple, abstract examples so here is what I currently have working:
First, I make a Color Model...
namespace example.Models
{
public class Color
{
public int ColorID { get; set; }
public string Name { get; set; }
}
}
Then I make a Shape Model...
namespace example.Models
{
public class Shape
{
public int ShapeID { get; set; }
public string Name { get; set; }
}
}
Next, I make a simple ViewModel...
namespace example.ViewModels
{
public class ColorAndShapeViewModel
{
public Color Color { get; set; }
public Shape Shape { get; set; }
}
}
Here is my DbContext...
namespace example.DAL
{
public class MyContext : DbContext
{
public DbSet<Color> Colors { get; set; }
public DbSet<Shape> Shapes { get; set; }
}
}
Next, the Controller...
namespace example.Controllers
{
public class MyController : Controller
{
private MyContext db = new MyContext();
public ActionResult Index()
{
return View();
}
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ColorAndShapeViewModel viewmodel)
{
Color newColor = new Color();
newColor.Name = viewmodel.Color.Name;
Shape newShape = new Shape();
newShape.Name = viewmodel.Shape.Name;
db.Colors.Add(newColor);
db.Shapes.Add(newShape);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
Lastly, the View...
#model example.ViewModels.ColorAndShapeViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.LabelFor(model => model.Color.Name)<br />
#Html.TextBoxFor(model => model.Color.Name)<br />
<br />
#Html.LabelFor(model => model.Shape.Name)<br />
#Html.TextBoxFor(model => model.Shape.Name)<br />
<br />
<input type="submit" value="Create" />
}
Thus far, everything works as expected: I am able to enter data for separate models into a single form and submit it. The database is updated (the color goes into the color table, the shape into the shape table) and all is well—or is it? In reality, I will have a much longer form using several models which are related to each other. My present solution would require a large controller, full of newObject.Property = viewModel.Object.Property statements. Isn't that undesirable? Is there a better way to handle this?
Thanks!

EF code first MVC4 - ArgumentNullException on edit - what am I doing wrong here?

Let's say I have 3 models:
[Table("UserProfile")]
public class UserProfile //this is a standard class from MVC4 Internet template
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public virtual UserProfile UserProfile { get; set; }
}
Now, I'm trying to edit Post
[HttpPost]
public ActionResult Edit(Post post)
{
post.UserProfileId = context.UserProfile.Where(p => p.UserName == User.Identity.Name).Select(p => p.UserId).FirstOrDefault();
//I have to populate post.Category manually
//post.Category = context.Category.Where(p => p.Id == post.CategoryId).Select(p => p).FirstOrDefault();
if (ModelState.IsValid)
{
context.Entry(post.Category).State = EntityState.Modified; //Exception
context.Entry(post.UserProfile).State = EntityState.Modified;
context.Entry(post).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
And I'm getting ArgumentNullException.
Quick look into debug and I can tell that my Category is null, although CategoryId is set to proper value.
That commented out, nasty-looking trick solves this problem, but I suppose it shouldn't be there at all. So the question is how to solve it properly.
I would say it's something with EF lazy-loading, beacuse I have very similar code for adding Post and in debug there is same scenerio: proper CategoryId, Category is null and despite of that EF automagically resolves that Post <-> Category dependency, I don't have to use any additional tricks.
On edit method, EF has some problem with it, but I cannot figure out what I'm doing wrong.
This is working as intended. Your Post object is not attached to the Context, so it has no reason to do any lazy loading. Is this the full code? I don't understand why you need to set Category as Modified since you're not actually changing anything about it.
Anyway, I recommend you query for the existing post from the Database and assign the relevant fields you want to let the user modify, like such:
[HttpPost]
public ActionResult Edit(Post post)
{
var existingPost = context.Posts
.Where(p => p.Id == post.Id)
.SingleOrDetault();
if (existingPost == null)
throw new HttpException(); // Or whatever you wanna do, since the user send you a bad post ID
if (ModelState.IsValid)
{
// Now assign the values the user is allowed to change
existingPost.SomeProperty = post.SomeProperty;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
This way you also make sure that the post the user is trying to edit actually exists. Just because you received some parameters to your Action, doesn't mean they're valid or that the post's Id is real. For example, some ill intended user could decide to edit posts he's not allowed to edit. You need to check for this sort of thing.
UPDATE
On a side note, you can also avoid manually querying for the current user's Id. If you're using Simple Membership you can get the current user's id with WebSecurity.CurrentUserId.
If you're using Forms Authentication you can do Membership.GetUser().ProviderUserKey.

Automapper and lazy loading with EF and performance

I have a model like this
public class Exam
{
public int NewsId { get; set; }
public string Title { get; set; }
public string Description{ get; set; }
public string Program{ get; set; }
}
and a view model like this
public class ExamViewModel
{
public int NewsId { get; set; }
public string Title { get; set; }
}
and I do config Automapper like this
AutoMapper.Mapper.CreateMap<Exam, ExamViewModel>();
and in an ActionResult I used Automapper like this:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel = AutoMapper.Mapper.Map<IEnumerable<Exam>, IEnumerable<ExamViewModel>>(examsDb);
return View(examViewModel);
}
and in view I loop through it
#model IEnumerable<AraParsESOL.ViewModels.ExamViewModel>
<ul>
#foreach (var e in Model)
{
<li>
#Html.ActionLink(e.Title, "Type", "Exam")
</li>
}
</ul>
My problem is that:
As you can see in the Model There are 4 properties but in viewModel there are only 2 properties.
How can i get only those 2 properties in viewModel and not the entire Model?
What happens here is that in view after each loop it goes and get the required column from the database but i want only those 2 properties and not going back to database.
i can get the database like this
db.Exam.ToList();
but it will cause the entire database gets back.
i want to use best practices here?
i know i can get the data from database by anonymouse type and select command but then what is the use of automapper?
what is the best solution here?
Don't use AutoMapper. It's not appropriate for IQueryable<T>. Instead, use LINQ projections:
public ActionResult Exam()
{
var examsDb = db.Exams;
IEnumerable<ExamViewModel> examViewModel =
from e in db.Exams
select new ExamViewModel
{
NewsId = e.NewsId,
Title = e.Title
};
return View(examViewModel);
}
If you look at the generated SQL, you will see that only the NewsId and Title columns are retured. It looks like the AutoMapper folks were interested in addressing this shortcoming, but I haven't heard anything about it since this.

MVC 2 Validation and Entity framework

I have searched like a fool but does not get much smarter for it..
In my project I use Entity Framework 4 and own PoCo classes and I want to use DataAnnotations for validation. No problem there, is how much any time on the Internet about how I do it. However, I feel that it´s best to have my validation in ViewModels instead and not let my views use my POCO classes to display data.
How should I do this smoothly? Since my repositories returns obejekt from my POCO classes I tried to use AutoMapper to get everything to work but when I try to update or change anything in the ModelState.IsValid is false all the time..
My English is really bad, try to show how I am doing today instead:
My POCO
public partial User {
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And my ViewModel
public class UserViewModel {
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Required")]
public string Password { get; set; }
}
Controller:
public ActionResult Edit(int id) {
User user = _userRepository.GetUser(id);
UserViewModel mappedUser = Mapper.Map<User, UserViewModel>(user);
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = mappedUser
};
return View(result);
}
[HttpPost]
public ActionResult Edit(UserViewModel viewModel) {
User user = _userRepository.GetUser(viewModel.Id);
Mapper.Map<UserViewModel, User>(viewModel, user);
if (ModelState.IsValid) {
_userRepository.EditUser(user);
return Redirect("/");
}
AstronomiGuidenModelItem<UserViewModel> result = new AstronomiGuidenModelItem<UserViewModel> {
Item = viewModel
};
return View(result);
}
I've noticed now that my validation is working fine but my values are null when I try send and update the database. I have one main ViewModel that looks like this:
public class AstronomiGuidenModelItem<T> : AstronomiGuidenModel {
public T Item { get; set; }
}
Why r my "UserViewModel viewModel" null then i try to edit?
If the validation is working, then UserViewModel viewModel shouldn't be null... or is it that the client side validation is working but server side isn't?
If that's the case it could be because of the HTML generated.
For instance, if in your view you have:
<%: Html.TextBoxFor(x => x.Item.UserName) %>
The html that gets rendered could possibly be:
<input name="Item.UserName" id="Item_UserName" />
When this gets to binding on the server, it'll need your action parameter to be named the same as the input's prefix (Item). E.g.
public ActionResult Edit(UserViewModel item) {
To get around this, do as above and change your action parameter to item OR you could encapsulate the form into a separate PartialView which takes the UserViewModel as it's model - that way the Html.TextBoxFor won't be rendered with a prefix.
HTHs,
Charles
Ps. If I'm totally off track, could you please post some code for the view.