Return record ID before commiting changes?

Mr Edge 221 Reputation points
2021-08-06T12:55:54.63+00:00

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

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.

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,325 questions
ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,237 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Michael Taylor 47,471 Reputation points
    2021-08-06T14:49:02.2+00:00

    Firstly I notice you commented out the SaveChanges call in your action. If you don't save the changes then nothing will be created. The default behavior if the controller returns/fails before you save changes is to throw away all your changes.

    EF is based upon proxies and relationships. You're thinking about this in terms of PK/FK relationships. Assuming you have set up the relationship in EF such that a Contact has an associated Customer and/or Customer has one or more Contact objects then ensure your navigation properties are marked as virtual. To create a new customer and contact you would simply create the customer object and add to your Customers set. Ensure the Customer object has a Contact assigned in its navigation property. When EF is told to add the customer it will see that there is also a Contact and, since the Contact is not being tracked yet, will automatically add the contact to the DB as well. You don't need to explicitly add it. Behind the scenes EF will (as part of the transaction) insert the Customer row and then insert the Contact row using the ID that was given when it inserted the Customer row. You don't have to do this manually.

    More interesting is that once you call SaveChanges then EF, because of the proxy object for the virtual navigation property, will update the Id properties on your objects for you. So if you were to check the value of Customer.ID after the save call you'd see it is no longer invalid. The Contact.Id will be the same way. But unfortunately you'll still see a mixture of other behavior. For example the Customer property on Contact, if any, will probably not be set. EF caches data when it loads things and won't refresh them. So you should take a look at your objects after saving to see what EF actually set. I wouldn't be surprised if Contact.CustomerId is also not set as it isn't a virtual property. In general virtual nav properties will be updated by EF after save but other properties will not be.


  2. Michael Taylor 47,471 Reputation points
    2021-08-06T19:35:04.36+00:00

    (Responding to your question with an answer so I have more space)

    Firstly I'll just say that using your EF data objects as your models for MVC is going to cause you problems down the road. Besides the fact that your exposing your DB structure to a view which drastically limits your options it also means that your MVC layer is going to have to be managing EF state which is tricky. But I'll let you worry about that later.

    Are you sure about your ContactType structure? It has a nav property to ContactDetail but that means you have a need to fetch all contacts (across all customers) that have a particular type. This seems like a perf issue just waiting to happen. I don't see ContactType being anything more than a lookup table so it wouldn't have nav properties.

    As for the double save issue it is because of how you're building your view model. In your view model you separate out the customer from the contact (of which your model allows one). When you are trying to add EF isn't looking at your model's single contact (because it knows nothing about your viewmodel). It is looking at the customer's ContactDetails property. So before adding the customer do this.

    var customer = viewModel.Customer;
    customer.ContactDetails.Add(viewModel.ContactDetail);
    customer = _unitOfWork.Customers.Add(customer);
    _unitOfWork.SaveAllChanges();
    

    By adding to the customer you are letting EF know about the relationship that was implied in the VM. Additionally notice that I'm capturing the result of calling Add. This is important because that is the proxy that EF will update on save. You need to use it to get the added data after save. For example it will have the IDs from the DB and the ContactDetails will contain the updated info as well.