Skip to content

Settings.ForeignKeyAnnotationsProcessing

Simon Hughes edited this page Aug 30, 2026 · 1 revision

Settings.ForeignKeyAnnotationsProcessing

Adds attributes to navigation properties, chosen per relationship.

Type Func<Table, Table, string, string, string[]>
Parameters (fkTable, pkTable, propName, fkPropName)
Returns Attribute names without brackets, or null for none
Default Returns null
Applies to EF 6 and EF Core
Databases All
In Database.tt? Yes, with a commented-out example

What it does

Navigation properties are the ones that break serialisation. Product.Category points at a Category, whose Products collection points back at the Product, and a naive JSON serialiser follows that cycle until it gives up. The usual fix is [JsonIgnore], and this is how you apply it selectively.

The callback runs per relationship and returns an array of attribute names. The generator wraps each in brackets and writes it above the property, so return "JsonIgnore", not "[JsonIgnore]".

The four parameters tell you which relationship you are looking at: fkTable is the table holding the foreign key, pkTable is the one being pointed at, propName is the navigation property about to be generated, and fkPropName is the underlying foreign key property.

Example

Settings.ForeignKeyAnnotationsProcessing = delegate(Table fkTable, Table pkTable, string propName, string fkPropName)
{
    return new[] { "System.Text.Json.Serialization.JsonIgnore" };
};
    // 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>
        [System.Text.Json.Serialization.JsonIgnore]
        public Category Category { get; set; } // FK_Product_Category

        public Product()
        {
            UnitPrice = 0m;
        }
    }

The attribute is written verbatim, which is why the fully qualified name works and needs no using. Return "JsonIgnore" on its own and add the namespace to Settings.AdditionalNamespaces if you prefer it short.

When to use it

Breaking serialisation cycles selectively. Suppress everything except the relationships an API actually returns:

Settings.ForeignKeyAnnotationsProcessing = delegate(Table fkTable, Table pkTable, string propName, string fkPropName)
{
    // Keep Order.Customer serialisable; hide everything else
    if (fkTable.NameHumanCase == "Order" && propName == "Customer")
        return null;

    return new[] { "System.Text.Json.Serialization.JsonIgnore" };
};

Validation or documentation attributes driven by the relationship rather than the column - [Required] on a navigation property whose foreign key is non-nullable, say.

Attributes for a specific serialiser, where you are targeting several. [IgnoreDataMember] for DataContractSerializer, [JsonIgnore] for System.Text.Json, both returned together.

Gotchas

Return the name without brackets. The generator adds them. Returning "[JsonIgnore]" generates [[JsonIgnore]].

Returning null means no attributes, which is also how you say "keep this one". There is no way to say "use the default" - there is no default. null and an empty array are the same thing.

It applies to navigation properties, not foreign key columns. To put attributes on the CategoryId property itself, use Settings.UpdateColumn and column.Attributes.

For blanket application, the two array settings are simpler. Settings.AdditionalReverseNavigationsDataAnnotations and Settings.AdditionalForeignKeysDataAnnotations apply the same attributes to every reverse navigation and every foreign key respectively, with no callback to write. Use this one when the choice varies by relationship.

Cutting the property beats hiding it, sometimes. If a reverse navigation is never used, removing it with Settings.ForeignKeyFilterFunc is cleaner than generating it and then telling every serialiser to skip it.

See also

Clone this wiki locally