简体   繁体   中英

Insert generated primary key as foreign key in different table from controller

I have multiple models and all of these models are in a view model. One of my action methods in controller is used to add data to the database.

I am using Entity Framework with code-first migrations

Primary keys of these tables are using Entity Framework ID's so these are automatically generated.

All these models are interdependent on each other with foreign keys. I am trying to insert the data into these model database tables at same time.

How I can grab the primary key of one table and insert as foreign key in different table?

Booking , Messages , and Items are different model classes

Booking model class

public class Booking
{
    [Key]
    [Column("lngBookingID")]
    public Int32 BookingID { get; set; }
    public double BookingCost { get; set; }
 }

Messages model class:

public class Messages
{
    [Key]
    [Column("lngMessageID")]
    public Int32 MessageID { get; set; }

    public string MessageSubject { get; set; }
    [ForeignKey("Booking")]
    public Int32 lngBookingID { get; set; }

    public Booking Booking { get; set; }
}

Code for action method. BookingViewModel bvm has all the data needed:

 _context.Booking.Add(bvm.Booking);
 _context.Messages.Add(bvm.Messages);
 _context.PetInformation.Add(bvm.items);
 _context.SaveChanges();

I would like generated Booking ID to be foreign key in messages table when I add it to database.

You need to ensure that the Booking reference in the Message points to the same instance as the Booking you are adding. EF will manage the FK association from there.

For instance:

bvm.Messages.Booking = bvm.Booking; // Associate the same reference.
_context.Booking.Add(bvm.Booking);
_context.Messages.Add(bvm.Messages);
_context.SaveChanges();

I would avoid passing Entity classes in a view model since this can lead to all kinds of problems as you may think that you are passing an entity, but you are really just passing a POCO instance of data that is not associated to the context. EF will treat each and every de-serialized instance as a new reference, even if they have matching IDs. You have to associate them with the DbContext, and update references to instances that might already be associated. It's ugly, error prone, and also prone to unauthorized data manipulation by malicious users.

For example: Take an scenario that takes the following data: Booking { Id = 0 }, Message { Id = 0, Booking { Id = 0 }} So it accepts a new Booking with ID 0, and a new message referencing our new booking. When we call context.Booking.Add(booking) EF will create a new booking with an auto-generated ID. Lets say, "15". When it get's to the message, the message contains a new, different booking reference, so EF goes and inserts that, and it gets Id 16. (Even though the rest of the data about the booking is identical to the first one passed in) Because the two booking references don't point to the same instance, they are treated as two completely separate instances by EF as well. By setting the message's booking reference to the first one in our method, when EF updates the Booking, the message's booking reference will point to that first one's ID.

To avoid all kinds of ugliness like this, we want to avoid the duplicate references. If we have a method that inserts a new booking with an optional message, then rather than passing Entities which pose problems like this, pass regular C# view models and then handle the entity creation (or load) on the server. Entities passed into methods like this are effectively just POCO classes, but they hold a lot more data than you should not "trust" to be updated to the database. By passing a separate view model we enforce a better practice of: Loading existing entity references from the current DBContext, and/or creating and associating entities based on the data provided.

So a method that creates a new booking with an optional message might look more like:

public ActionResult AddBooking(BookingViewModel booking, MessageViewModel message)
{
   var newBooking = new Booking { BookedDate = booking.BookedDate, /* ... */ };
   _context.Bookings.Add(newBooking);
   if (message != null)
   {
     var newMessage = new Message { MessageText = message.Text, Booking = newBooking };
     _context.Messages.Add(newMessage);
   }
   _context.SaveChanges();
}

Even better would be for the booking entity to have a collection of messages. I suspect you probably tried this initially but ran into serialization issues when trying to pass bookings to the client. (Another reason not to pass entities)

public ActionResult AddBooking(BookingViewModel booking, MessageViewModel message)
{
   var newBooking = new Booking { BookedDate = booking.BookedDate, /* ... */ };
   if (message != null)
   {
     var newMessage = new Message { MessageText = message.Text, Booking = newBooking };
     newBooking.Messages.Add(newMessage);
   }
   _context.Bookings.Add(newBooking);
   _context.SaveChanges();
}

This leverages the relationships between the entities so that the context doesn't have to treat every entity as a top level entity. When the booking is saved, it's messages will be saved as well and the FK references will be filled in automatically.

As you get into edit scenarios and such, continuing to pass entity references will lead to more pain with duplicate data, and context errors about entities with matching IDs already being tracked. It isn't worth the mess. :)

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