首页 > 解决方案 > 在与控制器不同的表中插入生成的主键作为外键

问题描述

我有多个模型,所有这些模型都在一个视图模型中。我在控制器中的一种操作方法用于向数据库添加数据。

我正在使用带有代码优先迁移的实体框架

这些表的主键使用实体框架 ID,因此这些是自动生成的。

所有这些模型都通过外键相互依赖。我正在尝试将数据同时插入到这些模型数据库表中。

如何获取一个表的主键并作为外键插入到不同的表中?

Booking, Messages, 和Items是不同的模型类

Booking模型类

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

Messages模型类:

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; }
}

操作方法的代码。BookingViewModel bvm拥有所有需要的数据:

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

当我将它添加到数据库时,我希望生成的预订 ID 作为消息表中的外键。

标签: c#asp.net-mvcentity-frameworklinqentity-framework-6

解决方案


您需要确保 Message 中的 Booking 引用指向与您正在添加的 Booking 相同的实例。EF 将从那里管理 FK 协会。

例如:

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

我会避免在视图模型中传递实体类,因为这可能会导致各种问题,因为您可能认为您正在传递一个实体,但实际上您只是传递了一个与上下文无关的数据的 POCO 实例。EF 会将每个反序列化的实例视为一个新的引用,即使它们具有匹配的 ID。您必须将它们与 DbContext 关联,并更新对可能已经关联的实例的引用。它很丑陋,容易出错,而且还容易被恶意用户未经授权的数据操作。

举个例子:假设一个场景需要以下数据: Booking { Id = 0 }, Message { Id = 0, Booking { Id = 0 }} 所以它接受一个 ID 为 0 的新预订,以及一条引用我们新预订的新消息。当我们调用context.Booking.Add(booking)EF 时,将使用自动生成的 ID 创建一个新的预订。比方说,“15”。当它到达消息时,消息包含一个新的、不同的预订参考,所以 EF 去插入它,它得到 Id 16。(即使关于预订的其余数据与传入的第一个相同)因为这两个预订参考不指向同一个实例,所以它们也被 EF 视为两个完全独立的实例。通过在我们的方法中将消息的预订参考设置为第一个,当 EF 更新预订时,消息的预订参考将指向第一个的 ID。

为了避免像这样的各种丑陋,我们要避免重复引用。如果我们有一个插入带有可选消息的新预订的方法,那么与其传递造成此类问题的实体,不如传递常规 C# 视图模型,然后在服务器上处理实体创建(或加载)。传递给这样的方法的实体实际上只是 POCO 类,但它们包含的数据比你不应该“相信”更新到数据库的数据多得多。通过传递单独的视图模型,我们强制执行以下更好的做法:从当前 DBContext 加载现有实体引用,和/或根据提供的数据创建和关联实体。

因此,使用可选消息创建新预订的方法可能看起来更像:

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();
}

预订实体拥有一组消息会更好。我怀疑您最初可能尝试过此操作,但在尝试将预订传递给客户时遇到了序列化问题。(另一个不通过实体的原因)

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();
}

这利用了实体之间的关系,因此上下文不必将每个实体都视为顶级实体。当预订被保存时,它的消息也将被保存,FK 参考将被自动填写。

当您进入编辑场景等时,继续传递实体引用将导致重复数据更加痛苦,并且已经跟踪具有匹配 ID 的实体的上下文错误。这不值得一团糟。:)


推荐阅读