Entity Framework: ViewModel to Domain Model - asp.net-mvc-3

I'm building an MVC 3 website. I have a model looking like this:
public class Survey
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
// Not in view
public DateTime DateCreated { get; set; }
// Not in view
public DateTime DateModified { get; set; }
}
Based on this I also have a View Model to edit the survey information:
public class SurveyEditViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
}
When the user finishes editing I would like to persist the changes. Here's my controller post action:
[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Map the view model to a domain model using AutoMapper
Survey survey = Mapper.Map<SurveyEditViewModel, Survey>(model);
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}
In my repository (it's a generic one for now) I have the following code:
public void Update(E entity)
{
using (ABCDataContext context = new ABCDataContext())
{
context.Entry(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
When this executes I get the following error: "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
I guess this was to be expected. Mapping from the view model to the model doesn't give me a complete Survey object.
I could modify my controller to look like this. And then it works:
[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Map the model to a real survey
survey = _repository.Find(model.Id);
survey.Name = model.Name;
survey.Description = model.Description;
survey.DateStart = model.DateStart;
survey.DateEnd = model.DateEnd;
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}
But I was wondering if a better way is available?

[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Fetch the domain model to update
var survey = _repository.Find(model.Id);
// Map only the properties that are present in the view model
// and keep the other domain properties intact
Mapper.Map<SurveyEditViewModel, Survey>(model, survey);
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}

Related

Creating an object with other object properties from DbContext in MVC EF Core

I am sorry if the answer to this question is so obvious, but all (and I really mean all) of my Google search results are purple, regardless of the search phrase I try to wrap around my question.
I am trying to build an MVC .NET Core 3.0 application with Code-First.
I am able to create my models, have these setup correct (I think) in my Database (Azure SQL), and using Visual Studio's standard templates to create controllers and views. I am therefore able to create each one of my model individually.
What I am trying to do is
Create a view from where I can create a Rental-object, with link to RentalOwner and ParkingSpot.
(Create a view from where I can see all rentals created. This is not in scope for this question)
My models
public class ParkingSpot
{
public int ParkingSpotId { get; set; }
public int ParkingSpotNumber { get; set; }
}
public class RentalOwner
{
public int RentalOwnerId { get; set; }
public int TenantId { get; set; }
public Tenant Tenant { get; set; }
}
public class Tenant
{
public int TenantId { get; set; }
[Required]
[Display(Name="Navn")]
public string Name { get; set; }
[EmailAddress]
[Required]
public string Email { get; set; }
}
public class Rental
{
public int RentalId { get; set; }
public int ParkingSpotId { get; set; }
public ParkingSpot ParkingSpot { get; set; }
public int RentalOwnerId { get; set; }
public RentalOwner RentalOwner { get; set; }
}
I have tried creating a ViewModel, to use for creation of the Rental-model and the binding to other models.
public class RentalCreationView
{
public int Id { get; set; }
public Rental Rental { get; set; }
public int ParkingSpotId { get; set; }
public int TenantId { get; set; }
}
I've tried with this GET and HttpPost POST Actions in my Controller.
// GET: Rentals/Create
public async Task<IActionResult> CreateAsync()
{
var parkingSpots = await _context.ParkingSpots.ToListAsync();
ViewData["ParkingSpots"] = new SelectList(parkingSpots, "ParkingSpotId", "ParkingSpotNumber");
var tenants = await _context.Tenants.ToListAsync();
ViewData["Tenants"] = new SelectList(tenants, "TenantId", "Name");
return View();
}
public async Task<IActionResult> Create(RentalCreationView rcw)
{
if (ModelState.IsValid)
{
_context.RentalOwners.Add(rcw.Rental.RentalOwner);
_context.Rentals.Add(rcw.Rental);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(rcw);
}
I don't think my issue is with my View, and I guess I am missing something in either my models or in my controller.
Right now, when I try to create a Rental, the view is just loading for a couple of minutes after submit.
I have seen numerous of tutorials, read hundreds of questions and articles, and I know that I am missing some basic steps, but the code above is what I have right now (I have about 500 lines of commented out code that doesn't work).
I've spent 2 weeks trying to learn how to do this but I need some help from someone who knows what I'm trying to do.
Any relevant links, videos or documentation would be awesome.. I am really stuck deep.
Thank you
EDIT: I have found a solution, and to all future readers who got this page from their 1000th Google search, what I did was change a bit in the models and add this HttpPost Create in my Controller.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RentalCreationView rcw)
{
List<ParkingSpot> parkingSpots = _context.ParkingSpots.ToList();
List<Tenant> tenants = _context.Tenants.ToList();
RentalOwner ro = new RentalOwner
{
Tenant = tenants.Find(t => t.TenantId == rcw.TenantId),
OwnerSince = rcw.StartDate
};
Rental ren = new Rental
{
StartDate = rcw.StartDate,
ParkingSpot = parkingSpots.Find(ps => ps.ParkingSpotId == rcw.ParkingSpotId),
RentalOwner = ro
};
if (ModelState.IsValid)
{
_context.RentalOwners.Add(ro);
_context.Rentals.Add(ren);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
{
var errors = ModelState.Select(x => x.Value.Errors)
.Where(y => y.Count > 0)
.ToList();
}
return View();
}

Reference built-in Account UserId Assign to Model as Foreign Key

after looking for an answer in the already existing questions, I am still a little confused as how I should proceed. I am new to the MVC 3 framework, so if I come off sounding like a dope, I appologize!!
Ok, so I created a MVC 3 internet application, created 3 new Users administrator, user1, and user2. I have created a new model, and controller for my "Posts" I am able to add, edit and delete the items. I currently have a column called UserID in my posts table. I would like this to be automagically populated with the current UsersID. I think I would define this in the controller like this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
public int User = System.Web.Security.Membership.GetUser(id);
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
Inside the model, this is what I currently have:
public class Post
{
public int PostID { get; set; }
public int UserID { get; set; }
public string PostTitle { get; set; }
public int PostType { get; set; }
public string PostBody { get; set; }
public string PostBlogTitle { get; set; }
public string PostBlogURL { get; set; }
public string PostCategory { get; set; }
public string PostSEO { get; set; }
public int PostStatus { get; set; }
}
public class PostDBContext : DbContext
{
public DbSet<Post> Posts { get; set; }
}
I would like to replace public int UserID { get; set; } with my newly defined variable in the controller, but not sure where/how to add it.
Not sure I'm clear on what you're trying to achieve. Are you just trying to assign the current user to the model, save it and then pass it back to the view?
If so you can just do this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
// Note: I'm assuming this actually works/returns an int
public int User = System.Web.Security.Membership.GetUser(id);
post.UserID = User;
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}

Clearing children deletes parent

I have three related tables. Calendar 1...* CalendarUser *...1 User. When I have edited the CalendarUsers in the edit calendar view I then post the ViewModel back to the controller. Here is my controller code:
[HttpPost]
public ActionResult Edit(int id, CreateCalendarViewModel cvm)
{
long userId = long.Parse(User.Identity.Name);
db.Calendars.Attach(cvm.CurrentCalendar);
cvm.Users= DbExtensions.GetUserList(userId);
if (ModelState.IsValid)
{
////Remove the deselected users
cvm.CurrentCalendar.CalendarUsers.Clear();
//Get the names from the selected users
var selectedUsers = from u in cvm.Users
where cvm.SelectedUsers.Contains(u.Key)
select new KeyValuePair<long, string>(long.Parse(u.Key), u.Value);
foreach (var selectedUser in selectedUsers)
{
User user = db.Users.Find(selectedUser.Key);
//If usr does not exist create a new
if (user == null)
{
db.Users.Add(new User
{
UserId = selectedUser.Key,
Name = selectedUser.Value,
Expires = DateTime.Now,
AccessToken = string.Empty
});
}
//Add the binding to the calendar
cvm.CurrentCalendar.CalendarUsers.Add(new CalendarUser
{
CalendarId = cvm.CurrentCalendar.CalendarId,
UserId = selectedUser.Key
});
}
db.Entry(cvm.CurrentCalendar).State = EntityState.Modified;
db.SaveChanges();
}
return View(cvm);
}
Here are my classes:
public partial class Calendar
{
public Calendar()
{
this.CalendarUsers = new HashSet<CalendarUser>();
}
public int CalendarId { get; set; }
public string CalendarTitle { get; set; }
public string CalendarDescription { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<CalendarUser> CalendarUsers { get; set; }
}
public partial class CalendarUser
{
public int CalendarUserId { get; set; }
public int CalendarId { get; set; }
public long UserId { get; set; }
public Nullable<bool> IsAdmin { get; set; }
public virtual Calendar Calendar { get; set; }
public virtual User User { get; set; }
}
public partial class User
{
public User()
{
this.Calendars = new HashSet<Calendar>();
this.CalendarUsers = new HashSet<CalendarUser>();
}
public long UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Calendar> Calendars { get; set; }
public virtual ICollection<CalendarUser> CalendarUsers { get; set; }
}
For some reason when i save the changes the calendar is being deleted as well? I've searched a bit but noone seem to have the same problem? Am I doing it wrong? Is there a better way to update/remove related entities?
It seems that I forgot to include a hidden field in in the view containing the id of the user and the result was that when I updated the calendar it saved with Id = 0 and thus hid the objects in the view for the specified user. Mental note: Always verify in the database what is really happening.
I also need to look into whats happening when I send objects back and forth between views and controller. Sometimes it seems to manage by itself and sometimes I need to specify all the fields myself.

asp.net mvc3 UpdateModel exclude properties is not working

I have a class, which has 8 props / 8 columns in DB. But on a Edit page, i dont want to show the AddedDate or UserID field, since i dont want user to change it.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit(Voucher voucher)
{
if (ModelState.IsValid)
{
string[] excludeProperties = { "AddedDate", "UserID" };
UpdateModel(ModelState, "", null, excludeProperties);
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, once i click on submit, i got the following error: System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the view model and triggered the error.
Would you please let me know how to fix it? Thanks!
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
no luck either
You are still passing in Voucher which could contain that field in it. I'm not sure what you are trying to accomplish with the UpdateModel here if you are already passing in a Voucher object?
Pass in Voucher, set it to modified and save it. If you want to use whats in the database then you'll have to
Load the object from the database
UpdateModel and exclude the properties
Save your entity.
You could simply use a View Model and post that.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
and then load up your object from the db":
var voucher = db.Vouchers.Where(o=>o.ID==voucherViewModel.Id);
//manually copy the fields here then save it
//copy
db.SaveChanges();

asp.net mvc 3 entity framework, passing model info in Get request of create action

I'm having trouble passing view information from my Get/Create action to my view. Here are my three model classes;
public class Competition
{
public int Id { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Prize { get; set; }
}
public class CompetitionEntry
{
public int Id { get; set; }
public int CompetitionEntryId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompetitionId { get; set; }
}
public class CompetitionEntryViewModel
{
public int Id { get; set; }
public Competition Competitions { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Here is my Get/Create action in CompetitionEntry Controller;
public ActionResult Create(int id)
{
CompetitionEntryViewModel competitionentryviewmodel = db.CompetitionEntriesView.Find(id);
return View(competitionentryviewmodel);
}
I know this doesn't work. The id parameter goes into the URL fine. How to I get access to my Competition class in th Get action? I need to be able to show the competion name on my Create Competition entry view.
Thanks in advance!
public ActionResult Create(int id)
{
var data = db.CompetitionEntriesView.Find(id);
CompetitionEntryViewModel competitionentryviewmodel = new CompetitionEntryViewModel();
competitionentryviewmodel.CompetitionName = data.Name;
return View(competitionentryviewmodel);
}
What you are trying to do is build an object graph and display it through a view model. In order to do this, you need to map your domain model(s) to your view model.
You can do the mapping yourself by writing a lot of code (re-inventing the wheel), or, you could consider using third party tools to do this for you. I recommend you use an AutoMapper as it is very simple to use imo.
The other problem is that your view model contains a domain model. This is likely to cause you a lot of headache in near future. If I were you, I would replace Competition with CompetitionViewModel.
I would also consider creating a view model for a list of competitions, i.e. CompetitionsViewModel. Look into partial views to see how you can display a list of competitions.
Good luck

Resources