C
C#•3w ago
YourMajesty

Creates new object instead of updating it

Hello, guys, I have a very simple MVC WebApp and I faced a problem. So thing is for some reason my edit method creates new instance in database instead of updating given object. From what I can tell issue is within my html form, because if I add input field for Id there it works fine.
40 Replies
Angius
Angius•3w ago
Show code My bet's you're sending a whole database entity to the backend, instead of using a DTO And EF interprets the empty ID as your desire to insert a new entity
YourMajesty
YourMajestyOP•3w ago
uff hold on discord blames my message for being too long
<form method="post">
<div class="border p-3 mt-4">
<div class="row pb-2">
<h2>Edit a movie</h2>
<hr>
</div>
<div hidden class="mb-3 p-1">
<label class="p-0">Id</label>
<input asp-for="Movie.Id" type="number" class="form-control">
</div>
<div class="mb-3 p-1">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label class="p-0">Category Name</label>
<input asp-for="Movie.Title" class="form-control">
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<select asp-for="Movie.CategoryId" class="form-control">
@foreach (var item in Model.Categories)
{
<option value="@item.Id">@item.Name</option>
}
</select>
<span asp-validation-for="Movie.CategoryId" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="Movie.ReleaseDate" type="date" class="form-control">
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="Movie.Rating" type="number" class="form-control">
<span asp-validation-for="Movie.Rating" class="text-danger"></span>
</div>
<div class="row">
<div class="col-6">
<button type="submit" class="btn btn-light form-control">Submit</button>
</div>
<div class="col-6">
<a asp-controller="Movie" asp-action="Index" class="btn btn-outline-primary form-control">Go back</a>
</div>
</div>
</div>
</form>
<form method="post">
<div class="border p-3 mt-4">
<div class="row pb-2">
<h2>Edit a movie</h2>
<hr>
</div>
<div hidden class="mb-3 p-1">
<label class="p-0">Id</label>
<input asp-for="Movie.Id" type="number" class="form-control">
</div>
<div class="mb-3 p-1">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label class="p-0">Category Name</label>
<input asp-for="Movie.Title" class="form-control">
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<select asp-for="Movie.CategoryId" class="form-control">
@foreach (var item in Model.Categories)
{
<option value="@item.Id">@item.Name</option>
}
</select>
<span asp-validation-for="Movie.CategoryId" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="Movie.ReleaseDate" type="date" class="form-control">
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="Movie.Rating" type="number" class="form-control">
<span asp-validation-for="Movie.Rating" class="text-danger"></span>
</div>
<div class="row">
<div class="col-6">
<button type="submit" class="btn btn-light form-control">Submit</button>
</div>
<div class="col-6">
<a asp-controller="Movie" asp-action="Index" class="btn btn-outline-primary form-control">Go back</a>
</div>
</div>
</div>
</form>
this is my html so if I remove this input field for Id, it will create new object
Angius
Angius•3w ago
And the backend code? The controller that handles this form's submission?
YourMajesty
YourMajestyOP•3w ago
yea second
public async Task<IActionResult> Edit(MovieCategoryVM movieCategoryVM)
{

if (!ModelState.IsValid)
{
_movieCategoryVM.Movie = movieCategoryVM.Movie;

return View(_movieCategoryVM);
}

try
{
await _movieRepo.Update(movieCategoryVM.Movie);
TempData["SuccessMessage"] = "Movie created successfully!";

return RedirectToAction("Index");
}
catch
{
TempData["ErrorMessage"] = "An error occurred while editing the movie. Please try again.";
return View(_movieCategoryVM);
}
}
public async Task<IActionResult> Edit(MovieCategoryVM movieCategoryVM)
{

if (!ModelState.IsValid)
{
_movieCategoryVM.Movie = movieCategoryVM.Movie;

return View(_movieCategoryVM);
}

try
{
await _movieRepo.Update(movieCategoryVM.Movie);
TempData["SuccessMessage"] = "Movie created successfully!";

return RedirectToAction("Index");
}
catch
{
TempData["ErrorMessage"] = "An error occurred while editing the movie. Please try again.";
return View(_movieCategoryVM);
}
}
Angius
Angius•3w ago
I'm gonna guess that Movie is the very same type as your database model for a movie
YourMajesty
YourMajestyOP•3w ago
so MovieCategoryVM is class to pass more than 1 model to view and back yes
Angius
Angius•3w ago
Use a DTO instead A class that contains only the properties you expect the form to send I don't see you using any other properties of MovieCategoryVM here either, so maybe use just that DTO That form only sends movie data after all
YourMajesty
YourMajestyOP•3w ago
I use those on the view itself, it contains list of categories for movies
Angius
Angius•3w ago
Cool The data you send on GET does not have to be the same as the data you receive on POST The form will send only those properties, so handle only those properties The controller action that handles the form submission doesn't need to know the entire list of categories
YourMajesty
YourMajestyOP•3w ago
ah so, I'd create new class, where I will avoid some props I don't need to file like Id and add object of this class as parameter in Edit method?
Angius
Angius•3w ago
Use just this object as a parameter of Edit
YourMajesty
YourMajestyOP•3w ago
yes and how can I transfer those properties to my database model? The obvious would be to do like
Movie.Id = MovieDTO.Id;
Movie.Title = MovieDTO.Title;
...
...
Movie.Id = MovieDTO.Id;
Movie.Title = MovieDTO.Title;
...
...
Angius
Angius•3w ago
Yep
YourMajesty
YourMajestyOP•3w ago
but is there smarter way? :harold:
Angius
Angius•3w ago
Or .ExecuteUpdateAsync()
var affectedRows = await _context.Movies
.Where(m => m.Id == id)
.ExecuteUpdateAsync(s => s
.SetProperty(m => m.Name, dto.Name)
.SetProperty(m => m.ReleaseDate, dto.ReleaseDate)
.SetProperty(m => m.Budget, dto.Budget));
var affectedRows = await _context.Movies
.Where(m => m.Id == id)
.ExecuteUpdateAsync(s => s
.SetProperty(m => m.Name, dto.Name)
.SetProperty(m => m.ReleaseDate, dto.ReleaseDate)
.SetProperty(m => m.Budget, dto.Budget));
YourMajesty
YourMajestyOP•3w ago
never used this method, will check what it does thanks a lot! it looks pretty much same, like you need to write every property separately
Angius
Angius•3w ago
Yeah
YourMajesty
YourMajestyOP•3w ago
ok, thanks again! will try to do it using ExecuteUpdateAsync method
Angius
Angius•3w ago
It calls the database just once
YourMajesty
YourMajestyOP•3w ago
but hold on haha
Angius
Angius•3w ago
While fetch -> update -> save, calls it twice
YourMajesty
YourMajestyOP•3w ago
I have another model where it looks pretty much same, both form and controller methods and there it works as intended hope im not asking too much, could u take a look? 🥹
Angius
Angius•3w ago
Sure
YourMajesty
YourMajestyOP•3w ago
<form method="post">
<div class="border p-3 mt-4">
<div class="row pb-2">
<h2>Edit Category</h2>
<hr>
</div>
<div class="mb-3 p-1">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label class="p-0">Category Name</label>
<input asp-for="Name" class="form-control">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="DisplayOrder" class="form-control">
<span asp-validation-for="DisplayOrder" class="text-danger"></span>
</div>
<div class="row">
<div class="col-6">
<button type="submit" class="btn btn-light form-control">Submit</button>
</div>
<div class="col-6">
<a asp-controller="Category" asp-action="Index" class="btn btn-outline-primary form-control">Go back</a>
</div>
</div>
</div>
</form>
<form method="post">
<div class="border p-3 mt-4">
<div class="row pb-2">
<h2>Edit Category</h2>
<hr>
</div>
<div class="mb-3 p-1">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label class="p-0">Category Name</label>
<input asp-for="Name" class="form-control">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="mb-3 p-1">
<label class="p-0">Display Order</label>
<input asp-for="DisplayOrder" class="form-control">
<span asp-validation-for="DisplayOrder" class="text-danger"></span>
</div>
<div class="row">
<div class="col-6">
<button type="submit" class="btn btn-light form-control">Submit</button>
</div>
<div class="col-6">
<a asp-controller="Category" asp-action="Index" class="btn btn-outline-primary form-control">Go back</a>
</div>
</div>
</div>
</form>
html
public async Task<IActionResult> Edit(Category category)
{
if (!ModelState.IsValid)
{
return View(category);
}

try
{
await _categoryRepo.Update(category);
TempData["SuccessMessage"] = "Category updated successfully!";

return RedirectToAction("Index");
}
catch
{
TempData["ErrorMessage"] = "An error occurred while updating the category. Please try again.";
return View("Index");
}
}
public async Task<IActionResult> Edit(Category category)
{
if (!ModelState.IsValid)
{
return View(category);
}

try
{
await _categoryRepo.Update(category);
TempData["SuccessMessage"] = "Category updated successfully!";

return RedirectToAction("Index");
}
catch
{
TempData["ErrorMessage"] = "An error occurred while updating the category. Please try again.";
return View("Index");
}
}
controller but the thing is in this case I am not using any separate class for ViewModel
Angius
Angius•3w ago
Ah, I just noticed you're doing repositories, huh Is both repositories' .Update() method the same?
YourMajesty
YourMajestyOP•3w ago
im using Category model yes ofc they inherit from same Repo class where those method are defined idk if it is proper way to use Repo Pattern lol
Angius
Angius•3w ago
The proper way is to not use the repo pattern :KEKW:
YourMajesty
YourMajestyOP•3w ago
yeah heard about that as well 💀 could this be the case?
Angius
Angius•3w ago
Not sure tbh Could be, that EF works when it's just the model, and doesn't when it's nested as a property of another
YourMajesty
YourMajestyOP•3w ago
well let it be :kekw: I'll try to use DTO, cause I guess it is actually proper way to do it?
Angius
Angius•3w ago
ye Database entities should never leave the backend boundary
YourMajesty
YourMajestyOP•3w ago
if it works will change both things to DTO and just forget lol I just thought of using custom methods like ToDto on Model and ToModel on DTO, is it something eatable?
Angius
Angius•3w ago
Leaving the model anemic and adding ToModel and FromModel on the DTO is more recommended Or creating a whole separate class that has a ToModel method, and some MapToDto extension method on IQueryable<Model> so it can be plugged into your LINQ query Or, as I like to do, an Expression<Func<Model, Dto>> that can be plugged into .Select()
YourMajesty
YourMajestyOP•3w ago
Uh I am not really sure about my delegate understanding and how to use them properly, so I think I will try to use separate class with extension methods on IQueryable<Model> Thanks a lot for your advices!
Angius
Angius•3w ago
public static Expression<Func<Model, Dto>> ToDto = (model) => new Dto {
Name = model.Name,
...
};
public static Expression<Func<Model, Dto>> ToDto = (model) => new Dto {
Name = model.Name,
...
};
YourMajesty
YourMajestyOP•3w ago
:pikawhat: Looks scary
Angius
Angius•3w ago
Then you just plug it into .Select()
YourMajesty
YourMajestyOP•3w ago
(model) is the method parameter here?
Angius
Angius•3w ago
var thing = await _ctx.Things
.Where(...)
.Select(WhateverClass.ToDto)
.FirstOrDefaultAsync();
var thing = await _ctx.Things
.Where(...)
.Select(WhateverClass.ToDto)
.FirstOrDefaultAsync();
ye
YourMajesty
YourMajestyOP•3w ago
Looks way more eatable ngl Yea definitely worth giving more practice to delegates, will try to work with this as well

Did you find this page helpful?