Reputation: 2404
I have a simple app which performs standard CRUD operations.
My issue is that at the moment, it does not seem to be editing values in the database. I have debugged through the process and seen that it fails on the set.Attach(entity)
line in the context.
Model
[Table("RepackRequest")]
public partial class RepackRequest
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public RepackRequest()
{
}
public int ID { get; set; }
[Required]
public string FromItem { get; set; }
[Required]
public string ToItem { get; set; }
public int Quantity { get; set; }
public int? QuantityCompleted { get; set; }
[Column(TypeName = "date")]
public DateTime DateRequested { get; set; }
[Required]
public string RequestedBy { get; set; }
[Required]
public string RequestedByEmail { get; set; }
public string ActionRequired { get; set; }
[Required]
public string Division { get; set; }
[Required]
[StringLength(1)]
public string Status { get; set; }
[Column(TypeName = "date")]
public DateTime? CompletionDate { get; set; }
[Required]
public string Customer { get; set; }
[Required]
public string Priority { get; set; }
[Required]
public string EnteredBy { get; set; }
public string CompletedBy { get; set; }
public string FromLocation { get; set; }
public string ToLocation { get; set; }
public string ReworkedBy { get; set; }
public string OriginalDriver { get; set; }
public string FromItemDriver { get; set; }
public string FromLocationDriver { get; set; }
public string ToLocationDriver { get; set; }
public string Workforce { get; set; }
[Required]
public string OrderNum { get; set; }
[Column(TypeName = "date")]
public DateTime PlannedCompletion { get; set; }
[Column(TypeName = "date")]
public DateTime? StartDate { get; set; }
}
API Controller Action
[HttpPost, Route("api/Repack/Update")]
public async Task<HttpResponseMessage> UpdateRepack([FromBody] RepackRequest repack)
{
var oldStatus = _uow.RepackService.Get(repack.ID).Status;
_uow.RepackService.UpdateRepack(repack);
_uow.Save();
if (repack.Status == "C")
await _helper.SendCompletedRepackEmail(repack);
else if (repack.Status != oldStatus)
await _helper.SendStatusChangeEmail(repack, oldStatus);
return Request.CreateResponse(HttpStatusCode.OK, repack.ID);
}
Service Method
public void UpdateRepack(RepackRequest repack)
{
_context.SetModified(repack);
_context.Save(); //_context.SaveChanges() called inside here
}
Context Method
public void SetModified<T>(T entity) where T : class
{
var set = Set<T>();
set.Attach(entity); //FAILS HERE
Entry(entity).State = EntityState.Modified;
}
I have checked that the ID etc of the object is filled so that Entity Framework can find the existing record and im now out of ideas.
I dont get any error messages at all. All i see is that once it tries to attach the entity, it goes to the Dispose()
method of my UnityResolver.
Any help would be greatly appreciated.
Thanks!!!!
Upvotes: 1
Views: 3136
Reputation: 8696
The error is self describing. The reason is that you get entity from context before attaching it by this var oldStatus = _uow.RepackService.Get(repack.ID).Status;
code line and Entity Framework keeps it in context. You have two workarounds:
In your UpdateRepack
re-get the entity from context using its id and set the values to new values.
public void UpdateRepack(RepackRequest repack)
{
RepackRequest fromDatabase = _uow.RepackService.Get(repack.ID);
// Set current values to new values.
_context.SetValues(fromDatabase, repack);
_context.Save(); //_context.SaveChanges() called inside here
}
public void SetValues<T>(T entity, T currentEntity) where T : class
{
var entry = Entry(entity);
entry.CurrentValues.SetValues(currentEntity);
}
Do not worry, getting data from context will not be costly operation, because it is already in the context. By using this method update query will be sent to update only changed properties, whereas if you set state of entity to modified then update query will be sent to update all column values.
You can use AsNoTracking
to tell EntityFramework not to store received entity in the context. But by doing so, every time you try to get object query will be executed against database. Additionally update query will be sent to update all column values and it much expensive than updating only needed values. To do so, you should add another method to RepackService
of unit of work called GetAsNoTracking
and implement it similar to below:
public Repack GetAsNoTracking(int id)
{
return _context.Set<Repack>()
.AsNoTracking()
.First(m => m.ID == id);
}
Then you should use GetAsNoTracking
to get your repack and not touch to remaining part of your current code. As it is not stored in context attaching it will not cause to an error.
[HttpPost, Route("api/Repack/Update")]
public async Task<HttpResponseMessage> UpdateRepack([FromBody] RepackRequest repack)
{
var oldStatus = _uow.RepackService.GetAsNoTracking(repack.ID).Status;
.........
}
Note: It is not good practice to save data for every operation. In Unit of Work pattern you are supposed to commit one time instead of calling SaveChanges
method for every action. From your code I see that you have _uow.Save();
method and I believe that you call _context.SaveChanges()
method inside this method. By doing so, you should avoid calling SaveChanges
at crud functions such as UpdateRepack
method.
public void UpdateRepack(RepackRequest repack)
{
// Do needed operations
// Remove this line. You are supposed to save changes in the end using _uow.Save(); -> _context.Save();
}
Upvotes: 2
Reputation: 2447
You're trying to attach an entity that doesn't belong to the context. you need to bring that object by key, modify the fetched object then attach and save (or just save it). More details here
You're code should be something like (as discussed in the comments):
public void SetModified<T>(T entity) where T : class
{
var set = Set<T>();
var entityFromCtx = set.Where(x => x.Id == entity.Id).FirstOrDefault();
Entry(entityFromCtx).CurrentValues.SetValues(entity);
//set.Attach(entity); // you don't need this anymore since you're not disposing the context yet.
Entry(entityFromCtx).State = EntityState.Modified;
//set.SaveChanges(); // I don't know if this fits here, but just don't forget it.
}
Upvotes: 0