Relations
A property typed as another mapped class or as a list of one, is treated as a relation. Componyx.Data
loads relations automatically as part of the same query, using a single set of joins rather than a
separate round trip per relation.
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public decimal Total { get; set; }
public Customer Customer { get; set; }
}
A list property (Orders) is treated as one-to-many. A single class-typed property
(Customer on Order) is treated as one-to-one. Both directions of the
relation are resolved from the same foreign key, CustomerId, so one property is
enough to describe it on the child side.
Componyx.Data looks for a [ForeignKey] attribute on the foreign key property first.
If none is present and AutoDetectForeignKeys is enabled on the DAL
instance, it falls back to naming convention: a property named as the parent class plus its key
property, CustomerId for a foreign key pointing at Customer.Id. With
that convention in place, CustomerId alone is enough. No attribute is required.
Where the naming does not follow that convention, decorate the foreign key property directly.
ForeignKeyAttribute has two constructors.
The type-based overload takes the parent type and the name of its key property. It resolves the
actual table and column names for you, correctly even if a mapped column name differs from the
property name, so this is the one to reach for by default.
public class Order
{
[ForeignKey(typeof(Customer), nameof(Customer.Id))]
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
The string-based overload takes the parent table and column name directly, for cases where you
do not have a Type to reference.
public class Order
{
[ForeignKey(pkTable: "Customer", pkColumn: "Id")]
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
Disambiguating multiple relations to the same type
A class can have more than one foreign key pointing at the same parent type, a
BillingCustomer and a ShippingCustomer both referencing
Customer, for example. Pass the name of the navigation property as the
relationalProperty argument on each attribute so Componyx.Data can tell them apart
when resolving the one-to-one back-reference.
public class Order
{
public int Id { get; set; }
[ForeignKey(typeof(Customer), nameof(Customer.Id), nameof(BillingCustomer))]
public int BillingCustomerId { get; set; }
public Customer BillingCustomer { get; set; }
[ForeignKey(typeof(Customer), nameof(Customer.Id), nameof(ShippingCustomer))]
public int ShippingCustomerId { get; set; }
public Customer ShippingCustomer { get; set; }
}
This disambiguation only applies to the attribute-based overloads. Naming-convention detection
does not support more than one foreign key to the same type on a single class.
Relations load recursively, so a relation's own relations load too, with cycle protection so a
self-referencing or mutually referencing pair of classes does not recurse forever. Querying
Order directly loads its Customer as a one-to-one relation. Querying
Customer, which already loads Orders as one-to-many, does not reload
Order.Customer for each order, since that would just be the same
Customer already in hand.