Update/Edit entity with relations - c#

I am working in MVC and creating som CRUD operations. I am not a pro, som im having trouble doing the Update/edit of an entity, when the entity have a relation, that i also want to update. Here is a short description of the tables:
I have a model called "User" with follwing properties (userId and so on....)
I have a model called "Address" with following properties (addressId, street, zipcode, city)
I have a model called "Home" with following properties (StreetNumber, floor, side, and two navigation properties"userid" and "addressId").
Now.. i have a viewModel with the properties from Address and Home.. And i want to update Both using this viewModel. Im not having probilem showing all the data in my view. But i cannot update both entities at the same time?
Can anyone give an example on how to do this?
Can i update the address model going through the home model?

It would've been easier if you had posted some of your code, but I'll assume what you have.
First, I'd update your viewmodel and include id of the user:
public class UserViewModel
{
public Guid Id {get;set;}
public string Name {get;set;}
public string Surname {get;set;}
public string City {get;set;}
public string Address {get;set;}
}
Then you just have to map the properties from the ViewModel to the properties of the model. Since you mentioned that you have some navigation properties it shouldn't be hard to find the address of the user you want to update.
public void Update(UserViewModel userVM)
{
var user = db.Users.Find(userVM.Id);
if(user != null)
{
user.Name = userVM.Name;
user.Surname = userVM.Surname;
var address = user.Address;
address.City = userVM.City;
address.Address = userVM.Address;
db.SaveChanges();
}
}
I think you should get the idea now.
In case you don't have navigation properties just include the id of the address to your viewmodel so you can find it the same way as I did for the user.

Related

Save record to multiple tables using UoW pattern

I am using MVC 5 with Entity Framework 6 and Unit of Work pattern.
Tables: and fields
Customer - Id, Name
ContactType - Id, Name (Home contact, work contact etc)
ContactDetails - Id, CustomerId, ContactTypeId, ContactValue
One Customer can have multiple Contact Details (so Customer1 has a home contact, work contact etc).
The ContactType table is a look-up table so it just displays the types of contacts available (home, work, emergency, mobile etc)
I have created the Interfaces and Classes as required to carry out the basic Add, Edit functionality, then created an Unit Of Work class to hold all these Repositories.
Tested it out and everything works as expected when i hard code values in.
When i created my MVC application, i added the below lines to add this entry into a database using the Unit of Work class
public ActionResult SaveContactDetails(CustomerContactType viewModel)
{
_unitOfWork.Customers.Add(viewModel.Customer);
_unitOfWork.ContactDetails.Add(viewModel.ContactDetail);
//_unitOfWork.SaveAllChanges();
return View();
}
I created a new ViewModel called CustomerContactType which is a class containing the tables i require in order to save the data successfully
public class CustomerContactType
{
public ContactType ContactType { get; set; }
public IEnumerable<ContactType> ContactTypes { get; set; }
public Customer Customer { get; set; }
public ContactDetail ContactDetail { get; set; }
}
I realised how to assign a dropdown value to the model within the .cshtml, so the ContactDetails table knows which ContactType is associated with that contact number (Home, emergency etc).
The problem i have is the ContactDetails requires a customer ID. This customer ID doesnt generate until the customer is saved so im not sure how i should be doing this?
These two lines carry out the task but i can see the customerID is null in the second line where i would have preferred it to contain the ID
_unitOfWork.Customers.Add(viewModel.Customer);
_unitOfWork.ContactDetails.Add(viewModel.ContactDetail);
I can provide additional code if required but wasnt sure if theres an easy fix or not.
Instead of using CustomerId in ContactDetails, why not change CustomerId to be of a complex type Customer and decorate it with a foreign key attribute? That way you can add a ContactDetails object along with a Customer object assigned to its property and a single call to SaveChanges should persist both entities to the DB.

EF Core with Automapper throw Exception 'Entity type cannot be tracked'

I have a problem with EF Core in combination with automapper.
My Solution contains this hierarchy:
BLL <=> DAL <=> EF Core <=> Mysql Database
I use automapper in the BLL.
And have as example this user class:
public class User
{
public Guid Id { get; set; } = Guid.NewGuid();
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid? CreatedById { get; set; }
public Guid? ModifiedById { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User ModifiedBy { get; set; }
}
And i have the same class for the UserDTO (DataTransferObject).
I already have a record for an user in the database to create another user, because it's necessary to fill createdby and modifiedby to create or update a user.
Get the user from the database:
UserDTO userDTO = this._UserBLL.GetUser("Unit", "Test");
//BLL
var mapped = new List<UserDTO>();
this._Mapper.Map(dalResult, mapped); //dalResult = List<User> object
return mapped;
And here an example how to add a user:
UserDTO testUser2 = new UserDTO
{
FirstName = "Test 2",
LastName = "Test 2",
CreatedBy = userDTO,
ModifiedBy = userDTO
};
this._UserBLL.Add(testUser2);
//BLL
List<User> mappedUsers = new List<User>();
this._Mapper.Map(users, mappedUsers); //users = List<UserDTO> that contains testUser2
DbContext.Users.AddRange(mappedUsers); //Throws exception
DbContext.SaveChanges();
The Exception that is thrown: The instance of entity type 'User' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
But i dont know how to solve the exception. Same issue is, when i update an existing record.
AutoMapping class is:
this.CreateMap<User, UserDTO>()
.ReverseMap();
Do u have an idea how to fix this?
I hope you understand my problem, else not, ask me.
King regards
There are a couple of misconceptions here
Bare in mind that a DTO is a dumb class, in a sense that it does nothing but serving as a container for the information to travel in a strongly typed way.
You will be normally dealing with DTOs when the front-end send you some data and your backend catches on the controller parameter (I know this is not exactly true, just trying to make a point here), or when returning a DTO to the front end to whatever reason it needs.
The general approach then will be:
Frontend sent a DTO
Backend controller catches
Backend services map the DTO to some domain model of interest (automapper)
Backend services perform the logical steps of the business rules such as
saving a record on the database or updating something I don't know you name it
Backend service map the response (if any) to a DTO (automapper)
Backend controller sends the data right back to the front end, to do whatever it needs to do
Now, regarding your bug it's a common bug of entity frameworks begginers. Let me ilustrate what happend:
UserDTO userDTO = this._UserBLL.GetUser("Unit", "Test");
//tip: as per the explanation above this should't be a DTO
//EF says "alright!, programmer pull out
//a record from the database and got a object of type UserDTO with Id = "someguid"
//im gonna begin tracking this object to see if it changes in the
//execution of the code.
//In case my fellow programmer wants to modify this object and
//save it in on the database, as I was tracking it down in
//the first place, I'm gonna know how to build my
//SQL statements to perform the right update/delete/create sentence in
//SQL form.
Somewhere over the mapping you are creating the same object type with the same Id
and then entity framework go bananas
//EF says: "mmm, that's weird, im tracking the same object
//again, im gonna alert the human. *throws exception*
The key here is to re-read the documentation of entity framework/core to fully understand how entity framework think
Now, regarding your code and business requirement, you should be aiming to do something like this:
//Suppose that the in the controller parameter we receive this DTO
var newUserDto = new UserDto()
{
Firstname = "Rodrigo",
Lastname = "Ramirez",
CreatedBy = 1, //this can be the user Id of the user that sent this DTO
};
var userToCreate = Mapper.Map<User>(newUserDto);
var userCreator = UserRepository.GetById(newUserDto.CreatedBy);
userToCreate.CreatedBy = userCreator;
final words on this topic:
It's a matter of preferences but I do rather read
var user = Mapper.Map<User>(newUserDto); //hey mapper, be a pal and transform this newUserDto into a User object.
Check that you are using List<> to map when you don't need to.
For this business logic, you don't need to prevent entity framework to begin tracking
a given entity. But in case you find such case you can checkout the method "AsNoTracking()" that does that.
Review the necesity of create a new GUID over User class instantiation. This seems to be buggy and you need to fully understand how EF works in order to succed with this practice.
The thing you want to do is a common audit practice, and it's advisable that you override the method OnSaveChanges() to perform this task.

Add variable to Entity Framework created model class without editing the actual database columns?

I just recently started creating a .net web app in mvc5. I've tried to search for the answer as I usually do rather than asking a question but Im not sure how to word this but here it goes:
I have a class:
public partial class employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
In which the class was created by using the Database First wizard with Entity Framework. (reasoning for the tag)
What I did was get the FistName, MiddleName, and LastName from the database. In which I successfully did with this ActionResult from my controller.
[ChildActionOnly]
public ActionResult Nav()
{
employee user;
using (DatabaseEntities context = new DatabaseEntities())
{
user = context.employees.FirstOrDefault(e => e.FirstName ="Bob");
if (!System.IO.File.Exists(Server.MapPath(user.imagefile)))
{
user.imagefile = $"/Content/Images/user/{user.FirstName[0]}.png";
}
}
return PartialView("_Nav", user);
}
Now I'm wondering if its possible, how to create another item to the class without adding a column to the database. I want to add a FormattedName variable to the class that is got after the database call to get the model (user) to include the new variable (FormattedName) that gets its value from the existing variables in the class.
I understand the wording and terminology could be off but any help would be great and thx in advance.
Edit:
In the comment section, I was informed about attributes ex.[NotMapped] which was information I definitely needed to know. But for this particular question, it doesn't work. Everything was ok until I updated the model using entity framework, the attributes were gone and have to keep adding them every model update which is not ideal for me. I went ahead and edited my database to include a (FormattedName) column so the problem is fixed. But I still would love to know if this is doable.

Why isn't Entity Framework updating the child object here?

I have a Customer model with a MailingAddress property - this is a navigation property to an Address class:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address MailingAddress { get; set; }
}
I have a form that posts a viewmodel back which contains a customer, along with the mailing address. Here is the save method in the controller:
public ActionResult Save(CustomerFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View("CustomerForm", viewModel);
}
if (viewModel.Customer.Id == 0)
{
_context.Customers.Add(viewModel.Customer);
}
else
{
var customerInDb = _context.Customers
.Single(c => c.Id == viewModel.Customer.Id);
_context.Entry(customerInDb).CurrentValues.SetValues(viewModel.Customer);
}
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
When posting a new customer, everything works fine (mostly, see note below) and the customer is created along with a corresponding address record. However, when I edit an existing entry, the customer is updated but the address is not. I verified the updated address is being passed in the customer object. If I add a line like this:
_context.Entry(customerInDb.MailingAddress).CurrentValues.SetValues(viewModel.Customer.MailingAddress);
Then it is updated.
Is the child here still considered a detached entity? I assumed since it is a property of the Customer I am fetching it would be automatically saved with the parent. Why does this work with a new record and not with an update?
One note about the new record creation - a Customer record is created and has a MailingAddress_Id pointing to the address. The Address record is also created, but its Customer_Id is null...an idea why EF is not adding the key on that side of the relationship? Address model and view model code in case it helps:
public class Address
{
public int Id { get; set; }
public string Street1 { get; set; }
// Snip a bunch of address data properties
public virtual Customer Customer { get; set; }
}
public class CustomerFormViewModel
{
// Snip irrelevant properties
public Customer Customer { get; set; }
}
First of all, if your Customer and Address are in one-to-one relationship, then no foreign key is needed. Actually, in one-to-one relatioonships primary key on dependant side of relationship is also foreign key to principal side. Secondly, when you create new Customer you use context.Customers.Add(viewModel.Customer); and it adds model with all of its child models, but when you try to update using _context.Entry(customerInDb).CurrentValues.SetValues(viewModel.Customer); it does not add all child navigation properties, to do so, you have to tell it to EntityFramework explicitly:
var customerInDb = _context.Customers
.Single(c => c.Id == viewModel.Customer.Id);
_context.Entry(customerInDb)
.CurrentValues
.SetValues(viewModel.Customer);
var mailingAddressInDb = _context.Addresses
.Single(m => m.Id = viewModel.Customer.MailingAddress.Id);
_context.Entry(mailingAddressInDb)
.CurrentValues
.SetValues(viewModel.Customer.MailingAddress);
It should work for you. But it is a bit awkward. When you have dozens of models, you would not even want to imagine it.
Good news
The good news is that, there is an API to solve this problem from its roots. Your problem will be solved in just a few steps. You install it from NuGet using Install-Package Ma.EntityFramework.GraphManager, configure your models to meet prerequisites (which are so easy) and handle whole graph using single line of code:
public ActionResult Save(CustomerFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View("CustomerForm", viewModel);
}
// Define state of whole graph with single line
_context.AddOrUpdate(viewModel.Customer);
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
Please, have a look at CodeProject article for a quick wallktrough. It has example code, so you can download and examine it. I am the owner of this API and I am ready to answer to your questions.

Entity Framework6 - One to many relationship doesn't want to update, but create works

I'm experiencing a pretty weird issue i can't explain. I can create a new entity and link it to his foreign key, but i can't update. This is my code
My Post.cs Entity :
public class Post(){
public int Id {get;set;}
public string Title {get;set;}
...
public int PostCategoryId {get;set;}
public virtual Category Category {get;set;}
}
My Category.cs Entity :
public class Category(){
public int Id {get;set;}
public string Title {get;set;}
...
public virtual IList<Post> Posts {get;set;}
}
The Update Method in the controller :
[HttpPost]
public ActionResult EditPost(Post post) {
....
_repository.UpdatePost(post);
And the UpdatePost Method in the repository :
public void UpdatePost(Post post){
var category = _db.Category.SingleOrDefault(x => x.Id == post.PostCategoryId);
post.Category = category;
// I clearly see that the post have the category in the object
_db.Entry(post).State = EntityState.Modified;
_db.SaveChanges();
Every property of my object is updated, unless the Category.
Does anyone have a solution ?
Thank you so much, i can't sleep 'cause of that issue :)
It might be that your Category variable is named Category, which is the exact same name as the class itself. Try renaming it to category (lowercase) or to PostCategory and see if that helps. Or it might be an issue with the entity state. EntityState is usually for when you bring changes directly from the view model, not after manually making changes to an object's fields.
Actually I think I know what it is. You need to fetch the Post from the database, not the Category.
Category category = _db.Category.SingleOrDefault(x => x.Id == post.PostCategoryId);
Post p = db.Post.SingleOrDefault() //whatever code you need to find the Post in the database
p.Category = category;
db.SaveChanges();
I would still remove the EntityState line.
Also, I'm assuming you're just trying to update the SQL table's "PostCategoryId" entry? In that case all you need to do is this:
Post p = db.Post.SingleOrDefault() //whatever code you need to find the Post in the database
p.PostCategoryId = post.PostCategoryId
db.SaveChanges();
Matt S.,
Maybe i'm wrong but, if i get the post from the db, i'll have to update every field that may be updated from the user (in fact, there is 10 properties in the Post entity).
And, the PostCategoryId is uptaded, any issue about that field. Tell me if i'm wrong, but i would have to avoid to request for the Category but get it on the post. Like :
var post.Category = _db.Category.SingleOrDefault(x => x.Id == post.CategoryId)
I could create a method for that and call it every time i need to retrive the Category Entity related to the Post.
But i thought i could do update the Category "into the post" and just call post.Category when I need. Maybe it's a misunderstanding from me ?

Categories

Resources