Create Complex Object WebApi - c#

Role Entity Of My Project has Multiple Action With This Modeling:
{
public string Uid { get; set; }
public string Descript { get; set; }
public bool Premitive { get; set; }
public virtual ICollection<ActionDto> Actions { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
I use UnitOfWork Repository Pattern
The create method is:
public async Task<IHttpActionResult> Create([FromBody] RoleFullDto dto)
{
try
{
if (dto.Actions == null || dto.Actions.Count <= 0)
return BadRequest();
//If I Pass Only action uid, return EntityModelException
//When I Pass Complete Entity, Create New Action
//foreach (var action in dto.Actions)
//{
//var act = UnitOfWork.ActionRepository.Get(action.Uid);
//action.ActionName = act.ActionName;
//action.MenuId = act.MenuId;
//action.PersianActionName = act.PersianActionName;
//}
var role = ModelFactory.GetRole(dto);
if (role == null)
return Content(HttpStatusCode.InternalServerError, dto);
var result = await this.AppRoleManager.CreateAsync(role);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return ActionResult<Role>(role, null, "CreateRole", HttpStatusCode.Created);
}
catch (Exception e)
{
Console.WriteLine(e);
return InternalServerError();
}
}
I pass this parameters To create new role:
{
Name: "TestRole1",
Descript: "This is Test",
Premitive: false,
Actions: [{uid:1},{uid:2},{uid:3}]
}
but this method wand to add 3 new Action that Exist in Databese
How Can I Create Role With Exist Actions?

Since you are passing entities that doesn't come from the db, Entity Framework tries to create them. You could use the Attach method of DBSet to link you action objects to entities. But since you're not using a DBSet directly you could also do something like this :
dto.Actions = dto.Actions.Select(a => UnitOfWork.ActionRepository.Get(a.Uid)).ToList();

Related

C# Updating model using HttpPost

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.

Entity Framework Core deletes entities during update

I have a problem concerning entities in ASP.NET Core.
I use Entity Framework Core as data access library.
The issue I've come across happens when I'm trying to update an entity. After I modify the properties and call SaveChanges, the entity gets deleted and I don't understand why.
Here's the entity:
public class Contract
{
public int Id { get; set; }
[Required]
public DateTime ExpiryDate { get; set; }
[Required]
[Range(0, float.MaxValue)]
public float MonthlyFee { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int CarId { get; set; }
public User User { get; set; }
public Car Car { get; set; }
}
Here's the related entities for reference:
public class User : IdentityUser
{
[Required]
[PersonalData]
public string Name { get; set; }
[Required]
[PersonalData]
public string Surname { get; set; }
[Required]
[PersonalData]
public string TaxCode { get; set; }
[Required]
[PersonalData]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
public string ProfilePictureUrl { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
}
public class Car
{
public int Id { get; set; }
[Required]
[RegularExpression("[A-Z][A-Z][0-9][0-9][0-9][A-Z][A-Z]")]
public string LicensePlate { get; set; }
public int CarModelId { get; set; }
public string FittingDescription { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
public CarModel CarModel { get; set; }
}
Here's my update method in repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.User = entity.User;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Has anyone got any idea how to solve this?
UPDATE:
This is the new Update method:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Here's the Fluent API configuration:
private void _configureUsers(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Contract)
.WithOne(c => c.User)
.HasForeignKey<Contract>(c => c.UserId);
}
private void _configureCars(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate);
modelBuilder.Entity<Car>()
.HasOne(c => c.Contract)
.WithOne(c => c.Car)
.HasForeignKey<Contract>(c => c.CarId);
}
Both this methods get called in the OnModelCreating method of the context.
I've finally managed to solve my issue.
I was already tracking the entity in my api controller like that:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
var contract = await _contractService.GetContractDTO(id);
if (contract == null)
return NotFound();
var modifiedContract = await _contractService.UpdateContract(viewModel);
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
}
This type of approach works in one to many relationships, but evidently when you have one to one relationship and you have to objects that rapresent the same entity the ChangeTracker cannot track the changes correctly.
I post my new controller and repository code if someone will burst into my same problem.
Controller:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
ContractDTO modifiedContract;
try
{
modifiedContract = await _contractService.UpdateContract(viewModel);
}
catch (EntityNotFoundException)
{
return NotFound();
}
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
Service:
public async Task<ContractDTO> UpdateContract(PutContractViewModel viewModel)
{
try
{
return await ParseContractToContractDTO(await _contractRepository.Update(ParsePutContractViewModelToContract(viewModel)));
}
catch(EntityNotFoundException)
{
throw;
}
}
Repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await _context.Contracts.Include(c => c.User).Include(c => c.Car).FirstOrDefaultAsync(c => c.Id == entity.Id);
if (dbContract == null)
{
throw new EntityNotFoundException();
}
var dbUser = await _context.Users.Include(u => u.Contract).FirstOrDefaultAsync(u => u.Id == entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _context.Cars.Include(c => c.Contract).FirstOrDefaultAsync(c => c.Id == entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.MonthlyFee = entity.MonthlyFee;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
I want to thank you all, you've been very helpful and patient with me.

Returning ViewModel works fine in EF query returning list of entities but not while returning single entity

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)
{
...
}

How to add/update many to many relations with one end of entity in EF core

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

"Edit" action, save old data

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);
}

Categories

Resources