I have a paintings web app that uses ASP.NET Core, Angular, EF Core, SQL Server, AutoMapper with a repository pattern.
The issue is that when I try to update a single painting from the painting table, it does not save to the database. I tried other tables in this same method to see if it was a problem with the flow but they save successfully.
Through swagger I call the put method, this calls the painting controller, goes into the repository, and the repository returns the updated object but when I go to the database nothing updates. If I call the get action from swagger I also DO NOT see the updates.
When I add breakpoints to see the data everything looks fine from start to end but it just does not save to the database. To test I even tried to remove auto mapper logic and manually created an object inside of the update method and set the existing object properties to these hard coded values to see it was the incoming data but still no luck. Again, for testing I tried updating other tables and those worked.
Controller
[HttpPut("{paintingId:int}")]
public async Task<IActionResult> UpdatePaintingAsync(int paintingId, [FromBody] UpdatePaintingRequest updatePaintingRequest)
{
try
{
if (await repository.Exists(paintingId))
{
var updatedPaiting = await repository.UpdatePainting(paintingId, mapper.Map<DataModels.Painting>(updatePaintingRequest));
if (updatedPaiting != null)
{
return Ok(updatePaintingRequest);
}
}
return NotFound();
}
catch (Exception ex)
{
logger.LogError($"Failed to update painting: {ex}");
return BadRequest("Failed to update painting");
}
}
Update method from repository
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}
Get painting to update
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.AsNoTracking()
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
Model (exact same on DAO and DTO)
public class Painting
{
public int PaintingId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public bool IsOriginalAvailable { get; set; }
public bool IsPrintAvailable { get; set; }
public bool IsActive { get; set; }
public ICollection<PaintingCategory> PaintingCategories { get; set; }
}
Context
public class JonathanKrownContext : DbContext
{
public JonathanKrownContext(DbContextOptions<JonathanKrownContext> options) : base(options)
{
}
public DbSet<Painting> Painting { get; set; }
}
ModelBuilder.Entity
modelBuilder.Entity("JonathanKrownArt.API.DataModels.Painting", b =>
{
b.Property<int>("PaintingId")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsOriginalAvailable")
.HasColumnType("bit");
b.Property<bool>("IsPrintAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("PaintingId");
b.ToTable("Painting");
});
CodePudding user response:
Your problem is that you used AsNoTracking when fetching the entity, and thus, context doesn't keep track of the changes anymore. So you need either to attach it before saving or remove AsNoTracking.
If you don't want to attach the entity, you need to change GetPaintingByIdAsync to this:
public async Task<Painting> GetPaintingByIdAsync(int paintingId)
{
return await context.Painting
.Include(x => x.PaintingCategories)
.ThenInclude(c => c.Category)
.Where(x => x.PaintingId == paintingId)
.FirstOrDefaultAsync();
}
If you want to keep AsNoTracking then in your UpdatePainting you need to add:
context.Painting.Update(existingPainting);
before you call save.
Update method does the following:
Begins tracking the given entity in the Modified state such that it will be updated in the database when SaveChanges() is called.
So change your method to this:
public async Task<Painting> UpdatePainting(int paintingId, Painting request)
{
var existingPainting = await GetPaintingByIdAsync(paintingId);
if (existingPainting != null)
{
existingPainting.Name = request.Name;
existingPainting.Description = request.Description;
existingPainting.ImageUrl = request.ImageUrl;
existingPainting.IsOriginalAvailable = request.IsOriginalAvailable;
existingPainting.IsPrintAvailable = request.IsPrintAvailable;
existingPainting.IsActive = request.IsActive;
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();
return existingPainting;
}
return null;
}
CodePudding user response:
I think using AsNoTracking()
is a good practice and you should use it wherever you can but in case of Update
you need to attach the entity to context by this EF
will know this entity should be updated.
So for solve your problem just add one line to code like this:
//other lines
context.Attach(existingPainting); //<--- by this line you tell EF to track the entity
context.Painting.Update(existingPainting);
await context.SaveChangesAsync();