1

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!!!!

18
  • 1
    What's the exception type, message etc... ? Commented Oct 4, 2016 at 7:26
  • Hi sorry i missed that info off. Ive now updated my question but there was no error or exception. It just goes to the Dispose method of my UnityResolver Commented Oct 4, 2016 at 7:27
  • did you try _context.SaveChanges() ? Commented Oct 4, 2016 at 7:28
  • SaveChanges is called inside the _context.Save(); method called from the UpdateRepack service method. However, it doesnt get to this point as it fails to attach the entity Commented Oct 4, 2016 at 7:30
  • I have commented the line of code where it is called just to clarify Commented Oct 4, 2016 at 7:31

2 Answers 2

2

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:

First

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.

Second (not recommended)

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(); 
}
Sign up to request clarification or add additional context in comments.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.