ChrisBint ChrisBint - 3 months ago 18
C# Question

Entity Framework performing INSERT where it should not be

I have the following Entity Data models, simplified for brevity;

public abstract class Entity<T> : BaseEntity, IEntity<T>
{
[Key]
public virtual T Id { get; set; }
}

public class User : Entity<int>
{
public List<Category> Categories { get; set; }
}

public class Category : Entity<int>
{
public string Description { get; set; }
}


public class List : Entity<int>
{
public Category Category { get; set; }
}


These are accessed from the DbContext using a DbContext exposed by either a generic service, or a more customised implementation to provide business logic.

When I publish the database and add the following code to the Seed() method, all is well and the data looks good directly in the database.

var user = new User
{
Email = "",
Categories = new List<Category>
{
new Category
{
Description = "Category 1",
},
new Category
{
Description = "Category 2",
}
}
};


context.Users.AddOrUpdate(u => u.Email, user);

var list = new List()
{
Id = 1,
Description = "Test List",
UserId = 1,
Category = user.Categories.FirstOrDefault()
};

context.Lists.AddOrUpdate(u =>u.Id, list);


Please note that the User owns the categories and you can (should only be able to) create them by accessing the Categories Property.

This gives me;

Categories post-Seed

Lists post-Seed

I am using these objects in my controller as such;

public ActionResult Edit(int id)
{
var categories = _usersService.GetUser(User.Id).Categories;
categories.Insert(0, new Category {Description = "", Id = 0});

var list = _listsService.GetList(id);

var viewModel = new EditViewModel
{
Id = list.Id,
Reference = list.Reference,
Description = list.Description,
CategoryId = list.Category?.Id ?? 0,
Categories = new SelectList(categories.AsEnumerable(), "Id", "Description")
};

return View(viewModel);
}


In the above test, I am using the
List
inserted during the Seed and I can see the
List
does indeed have a
Category
, and the values are correct.

enter image description here

For information, I am using the following ViewModel. I have been investigation methods to be able to 'select' the
User.Categories
from a DropDown and this appeared to work the best at present.

public class EditViewModel
{
[Required]
public int Id { get; set; }

[Required]
public string Description { get; set; }

public IEnumerable<SelectListItem> Categories { get; set; }

[ScaffoldColumn(false)]
public int CategoryId { get; set; }

[ScaffoldColumn(false)]
public Guid Reference { get; set; }
}


The populated ViewModel looks like this;

enter image description here
and finally, the POST method;

[HttpPost]
public ActionResult Edit(EditViewModel model)
{
if (ModelState.IsValid)
{
var categories = _usersService.GetUser(User.Id).Categories;

var list = _listsService.GetList(model.Id);

list.Description = model.Description;
list.Category = categories.FirstOrDefault(x => x.Id == model.CategoryId);

_listsService.Update(list);

categories.Insert(0, new Category { Description = "", Id = 0 });
model.Categories = new SelectList(categories.AsEnumerable(), "Id", "Description");

return View(model);
}

return View(model);
}


So, in the following scenarios, this is what happens. For clarity, each time I am doing this, I go back to the Lists Index and GET Edit again;


  • Select '' from the Dropdown - NO Categories INSERT,UPDATE on Lists table only, setting [Category_Id] = NULL - Correct

  • SELECT 'Category 1' from DropDown. INSERT categories, UPDATE lists - NOT Correct



The code being used to update the List is;

public void Update(T entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}


Now, I know this is something I am doing, but being new to EF, I have no idea.

enter image description here

Answer

The problem was down to how the values were being set.

I needed to set the Foreign Key and assign the value to this.

public class List : Entity<int>
{
    public int CategoryId { get; set; }

    [ForeignKey("CategoryId")
    public Category Category { get; set; } 
}

and the value was then set as

var list = _listsService.GetList(model.Id);
list.Description = model.Description;
list.CategoryId = model.CategoryId;
list.Category = null;
_listsService.Update(list);

After this, when getting the list from the repository, both the Category and CategoryID would be populated correctly.

The issue was down the setting the Entity as modified, this internally indicated that the Category was 'new' when in fact it was not. You could also 'attach' and existing category to the entity/context but decided the method above was better.

A slightly better approach to the above would be to create a new 'UpdateList' method which could be called rather than the generic update. This method would perform the setting of the relevant properties outside of the controller method.

Comments