2

I hit an issue when trying to delete records due to FK constraints. I therefore went back to the drawing board and am trying to specify how the relationship should work.

Here are my code first classes:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("DeferredDataId")]
    public virtual DeferredData DeferredData { get; set; }
}

public class DeferredData
{

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    //other properties
}

What I am looking to achieve is that the MemberDataSet has zero or one DeferredData. I can access DeferredData from MemberDataSet but DeferredData does not need a navigation property back to MemberDataSet. DeferredData should strictly require a MemberDataSet. In an ideal world deleting MemberDataSet will therefore delete DeferredData if assigned.

What seemed to me to be what I wanted to specify is this:

modelBuilder.Entity<MemberDataSet>().HasOptional(d => d.DeferredData).WithRequired().WillCascadeOnDelete(true);

i.e. MemberDataSet has an option DeferredData but DeferredData has a required MemberDataSet and this relationship should cascade on delete.

However, I then get an error:

The ForeignKeyAttribute on property 'DeferredData' on type 'MemberDataSet' is not valid. The foreign key name 'DeferredDataId' was not found on the dependent type 'DeferredData'. The Name value should be a comma separated list of foreign key property names.

Edit

After feeling happy with Sam's answer below I went ahead and changed a few other ForeignKey attributes. MemberDataSet has another property called SignedOffBy that is a userProfile. This previously looked like this:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("DeferredDataId")]
    public virtual DeferredData DeferredData { get; set; }

    public int? SignedOffById { get; set; }
    [ForeignKey("SignedOffId")]
    public virtual UserProfile SignedOffBy { get; set; }
}

After discussion below on what ForeignKey attribute is actually doing I changed this to:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("Id")]
    public virtual DeferredData DeferredData { get; set; }

    public int? SignedOffById { get; set; }
    [ForeignKey("UserId")]
    public virtual UserProfile SignedOffBy { get; set; }
}

However, I now get a very similar error message:

The ForeignKeyAttribute on property 'SignedOffBy' on type 'MemberDataSet' is not valid. The foreign key name 'UserId' was not found on the dependent type 'MemberDataSet'. The Name value should be a comma separated list of foreign key property names.

The difference here is that this relationship is Many to One i.e. 1 user can have several signedoff datasets. Is this what makes the difference? i.e. the UserProfile is now the principal object so the ForeignKey is on the MemberDataSet?

Many thanks again for any and all help.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
Mark007
  • 345
  • 1
  • 4
  • 8

1 Answers1

0

The error

The ForeignKeyAttribute on property 'DeferredData' on type 'MemberDataSet' is not valid. The foreign key name 'DeferredDataId' was not found on the dependent type 'DeferredData'.

is telling you exactly what is wrong.

DeferredData.Id is not DeferredData.DeferredDataId

This is your problem.

Just removing the attribute will solve your problem as Entity Framework figures out foreign keys based on the name of your entities. If you want to keep the attributes, use:

[ForeignKey("Id")]

instead of

[ForeignKey("DeferredDataId")]

So:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("Id")]
    public virtual DeferredData DeferredData { get; set; }
}

or change the Id of DeferredData to be DeferredDataId and not Id

A few notes about EF:

  1. Properties with names Id are automatically Keys, so no need for the Key attribute
  2. When you define a relationship using code first you don't need to manually decorate things with attributes, EF figures it out based on the structure.

Edit:

For a One-to-many relationship you need an ICollection<T>

public virtual ICollection<MemberDataSet> MemberDataSets { get; set; }

Does UserProfile have a UserId property?

Sam Leach
  • 12,746
  • 9
  • 45
  • 73
  • Thanks. I can see some logic here but am still slightly confused. I had thought that the attribute [ForeignKey("DeferredDataId")] was telling it that the ForeignKey for my navigation property was the DeferredDataId property of the MemberDataSet. Is that not right? – Mark007 Feb 18 '14 at 11:19
  • Nope, by using that attribute, you are stating that the key in `DeferredData` that you want to use as a foreign key in `MemberDataSet` is called `DeferredDataId`. It's not called `DeferredDataId`, it's called simply `Id`. – Sam Leach Feb 18 '14 at 11:22
  • Ok, this is starting to make sense. So what's the significance of public int? DeferredDataId { get; set; }? i.e. can this be deleted? Is it auto wired up? etc. – Mark007 Feb 18 '14 at 11:23
  • Ha! I can indeed. I presume it is needed as all the examples I've seen seem to have one. I therefore presume it has to have the format NavPropertyNameId to be auto attached. Or can you call it something different? If so how would you tell it what it is called? I had thought (wrongly as you have set out well above) that this is what the FK attribute was for (hence the decoration! As an aside I also quite like adding the specific attributes so I am clear myself what is being done) – Mark007 Feb 18 '14 at 11:34
  • After reading numerous pages I am still non the wiser to the answer to this: "I therefore presume it has to have the format NavPropertyNameId to be auto attached. Or can you call it something different? If so how would you tell it what it is called?" Thanks for your help. – Mark007 Feb 18 '14 at 11:49
  • @Mark007, you should open another question. – Sam Leach Feb 18 '14 at 12:00
  • Thanks - may well do so. Did think that might make sense. I have however added a follow up which I think directly relates to the first question as the error message is the same. – Mark007 Feb 18 '14 at 12:11