Entity Framework Code-First Database Not Updating - c#

I'm having issues updating a Boolean from 'false' to 'true' in my database.
I am using Asp.Net MVC5 in Visual Studio 2017, and have created a database (using entity framework code-first) to contain two tables - task and steps (one-to-many relationship).
The index.cshtml page is laid out to list all of the tasks along with their relevant steps, and then each step has a 'mark as completed' button beside it to change the 'completed' field in the steps entity from false to true.
Here is my code:
Steps.cs:
public class Steps
{
[Key]
public int Id { get; set; }
public int StepNumber { get; set; }
public string Description { get; set; }
public ToDoTask Task { get; set; }
public bool Completed { get; set; }
public DateTime? CompletedDate { get; set; }
public Client Client { get; set; }
}
Index.cshtml:
#foreach (var step in ViewData["steps"] as Dictionary<Steps, int>)
{
if (step.Value == task.Id)
{
<p>Step Number: #step.Key.StepNumber</p>
<p>Step Description: #step.Key.Description</p>
using (#Html.BeginForm("MarkStepAsCompleted", "Tasks"))
{
<div class="col-md-2" style="display:none">
#Html.TextBox("Id", #step.Key.Id)
</div>
<button type="submit" >Mark As Completed</button>
}
}
}
TasksController.cs:
[HttpPost]
[AllowAnonymous]
public ActionResult MarkStepAsCompleted(FormCollection form)
{
var completedStepId = Convert.ToInt32(form["Id"]);
var completedStep = db.Steps.Where(s => s.Id == completedStepId).FirstOrDefault();
StepMethods.MarkAsCompleted(completedStep);
return Redirect("Index");
}
StepMethods.cs:
public static void MarkAsCompleted(Steps step)
{
var context = new ApplicationDbContext();
var stepid = step.Id;
context.Steps.Find(stepid);
step.Completed=true;
context.SaveChanges();
}
The application runs well with no errors and when I hit the 'Mark As Completed' button, it redirects to the index page as wanted. But when I check the table in Server Explorer, the value in the 'Completed' column still says false.

You are not updating the entity retrieved from the database, but instead the local instance. Change to this:
public static void MarkAsCompleted(Steps step)
{
using (var context = new ApplicationDbContext())
{
step = context.Steps.Find(step.id); //use the retrieved instance
step.Completed = true;
context.SaveChanges();
}
}
Also, always use the using statement when working with ApplicationDbContext (or, in general, any class that implements the IDisposable interface)

Related

How to map users identity and Auditable properties in view model

This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.

How to update a collection inside an entity within a post action in ASP.NET MVC5?

I have created an ASP.NET MVC5 sample project. I created my entities and from that, scaffolded the controllers for CRUD operations. I can only edit the POD members with the scaffolded code. I want to be able to add/remove related entities.
With my current code, when I click save there is no error but no related entities are modified (POD data is modified though). For example, if I wanted to remove all players from the account, they aren't removed. What am I doing wrong?
How can I remove/add related entities and push those changes to the database?
Here is the form:
Here is the action to update the entity:
public async Task<ActionResult> Edit([Bind(Include = "Account,Account.AccountModelId,Account.Name,Account.CreatedDate,SelectedPlayers")] AccountViewModel_Form vm){
if (ModelState.IsValid){
if (vm.SelectedPlayers != null){
vm.Account.PlayerModels = db.PlayerModels.Where(p => p.AccountModel.AccountModelId == vm.Account.AccountModelId).ToList();
foreach (var player in vm.Account.PlayerModels){
player.AccountModel = null;
db.Entry(player).State = EntityState.Modified;
}
vm.Account.PlayerModels.Clear();
foreach (var player_id in vm.SelectedPlayers){
var player = db.PlayerModels.Where(p => p.PlayerModelId == player_id).First();
vm.Account.PlayerModels.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}
db.Entry(vm.Account).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vm);
}
Here are the models:
public class AccountViewModel_Form{
public AccountModel Account { get; set; }
public HashSet<Int32> SelectedPlayers { get; set; }
public virtual List<PlayerModel> PlayersList { get; set; }
}
public class AccountModel{
public AccountModel(){
PlayerModels = new HashSet<PlayerModel>();
}
public Int32 AccountModelId { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<PlayerModel> PlayerModels { get; set; }
}
public class PlayerModel{
public Int32 PlayerModelId { get; set; }
public float Gold { get; set; }
public string Name { get; set; }
public virtual AccountModel AccountModel { get; set; }
}
I'm basically lost. I can't find any examples in how to update related data. Could someone point me in the right direction?
I come from Symfony (PHP Framework) background. I thought it would be easier but I have been having problems.
Basically I was missing the Attach function and that I had to force the load on the collection to make it work.
I found how to attach a non-attached entity here: Model binding in the controller when form is posted - navigation properties are not loaded automatically
When you post the data, the entity is not attached to the context, and when you try to save changes to a complex entity, the context makes a mess.
The code is a little different because I was trying to make it work at home. But it is essentially the same models.
public ActionResult Edit(AccountEditViewModel vm)
{
if (ModelState.IsValid)
{
//I was missing these 2 important lines...
db.Accounts.Attach(vm.Account);
db.Entry(vm.Account).Collection(a => a.Players).Load();
if (vm.SelectedPlayers != null)
{
foreach (var player in vm.Account.Players.ToList())
{
if (vm.SelectedPlayers.Contains(player.Id) == false)
{
player.Account = null;
vm.Account.Players.Remove(player);
db.Entry(player).State = EntityState.Modified;
vm.SelectedPlayers.Remove(player.Id);
}
}
foreach (var player_id in vm.SelectedPlayers)
{
var player = db.Players.Where(p => p.Id == player_id).First();
player.Account = vm.Account;
vm.Account.Players.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}else
{
vm.Account.Players.Clear();
}
db.Entry(vm.Account).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}

Based on class that has no key defined mvc5

I try to add view from this controller . I only need this view to show data not for insert or update or delete
public ActionResult Index()
{
var CartObj = ShoppingCart.GetCart(this.HttpContext);
var classshop = new New
{
CartItems = CartObj.GetCartItems(),
CartTotal = CartObj.GetSum()
};
return View(classshop);
}
namespace MusicStore.Models
{
public class ShoppingCart
{
MusicStoreEntities dbo = new MusicStoreEntities();
string ShoppingCartID { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase Context)
{
var cart = new ShoppingCart();
cart.ShoppingCartID = cart.GetCardId(Context);
return cart;
}
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public List<Cart> GetCartItems()
{
return dbo.Carts.Where(a => a.CartId == ShoppingCartID).ToList();
}
public decimal? GetSum()
{
decimal? Sum = (from items in dbo.Carts
where items.CartId == ShoppingCartID
select (int)items.Count * items.album.Price).Sum();
return Sum ?? decimal.Zero;
}
}
}
and then I got this error:
there was an error running the selected code generator:
'unable to retrieve metadata for 'Musicstore.Model.new'
one or more validation error were detected during model generation
musicstore,models.New :entity type'New' has no key defined .
define the key of entityType
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MusicStore.Models
{
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
}
There are two options here. First, if this class is mapped to a table in your database, every model in entity framework requires a primary key. Add this into your model:
[Key]
public int Id { get; set; }
This creates a new property called Id and the [Key] attribute makes it a primary key. Technically you don't need the attribute as EF will pick up Id property and use it as a key, but I prefer to be explicit.
Alternatively, if you don't want this class to be a table in your database, add the NotMapped attribute to the class like this:
[NotMapped]
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
I know this is old, but I just ran across this issue.
What happen is when I created a class, CreateEmployeeViewModel, inside the Models folder Visual Studio "smartly" put a line in my DB Context class
public System.Data.Entity.DbSet<eManager.Web.Models.CreateEmployeeViewModel>
CreateEmployeeViewModels { get; set; }
So a table was created on the next update-migration. Removing this line removed the requirement for a key field.
Note: You may also have to add the line AutomaticMigrationDataLossAllowed = true; to your DBMigrationConfiguration Class if the table was created.

Insert one-many relationship data using a ViewModel EF ASP.NET MVC

I am trying to figure out how to insert multiple rows of data using a viewmodel with a single postback. I am using EF and code first approach. The model I an having trouble with has one to many relationship. What I would like to do is that whenever a Person is created, multiple Tasks should be saved to the database along with a Person (different tables).
So far, I am only able to save the very first Task and not all.
My models are:
public class Person
{
public Person()
{
this.Tasks = new List<Task>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
[key]
public int ID { get; set; }
public int PersonID { get; set; }
public string Task { get; set; }
public virtual Person Person { get; set; }
}
ViewModel:
Public class PersonData
{
public Person Person { get; set; }
public Task Task { get; set; }
}
View:
#model Project.ViewModels.PersonData
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Event</legend>
<div>
#Html.TextBoxFor(model => model.Event.Name)
</div>
<div id="taskdiv">
</div>
#*dynamically generated textboxes here*#
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</fieldset>
<script type="text/javascript">
// took few codes out here, but whats happening here is based on int value textboxes for Task appears
for (var i = 1; i <= $count; i++) {
$('#taskdiv').append('<div><label>Task #' + i + ' </label>#Html.TextBoxFor(model => model.Task.Task)</div>');
}
Controller:
public class PersonController : Controller
{
private Context db = new DBContext();
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(ViewModels.PersonData personData)
{
if (ModelState.IsValid)
{
db.Person.Add(eventData.Person);
db.Tasks.Add(eventData.Task);
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
At least I know that tasks are being passed. I tried few things like
foreach (char c in personData.Task.Task.AsEnumerable)
{ count += 1}
and received the correct amount of number. I thought using ViewModels are relatively common, but I didn't see what I would like to do in any tutorials or forums....
My goal is to be able to save multiple tasks per person with one postback. Any advice will be appreciated!
In your ViewModel you only have one Task, should be a list of tasks right?
When I do stuff like this I usually save the Person object first then add tasks to it using ajax.
The Method could look something like this.
public void SaveTasks(List<Task> tasks){
//important that tasks already have personId --set on client side
foreach(var task in Tasks){
//do some validation
db.Tasks.Add(task);
}
db.SaveChanges();
}

Include() in LINQ to Entities query

I have the following models in my ASP.NET MVC 3 project:
public class Task
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public TaskStatus Status { get; set; }
}
public class TaskStatus
{
public int Id { get; set; }
public string Description { get; set; }
}
For reference, here is my DbContext class:
public class TaskManagerSets : DbContext
{
public DbSet<Task> TaskSet { get; set; }
public DbSet<TaskStatus> TaskStatusSet { get; set; }
}
Then I have a List Action in my TaskController:
TaskManagerSets dbcontext = new TaskManagerSets();
public ActionResult List()
{
var tasks = from tsk in dbcontext.TaskSet.Include("TaskStatusSet")
select tsk;
return View(tasks.ToList());
}
Finally I have the Task List View:
#model IEnumerable<TaskManager.Models.Task>
<ul>
#foreach (var tsk in Model)
{
<li>#tsk.Id | #tsk.CreatedOn | #tsk.Status.Description</li>
}
</ul>
When I execute my project I get the following error:
A specified Include path is not valid. The EntityType 'CodeFirstNamespace.Task' does not declare a navigation property with the name 'TaskStatus'.
The problem is definitely on the Include("TaskStatusSet") but how should I fix this?
The navigation property name in your Task class is Status. So, you would have to use:
var tasks = from tsk in dbcontext.TaskSet.Include("Status")
select tsk;
But since you are working with the DbContext API a better option is to use the type-safe overload of Include:
using System.Data.Entity;
// You must add a using statement for this namespace to have the following
// lambda version of Include available
//...
var tasks = from tsk in dbcontext.TaskSet.Include(t => t.Status)
select tsk;
You will get Intellisense and compile-time checks which helps to avoid issues with wrong strings like you had.

Categories