1

I'm trying to set the foreign key name (not the foreign key column) using Code First Migrations on EF6.4.

I know that it can be set by updating the generated migration code, like so:

.ForeignKey("Documents", Function(t) t.DocumentId, cascadeDelete:=True, name:="FK_Sections_Documents")

...but I'd like to do it before the migration is added, using the Fluent API.

I seem to recall something about the HasForeignKey() call accepting a Func that contains a call to an anonymous type in its body, such as what we find here. But I'll be darned if I can locate anything discussing what the general structure of that type should be.

The official documentation doesn't discuss it:

Nor do these similar Q&As quite exactly address the issue:

This same question was asked a couple of months ago here, but so far it hasn't received an answer.

I'm using EntityTypeConfiguration(Of T). Here's my code:

Namespace Configuration
  Friend Class SectionConfig
    Inherits EntityTypeConfiguration(Of Db.Section)

    Public Sub New()
      Me.HasRequired(Function(Section) Section.Document).WithMany.HasForeignKey(Function(Section) Section.DocumentId)

      Me.Property(Function(Section) Section.DocumentId).IsRequired()
      Me.Property(Function(Section) Section.SectionId).IsRequired()
      Me.Property(Function(Section) Section.IsSent).IsRequired()
      Me.Property(Function(Section) Section.Markup).IsRequired.IsMaxLength()
      Me.Property(Function(Section) Section.Title).IsRequired.HasMaxLength(60)

      Me.HasIndex(Function(Section) Section.DocumentId).HasName("IX_Sections_DocumentId")
      Me.HasIndex(Function(Section) Section.SectionId).HasName("IX_Sections_SectionId")
      Me.HasIndex(Function(Section) Section.Title).HasName("IX_Sections_Title")

      Me.Ignore(Function(Section) Section.Subject)
    End Sub
  End Class
End Namespace

How does one set a foreign key name, or—even more specific, assuming I'm remembering correctly—what should be the general structure of that anonymous type?

--UPDATE--

I tried this:

Me.HasRequired(Function(Section) Section.Document).WithMany.HasForeignKey(Function(Section) New With {.DependentKeyExpression = Section.DocumentId, .Name = "FK_Sections_Documents"})

...but a migration creation attempt answered with this:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: The properties expression 'Section => new VB$AnonymousType_0`2(DependentKeyExpression = Section.DocumentId, Name = "FK_Sections_Documents")' is not valid. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple properties use an anonymous type: C#: 't => new { t.MyProperty1, t.MyProperty2 }' VB.Net: 'Function(t) New With { t.MyProperty1, t.MyProperty2 }'.

So, given that the anonymous type construct is for specifying the key column(s), it's not the way to specify a foreign key name.

The question still stands: How may we specify the foreign key name using the Fluent API in EF6.4?

InteXX
  • 6,135
  • 6
  • 43
  • 80

2 Answers2

2

Unfortunately, in EF6 there is no built-in way to do this. To clarify your question a little, What you are asking is how to specify the name of the underlying database constraint that is created based on the foreign key specified in your model. This would be a job for the migration generator. Though the functionality is there in EF6 migrations to pass a constraint name, If you explore the source code you'll see it is never actually used, and all of the methods are private and internal.

This was recitfied in EFCore where you can now do: Me.HasRequired(Function(Section) Section.Document).WithMany().HasForeignKey(Function(Section) Section.DocumentId).HasConstraintName("FK_Sections_Documents") but that doesn't help you.

So what you need is to write a custom migration generator that will do what you want. This exact question was answered (code in C#, database is SQL Server) in this post: Change foreign key constraint naming convention

Dimitri
  • 1,188
  • 5
  • 7
  • That custom generator looks pretty good, although I'm having trouble getting it to work. The custom name doesn't appear in the generated migration code. Anyway, I've awarded the bounty because it's set to expire soon and I don't want you to lose it. Thanks for finding the link, and for the clarification as well. I suspected as much, and you've confirmed it. – InteXX Dec 26 '19 at 03:38
  • Which database and data provider are you using? – Dimitri Dec 26 '19 at 22:15
  • I'm using MSSQL. I wired it up as advised. I inserted a logging statement in `GetFkName()`, which reveals that the function is never hit during `Add-Migration`. Odd. – InteXX Dec 26 '19 at 23:36
0

Using Fluent Mappings, ForeignKey attibute expects a property name in your class as the argument.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<WidgetEntity>()
   .HasRequired(w => w.Sequence)
   .WithMany()
   .Map(m => m.MapKey("FK_Sections_Documents"));
}

LookupData Config:

HasRequired(p => p.LookupType).WithMany(p=>p.LookupData).HasForeignKey(p=>p.LookupTypeId).WillCascadeOnDelete(false);

Name Config:

HasOptional(p => p.Gender).WithMany(p=>p.Name).HasForeignKey(p=>p.GenderLookupId).WillCascadeOnDelete(false);

You can also use the InversePropertyAttribute, if you don't want to use fluent syntax, as the Inverse Property is easier to read and are written just above the property they are affecting.

Update:

Use ForeignKey with an associated property

Example 1:

[Table("ENTITIES")]
public class Entity {

    [Column("ENTITY_NO")]
    public int No { set; get; }

    [Column("SEQUENCE_NO")]
    public int SequenceNo { set; get; }

    [ForeignKey("SequenceNo")] //Has to be a property name, not table column name
    public Sequence Sequence { set; get; }

    // and other properties that map correctly
}

[Table("SEQUENCES")]
public class Sequence { 

    [Column("SEQUENCE_NO")]
    public int No { set; get; }

    [Column("NUMBER")]
    public int Number { set; get; }
}

Example 2:

[Table("ENTITIES")]
public class Entity {

    [Column("ENTITY_NO")]
    public int No { set; get; }

    [ForeignKey("Sequence")] //Has to be a property name, not table column name
    [Column("SEQUENCE_NO")]
    public int SequenceNo { set; get; }

    public Sequence Sequence { set; get; }

    // and other properties that map correctly
}

[Table("SEQUENCES")]
public class Sequence { 

    [Column("SEQUENCE_NO")]
    public int No { set; get; }

    [Column("NUMBER")]
    public int Number { set; get; }
}
InteXX
  • 6,135
  • 6
  • 43
  • 80
Sreeram Nair
  • 2,369
  • 12
  • 27
  • That *almost* works. Technically it does answer my question, so I've awarded the bounty, but there's still an unanticipated issue. `MapKey()` provides for a custom FK name, yes, but the generated migration adds a column with that same name to the table and uses the new column for the relation. There doesn't appear to be a way to use `MapKey()` and specify the target column at the same time. – InteXX Dec 21 '19 at 01:25
  • 2
    Oops, actually it *doesn't* work. I awarded the bounty a bit too soon, so go ahead and keep it with my compliments :-) Here's the problem: upon a closer look at `MapKey()` it turns out that it's for specifying the columns to be used in the key, not the actual foreign key name. This is not unlike the anonymous type approach under `HasForeignKey()`. The means for naming the FK—and doing ONLY that—still eludes us. – InteXX Dec 21 '19 at 01:31
  • You can use the Foreign Key with an associated property as updated in my answer – Sreeram Nair Dec 23 '19 at 06:57
  • Thanks, but these are data annotations. Unfortunately, it seems that customizing a foreign key name isn't possible using the Fluent API. – InteXX Jan 07 '20 at 13:56