-
Notifications
You must be signed in to change notification settings - Fork 226
Settings.ForeignKeyName
Names the navigation properties a foreign key produces, and is called repeatedly until the name it returns is unique.
| Type | Func<string, ForeignKey, string, Relationship, short, string> |
| Parameters | (tableName, foreignKey, foreignKeyName, relationship, attempt) |
| Default | A set of rules that strip an Id suffix, fall back to the table name, and add a number as a last resort |
| Applies to | EF 6 and EF Core |
| Databases | All |
In Database.tt? |
Yes, and it is the longest block in the file |
A foreign key produces up to two navigation properties: one on the child pointing at the parent
(Product.Category) and one on the parent holding the children (Category.Products). Naming them is
surprisingly hard, because the obvious name is frequently taken - by the table's own name, by one of its
columns, or by another foreign key to the same table.
So the generator does not ask once. It calls this callback, checks whether the answer collides with anything
already used on that table, and if it does, calls again with a higher attempt number. Your job is to return
a name; its job is to keep asking until one sticks.
attempt |
What the generator is trying |
|---|---|
| 1 | The foreign key column name, PascalCased - CategoryId
|
| 2 | The same with a trailing Id stripped - Category. Only tried when the name ends in id
|
| 3 | Another go on the child side, for the awkward cases |
| 4 | The parent table name |
| 5 | The same again, with 1, 2, 3… appended until one is free |
| 6 | Whatever you return is used, collision or not |
The numbers are part of the public contract and are never renumbered, so branching on attempt is safe.
The other parameters: tableName is the other end's name (already singular or plural as appropriate),
foreignKeyName is the candidate the generator built, relationship is OneToMany, ManyToOne,
OneToOne or ManyToMany, and foreignKey gives you FkColumn, PkColumn, ConstraintName, FkTableName
and PkTableName for matching.
Renaming one relationship by matching on its column:
Settings.ForeignKeyName = delegate(string tableName, ForeignKey foreignKey, string foreignKeyName, Relationship relationship, short attempt)
{
if (foreignKey.FkColumn == "CategoryId" && attempt == 1)
return "Group";
return tableName;
}; // Product
public class Product
{
public int ProductId { get; set; } // ProductId (Primary key)
public string ProductName { get; set; } // ProductName (length: 100)
public decimal UnitPrice { get; set; } // UnitPrice
public string Notes { get; set; } // Notes
public int CategoryId { get; set; } // CategoryId
public string DisplayLabel { get; private set; } // DisplayLabel (length: 150)
// Foreign keys
/// <summary>
/// Parent Category pointed by [Product].([CategoryId]) (FK_Product_Category)
/// </summary>
public Category Category { get; set; } // FK_Product_Category
public Product()
{
UnitPrice = 0m;
}
} // Product
public class Product
{
public int ProductId { get; set; } // ProductId (Primary key)
public string ProductName { get; set; } // ProductName (length: 100)
public decimal UnitPrice { get; set; } // UnitPrice
public string Notes { get; set; } // Notes
public int CategoryId { get; set; } // CategoryId
public string DisplayLabel { get; private set; } // DisplayLabel (length: 150)
// Foreign keys
/// <summary>
/// Parent Category pointed by [Product].([CategoryId]) (FK_Product_Category)
/// </summary>
public Category Group { get; set; } // FK_Product_Category
public Product()
{
UnitPrice = 0m;
}
}Product.Category became Product.Group. The XML comment still names the real constraint, so the mapping
back to the database is never lost.
Encoding the names in the constraint. If your team names constraints
FK_Child_ParentNavName_ChildNavName, you can drive both navigation property names from the database:
Settings.ForeignKeyName = delegate(string tableName, ForeignKey fk, string fkName, Relationship relationship, short attempt)
{
var parts = fk.ConstraintName.Split('_');
if (fk.ConstraintName.StartsWith("FK_") && parts.Length == 4 && parts[1] == fk.FkTableName)
{
if (relationship == Relationship.OneToMany) return parts[3];
if (relationship == Relationship.ManyToOne) return parts[2];
}
return tableName;
};Self-referencing keys, where the default names are rarely what you want. Employee.ReportsTo reads far
better as Employee.Manager:
if (fk.FkTableName == "Employee" && fk.FkColumn == "ReportsTo")
return "Manager";Two foreign keys to the same table. Order.ShipToAddress and Order.BillToAddress instead of Address
and Address1.
Always return something. Returning null or an empty string generates a property with no name, which does
not compile. When your rule does not apply, return tableName or foreignKeyName rather than falling off the
end.
Guard on attempt or you will fight the collision resolver. Returning the same constant for every attempt
means the generator asks five times, gets the same taken name each time, and lands on attempt 6 - which it
uses regardless of the collision. Either branch on attempt == 1 as above, or make sure your name is
genuinely unique.
It is called for both ends of the relationship. relationship tells you which end you are naming;
ignoring it renames both.
A name you supply through Settings.AddExtraForeignKeys
wins over this callback. ParentName and ChildName on a manually added relationship are used verbatim and
this is never consulted for them.
Renaming does not change the mapping. HasForeignKey still points at the real column; only the C# property
name moves.
- Settings.ForeignKeyFilterFunc - excluding a relationship, or suppressing just the reverse navigation
- Settings.ForeignKeyAnnotationsProcessing - attributes on navigation properties
- Settings.AddExtraForeignKeys - relationships the database does not declare
- Settings Callbacks | Settings Reference
- Settings A-Z - every setting, with a page each
- Common Settings Types Explained
- Settings Callbacks
- Settings runtime values and helpers
- Filtering
- Full Control Over the Generated Code
- Enum Generation from Table Data
- Owned Entities
- JSON column support
- Global Query Filters
- Extended Property Names Feature
- Partial Properties
- File-Scoped Namespaces
- Data Annotations
- Spatial Types
- HierarchyId
- RowVersion and TimeStamp columns
- Lazy Loading
- Stored proc result sets
- Custom File-Based Templates
- Extra entities via partial classes
- INotifyPropertyChanged
- Syntax colour for T4