简体   繁体   中英

C# - Entity Framework not updating Entities

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM