1

I have a really simple model:

public class Item : DbEntity
{
    public string Name {get;set;}
    public int Quantity {get;set;}
    public virtual Category Category {get;set;}
}

public class Category : DbEntity
{
    public string Name {get;set;}
}

I use the below methods to save and commit changes to the database:

public void SaveEntity<TEntity>(TEntity entity) where TEntity : DbEntity
{
    if (entity.Id.Equals(Guid.Empty))
        _context.Set<TEntity>().Add(entity);
    else
        _context.Entry(entity).State = System.Data.EntityState.Modified;

    _context.SaveChanges();
}

public void DeleteEntity<TEntity>(TEntity entity) where TEntity : DbEntity
{
     _context.Set<TEntity>().Remove(entity);
     _context.SaveChanges();
}

These save and delete methods reside in the Repository, this Repository is Ninjected, so it should have a constant state throughout the application life. This is a tried and tested method, it has always worked for me. Now for some reason when I do this:

[HttpPost]
public ActionResult Edit(MenuItemViewModel vm)
{
    if (ModelState.IsValid)
    {
        // TODO: Add ViewModel Logic
        vm.MenuItem.Category = _repository.Categories.FirstOrDefault(x => x.Id.Equals(vm.SelectedCategory));

    try
    {
        // TODO: Add Save Logic
            _repository.SaveEntity(vm.MenuItem);

            TempData["success"] = true;
            TempData["message"] = String.Format("Saved {0} Successfully", vm.MenuItem.Description);
    }
    catch (Exception ex)
    {
             TempData["error"] = true;
             TempData["message"] = "Error Occured";
    }

    return _redirectTo;
    }

    return View("Shape", vm);
}

The view part is simple, just some fields for the Name and Quantity, the Category is a Drop Down box containing all the category names as Text and id as Value, this gets posted back to the server where it the repository goes and collects the new Category and assigns it to the entity.

For some reason this just doesn't commit any changes to the database, break pointed before and after, when I breaked after I checked the database, it hadn't changed!

Can anyone see the silly little mistake that is preventing it from updating.

EDIT

Just read this StackOverflow Post, I picked up on the part where is says, you attach it first then edit it, because when attach is called, you call it with Unchanged state, then when you modify it, the changes will be saved when you call SaveChanges().

Has this got anything to do with it?!

UPDATE
Okay some interesting news, I step through the SaveEntity method, after i step past the SaveChanges() I check the context again, it shows that it has been updated?! Could the fact that I redirect to another controller and action be affecting the results or table modification? I don't think the issue is with in the SaveEntity method!!

LATEST UPDATE

I changed the model of the Item and simply added the line public Guid CategoryId {get;set;} which simply changes the Category to turn on Cascade On Delete. Now I get this error when I try and SaveChanges:

A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.

Community
  • 1
  • 1
Callum Linington
  • 14,213
  • 12
  • 75
  • 154

2 Answers2

1

So, to fix this I changed the Item entity to:

public class Item : DbEntity
{
    public string Name {get;set;}
    public int Quantity {get;set;}

    public Guid? CategoryId {get;set;}
    public virtual Category Category {get;set;}
}

This basically, makes it easier and quicker to change the Category because you don't have to make another call to the database. the ? makes this a nullable property which sets Cascade Delete to No Action so that when you delete an Item it doesn't annoyingly go off and delete the associated Categories.

I still have no idea why the original way doesn't work, it has done in the past and it used to negate having to put public Guid? CategoryId {get;set;} because it would set the nullable property and would already create a Category_Id field. But hey ho, that's the way it wants to be, then I'll play ball!!

Callum Linington
  • 14,213
  • 12
  • 75
  • 154
0

In SaveEntity, if the Id doesn't equal Guid.Empty, you are not attaching the item to the context, only setting the State to Modified. Try:

public void SaveEntity<TEntity>(TEntity entity) where TEntity : DbEntity
{
    if (entity.Id.Equals(Guid.Empty))
    {
        _context.Set<TEntity>().Add(entity); // Automatically sets state to Added
    }
    else 
    {
        _context.Set<TEntity>().Attach(entity);
        _context.Entry(entity).State = System.Data.EntityState.Modified;
    }

    _context.SaveChanges();
}

Also, for DeleteEntity, try Attach and Remove:

public void DeleteEntity<TEntity>(TEntity entity) where TEntity : DbEntity
{
    _context.Set<TEntity>().Attach(entity); // Attaches entity with state Unchanged
    _context.Set<TEntity>().Remove(entity); // Changes entity state to Deleted
    _context.SaveChanges();
}

The key is that in a disconnected environment such as ASP.NET, the model returning from a post is not attached to the context and as such needs to be reattached before it can be processed.

As a test, check the state of the entity in your existing code, before SaveChanges is called, and it is likely Unchanged meaning no action will be taken on it.

Steve Andrews
  • 561
  • 3
  • 14