ChatGPT解决这个技术问题 Extra ChatGPT

How Should I Declare Foreign Key Relationships Using Code First Entity Framework (4.1) in MVC3?

I have been searching for resources on how to declare foreign key relationships and other constraints using code first EF 4.1 without much luck. Basically I am building the data model in code and using MVC3 to query that model. Everything works via MVC which is great (kudos to Microsoft!) but now I want it NOT to work because I need to have data model constraints.

For example, I have a Order object that has a ton of properties that are external objects (tables). Right now I can create an Order no problem, but without being able to add the foreign key or external objects. MVC3 sets this up no problem.

I realize that I could just add the objects myself in the controller class prior to save, but I would like the call to DbContext.SaveChanges() to fail if the constraint relationships have not been met.

NEW INFORMATION So, specifically, I would like an exception to occur when I attempt to save an Order object without specifying a customer object. This does not seem to be the behavior if I just compose the objects as described in most Code First EF documentation.

Latest code:

public class Order
{
    public int Id { get; set; }

    [ForeignKey( "Parent" )]
    public Patient Patient { get; set; }

    [ForeignKey("CertificationPeriod")]
    public CertificationPeriod CertificationPeriod { get; set; }

    [ForeignKey("Agency")]
    public Agency Agency { get; set; }

    [ForeignKey("Diagnosis")]
    public Diagnosis PrimaryDiagnosis { get; set; }

    [ForeignKey("OrderApprovalStatus")]
    public OrderApprovalStatus ApprovalStatus { get; set; }

    [ForeignKey("User")]
    public User User { get; set; }

    [ForeignKey("User")]
    public User Submitter { get; set; }

    public DateTime ApprovalDate { get; set; }
    public DateTime SubmittedDate { get; set; }
    public Boolean IsDeprecated { get; set; }
}

This is the error I get now when accessing the VS generated view for Patient:

ERROR MESSAGE The ForeignKeyAttribute on property 'Patient' on type 'PhysicianPortal.Models.Order' is not valid. The foreign key name 'Parent' was not found on the dependent type 'PhysicianPortal.Models.Order'. The Name value should be a comma separated list of foreign key property names.

Regards,

Guido


S
Sergi Papaseit

If you have an Order class, adding a property that references another class in your model, for instance Customer should be enough to let EF know there's a relationship in there:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    public virtual Customer Customer { get; set; }
}

You can always set the FK relation explicitly:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    [ForeignKey("Customer")]
    public string CustomerID { get; set; }
    public virtual Customer Customer { get; set; }
}

The ForeignKeyAttribute constructor takes a string as a parameter: if you place it on a foreign key property it represents the name of the associated navigation property. If you place it on the navigation property it represents the name of the associated foreign key.

What this means is, if you where to place the ForeignKeyAttribute on the Customer property, the attribute would take CustomerID in the constructor:

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

EDIT based on Latest Code You get that error because of this line:

[ForeignKey("Parent")]
public Patient Patient { get; set; }

EF will look for a property called Parent to use it as the Foreign Key enforcer. You can do 2 things:

1) Remove the ForeignKeyAttribute and replace it with the RequiredAttribute to mark the relation as required:

[Required]
public virtual Patient Patient { get; set; }

Decorating a property with the RequiredAttribute also has a nice side effect: The relation in the database is created with ON DELETE CASCADE.

I would also recommend making the property virtual to enable Lazy Loading.

2) Create a property called Parent that will serve as a Foreign Key. In that case it probably makes more sense to call it for instance ParentID (you'll need to change the name in the ForeignKeyAttribute as well):

public int ParentID { get; set; }

In my experience in this case though it works better to have it the other way around:

[ForeignKey("Patient")]
public int ParentID { get; set; }

public virtual Patient Patient { get; set; }

Thanks Sergi - I added some additional information in the block quote.
@Guido - I've updated my answer based in your latest code edit, hope this helps.
L
Leon Bambrick

You can define foreign key by:

public class Parent
{
   public int Id { get; set; }
   public virtual ICollection<Child> Childs { get; set; }
}

public class Child
{
   public int Id { get; set; }
   // This will be recognized as FK by NavigationPropertyNameForeignKeyDiscoveryConvention
   public int ParentId { get; set; } 
   public virtual Parent Parent { get; set; }
}

Now ParentId is foreign key property and defines required relation between child and existing parent. Saving the child without exsiting parent will throw exception.

If your FK property name doesn't consists of the navigation property name and parent PK name you must either use ForeignKeyAttribute data annotation or fluent API to map the relation

Data annotation:

// The name of related navigation property
[ForeignKey("Parent")]
public int ParentId { get; set; }

Fluent API:

modelBuilder.Entity<Child>()
            .HasRequired(c => c.Parent)
            .WithMany(p => p.Childs)
            .HasForeignKey(c => c.ParentId);

Other types of constraints can be enforced by data annotations and model validation.

Edit:

You will get an exception if you don't set ParentId. It is required property (not nullable). If you just don't set it it will most probably try to send default value to the database. Default value is 0 so if you don't have customer with Id = 0 you will get an exception.


Thanks Ladislav - I added some additional information in the block quote.
@Ladislav. So in order to enforce this constraint I MUST have both the reference to Parent and a reference to ParentId. Is that correct? I will add the actual class above for reference.
@Guido: That is the new information. You are not using foreign key properties. All your navigation properties are handled as optional by default. Use fluent mapping to map them as required.
@Ladislav: Thank you again. I am looking around to understand the differences between using Data Annotations and the Fluent API. I made the changes to the code above in line with what I think you are saying. Is the above all I have to do? Regards.
No ForeignKey attribute defines navigation property related to foreign key property or vice versa. You don't have foreign key properties so you can't use that attribute. Try to use Required attribute on your navigation properties (I didn't tested it).