简体   繁体   中英

How do I create a table that has a composite key each member of which is a foreign key member to other tables

I am trying to do code first with annotations (for the first time) on an MVC project.

I have created the following POCOs.

[Table("Customers")]
public partial class Customer
{
    public int Id { get; set; }

    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }

    [DisplayName("Last Name")]
    [Required]
    public string LastName { get; set; }
    //other properties...
 }

[Table("Vehicles")]
public partial class Vehicle
{
    [Required]
    public int Id { get; set; }

    [Required]
    public int CustomerId { get; set; }

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

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

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

    //other fields
    [ForeignKey("CustomerId")]
    public virtual Customer Customer { get; set; }
}


[Table("CustomerAppointments")]
public partial class CustomerAppointment
{

    [Key,Column(Order=0)]
    public int CustomerId { get; set; }

    [Key,Column(Order=1)]
    public int VehicleId { get; set; }

    public DateTime? AppointmentDate { get; set; }

    public DateTime? AppointmentTime { get; set; }

    public string AvailableDays { get; set; }

    //other fields

    [ForeignKey("CustomerId")]
    public virtual Customer Customer { get; set; }

    [ForeignKey("VehicleId")]
    public virtual Vehicle Vehicle { get; set; }
}

I think my intent here is fairly obvious. I have customers. Those customers have vehicles. I want to create a table CustomerAppointments where a customer and one of the customers vehicles is scheduled for a service.

For the record, this is not the whole model and has been simplified for the purposes of the question.

I am using MvcScaffolding to build out the EF items and the views.

Everything compiles but when I try to navigate to the Customers page (actually a class not mentioned that references customers) I am getting the following error...

Introducing FOREIGN KEY constraint 'FK_dbo.CustomerAppointments_dbo.Vehicles_VehicleId' on table 'CustomerAppointments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

I have tried different annotations and even tried to use the fluent API with something like this...

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<CustomerAppointment>()
        .HasRequired(ca => ca.Customer)
        .WithRequiredPrincipal()
        .WillCascadeOnDelete(false);

    modelBuilder.Entity<CustomerAppointment>()
        .HasRequired(ca => ca.Vehicle)
        .WithRequiredPrincipal()
        .WillCascadeOnDelete(false);

}

But I cannot get it to work. I have read every sample I can find on google and SO but to no avail.

PS...if this can work with Annotations only that would be my preference.

Your model has two cascading delete paths from Customer to CustomerAppointment when a Customer is deleted:

  • Customer -> Vehicle -> CustomerAppointment
  • Customer -> CustomerAppointment

That's not allowed in SQL Server and causes the exception. You need to disable cascading delete for at least one of those three subpaths which is only possible with Fluent API. For example the Customer -> Vehicle path:

modelBuilder.Entity<Vehicle>()
    .HasRequired(v => v.Customer)
    .WithMany()
    .HasForeignKey(v => v.CustomerId)
    .WillCascadeOnDelete(false);

You could also make CustomerId nullable to have an optional relationship in which case EF will disable cascading delete by default. But changing a required to an optional relationship expresses a change in business rules which I wouldn't do just to avoid Fluent API.

BTW: Is it really correct that CustomerAppointment should have a composite primary key? It would mean that a given customer with a given vehicle could only have one service appointment. Couldn't there be many appointments for the same customer/vehicle combination at different appointment dates? If yes, you should rather have a separate key for CustomerAppointment and CustomerId and VehicleId would be just foreign keys without being part of the primary key.

By convention, cascade deletes are handled by the introduction of the actual foreign key into your model. If you use a non-nullable foreign key, it will require delete. Use a nullable foreign key to turn it off.

Change your class to the following by making the foreign keys nullable:

[Table("CustomerAppointments")]
public partial class CustomerAppointment
{

    [Key,Column(Order=0)]
    public int? CustomerId { get; set; }

    [Key,Column(Order=1)]
    public int? VehicleId { get; set; }

    public DateTime? AppointmentDate { get; set; }

    public DateTime? AppointmentTime { get; set; }

    public string AvailableDays { get; set; }

    //other fields

    [ForeignKey("CustomerId")]
    public virtual Customer Customer { get; set; }

    [ForeignKey("VehicleId")]
    public virtual Vehicle Vehicle { get; set; }
}

Remember to also remove the fluent mapping.

From http://msdn.microsoft.com/en-US/data/jj679962

If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null.

您最好使用数据库优先方法,然后使用ado enity数据模型生成模型。

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