OData V4 supports managing relationships between entities using entity references ($ref). This allows you to link and unlink entities without affecting the entities themselves.
- Overview
- Adding References (Collection)
- Removing References (Collection)
- Setting References (Single-Valued)
- Deleting References (Single-Valued)
Entity references manage navigation property relationships:
- Collection navigation properties: Can have multiple related entities (e.g., Order.Items)
- Single-valued navigation properties: Can have one related entity (e.g., Product.Category)
Reference operations only affect the relationship, not the entities themselves.
Add an entity to a collection navigation property:
// Add a product to a category's Products collection
// POST Categories(1)/Products/$ref
// Body: { "@odata.id": "Products(42)" }
await client.AddReferenceAsync(
"Categories", // Source entity set
1, // Source key
"Products", // Navigation property
"Products", // Target entity set
42 // Target key
);// Add employee to department's Employees collection
await client.AddReferenceAsync(
"Departments",
"Engineering", // Department key
"Employees", // Navigation property
"Employees", // Target entity set
"EMP001" // Employee key
);// Add a friend relationship
await client.AddReferenceAsync(
"People",
"russellwhyte", // Person username
"Friends", // Navigation property
"People", // Target entity set
"scottketchum" // Friend username
);Remove an entity from a collection navigation property:
// Remove a product from a category
// DELETE Categories(1)/Products/$ref?$id=Products(42)
await client.RemoveReferenceAsync(
"Categories", // Source entity set
1, // Source key
"Products", // Navigation property
"Products", // Target entity set
42 // Target key to remove
);await client.RemoveReferenceAsync(
"Departments",
"Engineering",
"Employees",
"Employees",
"EMP001"
);await client.RemoveReferenceAsync(
"People",
"russellwhyte",
"Friends",
"People",
"scottketchum"
);Set a single-valued navigation property to a specific entity:
// Set a product's category
// PUT Products(42)/Category/$ref
// Body: { "@odata.id": "Categories(1)" }
await client.SetReferenceAsync(
"Products", // Source entity set
42, // Source key
"Category", // Navigation property (single-valued)
"Categories", // Target entity set
1 // Target key
);// Set an employee's manager
await client.SetReferenceAsync(
"Employees",
"EMP001", // Employee
"Manager", // Navigation property
"Employees", // Manager is also an Employee
"MGR001" // Manager key
);// Associate an order with a customer
await client.SetReferenceAsync(
"Orders",
orderId,
"Customer",
"Customers",
"ALFKI"
);Clear a single-valued navigation property (set to null):
// Remove the category from a product
// DELETE Products(42)/Category/$ref
await client.DeleteReferenceAsync(
"Products", // Source entity set
42, // Source key
"Category" // Navigation property to clear
);// Clear employee's manager reference
await client.DeleteReferenceAsync(
"Employees",
"EMP001",
"Manager"
);var headers = new Dictionary<string, string>
{
{ "If-Match", "*" } // Optional: concurrency control
};
await client.AddReferenceAsync(
"Categories",
1,
"Products",
"Products",
42,
headers
);// Scenario: Managing products in an order
// 1. Add products to order
await client.AddReferenceAsync("Orders", orderId, "Items", "Products", 101);
await client.AddReferenceAsync("Orders", orderId, "Items", "Products", 102);
await client.AddReferenceAsync("Orders", orderId, "Items", "Products", 103);
// 2. Remove a product from order
await client.RemoveReferenceAsync("Orders", orderId, "Items", "Products", 102);
// 3. Set the shipping address (single-valued)
await client.SetReferenceAsync("Orders", orderId, "ShippingAddress", "Addresses", addressId);
// 4. Clear the shipping address
await client.DeleteReferenceAsync("Orders", orderId, "ShippingAddress");// Scenario: Managing friend relationships
var currentUser = "russellwhyte";
var newFriend = "scottketchum";
// Add friend
await client.AddReferenceAsync(
"People", currentUser, "Friends",
"People", newFriend
);
// The relationship might be bidirectional
await client.AddReferenceAsync(
"People", newFriend, "Friends",
"People", currentUser
);
// Remove friend
await client.RemoveReferenceAsync(
"People", currentUser, "Friends",
"People", newFriend
);public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
// Single-valued navigation (use SetReferenceAsync/DeleteReferenceAsync)
public Category? Category { get; set; }
// Collection navigation (use AddReferenceAsync/RemoveReferenceAsync)
public List<Tag> Tags { get; set; } = [];
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
// Collection navigation
public List<Product> Products { get; set; } = [];
}| Approach | Use Case |
|---|---|
| Reference operations ($ref) | Managing relationships only |
| PATCH with foreign key | Update entity and relationship together |
| Deep insert | Create entity with related entities |
// Using $ref - only changes relationship
await client.SetReferenceAsync("Products", 42, "Category", "Categories", 1);
// Using PATCH - updates entity property directly
await client.UpdateAsync<Product>("Products", 42, new { CategoryId = 1 });