How i can save old data in action [post]Edit?
I have these two models:
public class Task
{
public int ID { get; set; }
public int sheet_length { get; set; }
public int sheets_num { get; set; }
[Required]
public int spoolID { get; set; }
[ForeignKey("spoolID")]
public virtual Spool Spool { get; set; }
}
public class Spool
{
public int ID { get; set; }
public string name { get; set; }
public int weight { get; set; }
public int weight_meter { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
When I create Task - Spool.Weight changes
Task.Spool.Weight = Task.Spool.Weight - (Task.sheet_length * Task.sheets_num * Task.Spool.weight_meter)
When I edit a task - I want to do this:
TaskOld.Spool.Weight = TaskOld.Spool.Weight + (TaskOld.sheet_length * TaskOld.sheets_num * TaskOld.Spool.weight_meter)
Task.Spool.Weight = Task.Spool.Weight - (Task.sheet_length * Task.sheets_num * Task.Spool.weight_meter)
But when I try to get old data in edit action
[HttpPost]
public ActionResult Edit(Task task)
{
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
////some code
db.Tasks.Attach(task);
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
///some code }
I get an ObjectStateManager error (it has object with the same key)
The problem occurs when you attach the new task.
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
////some code
// Error here! db.Tasks already contains something for the Id
// Can't have two tasks with the same Id. Attach doesn't update the
// existing record, but adds the 'task' to the object graph for tracking.
db.Tasks.Attach(task);
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
Both have the same Id, and the ObjectStateManager can't track two of the same object with the same Id.
A more normal approach is to map the Task input to taskOld:
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
// ... Some code ...
// taskOld is already attached to the DbContext, so just map the updated
// properties.
taskOld.Property1 = task.Property1;
taskOld.Property2 = task.Property2;
...
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
Or, instead of assigning properties individually, you can call UpdateModel(taskOld) and it will attempt to do the same thing.
You can following Leniency code, or:
[HttpPost]
public ActionResult Edit(Task t)
{
if (ModelState.IsValid)
{
db.Entry(t).State = EntityState.Modified;
db.SaveChanges();
}
return View(t);
}
Related
I started learning C# and I want to update my model using the [HttpPost] annotation. I tried removing the [FromBody]Item itm parameter and the fields on the repository but it's not working either. Below is my code.
Controller:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem([FromBody]Item itm, int id)
{
var getItem = _repository.GetItemById(id);
if (getItem == null)
{
return NotFound();
}
_repository.UpdateItem(itm);
_repository.SaveChanges();
return Ok(getItem);
}
Repository:
public void UpdateItem(Item itm)
{
if (itm == null)
{
throw new ArgumentNullException(nameof(itm));
}
var itemToUpdate = this.GetItemById(itm.Id);
if (itm.Name != null)
{
itemToUpdate.Name = itm.Name;
}
itemToUpdate.Price = itm.Price;
itemToUpdate.Condition = itm.Condition;
itemToUpdate.Size = itm.Size;
itemToUpdate.DateSold = itm.DateSold;
itemToUpdate.SellMethod = itm.SellMethod;
_context.Items.Update(itemToUpdate);
}
Interface:
void UpdateItem(Item itm);
Model:
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Price { get; set; }
public string Condition { get; set; }
public string Size { get; set; }
public string DateSold { get; set; }
public string SellMethod { get; set; }
First of all verify that you're sending that item correctly:
Is the form correct and pointing to that method of your controller?
Are you sending that item via the form (have you used the provided methods for this) ?
After that, if you're sending the item in the body of your post request, then verify the item in the method's parameter is available.
EDIT:
Well, as already discussed with Panagiotis you should rather directly use the DbContext itself as it already provides everything you need.
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var foundItem = _dbContext.Items.SingleOrDefault(x => x.Id == id);
if (foundItem == null)
{
return NotFound();
}
foundItem.Name = itemData.Name;
foundItem.Size = itemData.Size;
// and so on
_dbContext.SaveChanges();
return Ok(foundItem);
}
Another way to keep your current structure, but it's not recommended, would be the following:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var updatedItem = _repository.UpdateItem(id, itemData);
if (updatedItem == null)
{
return NotFound();
}
return Ok(updatedItem);
}
public void UpdateItem(int id, Item itemData)
{
// you can validate parameters and throw errors if e.g. itemData == null
var originalItem = GetItemById(id); // this should internally get the item e.g. _dbContext.Items.Where(x => x.id == itemData.Id);
if(originalItem == null)
{
return null;
}
originalItem.Name = itemData.Name;
originalItem.Price = itemData.Price;
originalItem.Condition = itemData.Condition;
originalItem.Size = itemData.Size;
originalItem.DateSold = itemData.DateSold;
originalItem.SellMethod = itemData.SellMethod;
SaveChanges(); // guess this will be _dbContext.SaveChanges() instead
return originalItem;
}
Well, you could also change it to first load the item and then pass the originalItem and the itemData into the UpdateItem method inside your repository. But as you see the better way to directly use the DbContext is more clearer and shorter.
I have a method in my Asp.Net Core Api for insert new entity which get a ViewModel as an argument and returns another ViewModel as result.
I have added Set() method as one of its properties to return list of CreditUserViewModel and it's working fine.
public async Task<IEnumerable<CreditUserViewModel>> GetAll()
{
return await Task.Run(() =>
_context.Credit
.Select(CreditUserViewModel.Set));
}
But while I want to return single object, I have no access to Set() method. Why?
Result ViewModel :
public class CreditUserViewModel
{
public string PlanTitle { get; set; }
public string CardSerial { get; set; }
public DateTime PurchaseDate { get; set; }
public int ValidationDays { get; set; }
public int PaidAmount { get; set; }
public string PurchaseCode { get; set; }
public static CreditUserViewModel Set(Credit credit)
{
return new CreditUserViewModel
{
PlanTitle = credit.CreditPlan.Title,
CardSerial = credit.Card.SerialNumber,
PurchaseDate = DateTime.UtcNow,
ValidationDays = credit.CreditPlan.ValidationDays,
PaidAmount = credit.CreditPlan.Cost,
PurchaseCode = credit.PurchaseCode
};
}
}
Add() method which I expect to return single CreditUserViewModel:
public async Task<CreditUserViewModel> Add(CreditRegisterViewModel credit)
{
var newCredit = new Credit
{
CardId = credit.CardId,
CreditPlanId = credit.CreditPlanId,
PurchaseCode = credit.PurchaseCode,
RechargeDate = DateTime.UtcNow
};
_context.Entry(newCredit).State = EntityState.Added;
try
{
await _context.SaveChangesAsync();
}
catch
{
throw new Exception();
}
return new CreditUserViewModel.Set(newCredit); **// Error is in this line: The type name Set does not exist in the type CreditUserViewModel**
}
Error message : The type name Set does not exist in the type
CreditUserViewModel
Shouldn't that be something like:
public async Task<IEnumerable<CreditUserViewModel>> GetAll()
{
var data = _context.Credit
.Select( c => CreditUserViewModel.Set(c) )
.ToList();
return Task.FromResult(data);
}
And this
return new CreditUserViewModel.Set(newCredit);
should be
return CreditUserViewModel.Set(newCredit);
Since CreditUserViewModel.Set is a method, not a type, and by convention would normally be called Create as it's in lieu of a constructor. Or implemented as an Extension method called ToViewModel() targeting the entity type, something like:
public static CreditUserViewModel ToViewModel(this CreditUser entity)
{
...
}
We have visited quite a few links on EF Core many to many update, yet could not figure a concrete answer to our question and clear our understanding.
Scenario:
We wish to add/update an entity and its related many to many relations in one go like (dbset.Add() or dbset.Update())
We were trying the following and could only add/update the parent entity and not the many-to-many relation list. Can you help us know where we are wrong? and what can be done?
Current Model Structure:
public class Teacher
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Required]
public long Id { get; set; }
public string Name { get; set; }
public List<TeacherDuty> TeacherDuties { get; set; }
}
public class Duty
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Required]
public long Id { get; set; }
public string Description { get; set; }
public List<TeacherDuty> TeacherDuties { get; set; }
}
public class TeacherDuty
{
public long TeacherId { get; set; }
public Teacher Teacher { get; set; }
public long DutyId { get; set; }
public Duty Duty { get; set; }
}
And we are trying to add/update using following methods:
public async Task<Teacher> AddTeacher(Teacher pTeacher)
{
try
{
return await _teacher.AddAsync(pTeacher);
}
catch (Exception ex) { throw ex; }
}
public async Task<Teacher> UpdateTeacher(Teacher pTeacher)
{
try
{
return await _teacher.Update(pTeacher);
}
catch (Exception ex) { throw ex; }
}
Kindly point us to our misinterpretation of concept and solution if possible.
Thanks.
I create a demo to add and edit a teacher.(_context is database context)
Add a teacher:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Teacher teacher)
{
//get your desired dutyId with your own logic
var SelectedDutyIds = new int[] { 1 };
var teacherDuties = new List<TeacherDuty>();
if (ModelState.IsValid)
{
_context.Add(teacher);
await _context.SaveChangesAsync();
foreach (var id in SelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = id,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(teacher);
}
Edit the teacher: remove all the existing TeacherDuties of the teacher firstly and then add new ones.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, Teacher teacher)
{
if (id != teacher.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
//your new dutyIds
var newSelectedDutyIds = new int[] { 3 };
var teacherDuties = new List<TeacherDuty>();
var tdList = await _context.TeacherDuties.Where(td => td.TeacherId == teacher.Id).ToListAsync() ;
_context.RemoveRange(tdList);
foreach (var newid in newSelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = newid,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
_context.Update(teacher);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TeacherExists(teacher.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(teacher);
}
Refer to Entity framework core update many to many
I'm trying to save collections with part common data for all and different part for each collection. I wrote hard coded way to save one collection and I don't know how I can do it for multiple collections. So this is what I have:
Controller:
[HttpPost]
public ActionResult MakeComplaint(CommonData model)
{
if (ModelState.IsValid)
{
model.CreateComplaint();
return RedirectToAction("Index");
}
}
Model:
public class CommonData
{
public string Data1 { get; set; }
public string Data2 { get; set; }
public List<DifferentData> Differents { get; set; }
SecondModel DataContainer()
{ //help needed here
SecondModel tmp = new SecondModel();
tmp.ID = Differents[0].ID; //how can I change it to save more collections?
tmp.Name = Differents[0].Name;
tmp.Data1 = Data1;
tmp.Data2 = Data2;
return tmp;
}
public int CreateComplaint()
{
var complaint = DataContainer();
db.AddComplaint(complaint);
db.Save();
}
}
public class DifferentData
{
public int ID { get; set; }
public string Name { get; set; }
}
How should my DataContainer() look in order to handle Differents[1] and more?
EDIT:
My DataContainer() is a list of objects now, so 1 problem solved
List<SecondModel> DataContainer()
{
var listOfComplaints = new List<SecondModel>;
for(int i = 0; i<Differents.Count; i++){
SecondModel tmp = new SecondModel();
tmp.ID = Differents[i].ID;
tmp.Name = Differents[i].Name;
tmp.Data1 = Data1;
tmp.Data2 = Data2;
listOfComplaints.Add(tmp)
}
return listOfComplanints;
}
EDIT2:
Working solution:
public void AddComplaint(List<SecondModel> model){
db.SecondModels.InsertAllOnSubmit(model) //saving multiple objects
}
Usually you get new Models from the page. Not as a DataContainer but as a SecondModel or another specific model. Your DataContainer and nested classes in CommonData is probably adding confusion. You can actually remove all of that, and use the code you have in AddComplaint directly from your controller like this:
[HttpPost]
public ActionResult MakeComplaint(SecondModel model)
{
if (ModelState.IsValid)
{
db.SecondModels.InsertOnSubmit(model)
return RedirectToAction("Index");
}
}
and just do something similar for another model:
[HttpPost]
public ActionResult MakeSomethingElse(ThirdModel model)
{
if (ModelState.IsValid)
{
db.ThirdModels.InsertOnSubmit(model)
return RedirectToAction("Index");
}
}
I have Book and each book can have multiple Chapter. Each of the Chapter has a single audio file. How can I update single row of chapter sub entity?
This is my model:
public class Book
{
public Book()
{
this.Chapters = new List<Chapter>();
}
[Key]
public Int64 ISBN { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BookID { get; set; }
public string bookName { get; set; }
public string bookAuthor { get; set; }
[DataType(DataType.Currency)]
public decimal bookPrice { get; set; }
public virtual ICollection<Chapter> Chapters { get; set; }
}
public class Chapter
{
public int ChapterId { get; set; }
public string chapterName { get; set; }
[ForeignKey("Book")]
public Int64 ISBN { get; set; }
public virtual Book Book { get; set; }
}
In Create ActionResult I add new Chapter by create new object from Chapter model and add it using book.Chapters.Add()
for (int i = 1; i < Request.Files.Count; i++)
{
var mfile = Request.Files[i];
if (mfile != null && mfile.ContentLength > 0)
{
var fileName = Path.GetFileNameWithoutExtension(mfile.FileName);
Chapter _bChapter = new Chapter()
{
chapterName = fileName,
chapterLink = BookDir + mfile.FileName
};
book.Chapters.Add(_bChapter);
mfile.SaveAs(Server.MapPath(_bChapter.chapterLink));
}
}
in Edit ActionResult I get new audio using HttpPostedFileBase
public ActionResult Edit(Book book, IEnumerable<HttpPostedFileBase> file)
{
if (ModelState.IsValid)
{
db.Entry(book).State = EntityState.Modified;
//do some File operation to save audio in server folder
db.SaveChanges();
return RedirectToAction("Index");
}
return View(book);
}
But I dont know how to save new uploaded audio to chapter sub entity
You can update Chapter, first find related chapter by using SingleOrDefault method, next edit chapterName property and call SaveChanges() method, like this:
using (var context = new YourDbContext())
{
var result = context.Chapter.SingleOrDefault(b => b.ISBN == book.ISBN);
if (result != null)
{
result.chapterName = #"/root/my/audios";
context.SaveChanges();
}
}
EDIT:
using (var context = new YourDbContext())
{
var result = context.Chapter.Where(b => b.ISBN == book.ISBN).ToList();
if (result.Any())
{
foreach(var chapter in result)
{
chapter.chapterName = #"/root/my/audios";
}
context.SaveChanges();
}
}