--- title: "Relations" description: "Define and query relationships between entities." canonical: "https://roomsharp.dev/docs/v0.5.3/relations" source: "src/content/v0.5.3/relations.mdx" --- # Relations RoomSharp supports relationships between entities through the `@Relation` attribute and embedded types. ## Relation Basics Use `[Relation]` to define parent-child relationships between entities: ```csharp [Entity(TableName = "users")] public class User { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } public required string Name { get; set; } } [Entity(TableName = "posts")] public class Post { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } [ForeignKey(typeof(User))] public long AuthorId { get; set; } public required string Title { get; set; } } // Result type combining both public class UserWithPosts { [Embedded] public required User User { get; set; } [Relation(ParentColumn = "Id", EntityColumn = "AuthorId")] public List Posts { get; set; } = []; } ``` ## Loading Relations Query methods can return relation types: ```csharp [Dao] public interface IUserDao { [Query("SELECT * FROM users WHERE Id = :id")] Task GetUserWithPostsAsync(long id); [Query("SELECT * FROM users ORDER BY Name")] Task> GetAllUsersWithPostsAsync(); } ``` Relations are loaded eagerly by default. RoomSharp generates the necessary JOIN or secondary queries automatically. ## Many-to-Many Relations For many-to-many relationships, use a junction table: ```csharp [Entity(TableName = "users")] public class User { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } public required string Name { get; set; } } [Entity(TableName = "groups")] public class Group { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } public required string Name { get; set; } } [Entity(TableName = "user_groups")] public class UserGroup { [ForeignKey(typeof(User))] public long UserId { get; set; } [ForeignKey(typeof(Group))] public long GroupId { get; set; } } // Result types public class UserWithGroups { [Embedded] public required User User { get; set; } [Relation( ParentColumn = "Id", EntityColumn = "UserId", AssocEntity = typeof(UserGroup), AssocParentColumn = "UserId", AssocEntityColumn = "GroupId", ProjectedType = typeof(Group) )] public List Groups { get; set; } = []; } ``` ## Embedded Types Use `[Embedded]` to include all columns from another type inline: ```csharp public class Address { public string Street { get; set; } = ""; public string City { get; set; } = ""; public string Country { get; set; } = ""; } [Entity(TableName = "users")] public class User { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } public required string Name { get; set; } [Embedded(Prefix = "home_")] public Address HomeAddress { get; set; } = new(); [Embedded(Prefix = "work_")] public Address WorkAddress { get; set; } = new(); } ``` The table would have columns: `Id`, `Name`, `home_Street`, `home_City`, `home_Country`, `work_Street`, `work_City`, `work_Country`. ## Relation Attributes | Attribute | Description | |-----------|-------------| | `ParentColumn` | Column on the parent entity to match | | `EntityColumn` | Column on the child entity to match | | `AssocEntity` | Junction table entity for many-to-many | | `AssocParentColumn` | Junction column referencing parent | | `AssocEntityColumn` | Junction column referencing child | | `ProjectedType` | The actual type to project (for many-to-many) | ## Performance Considerations Loading deep relation hierarchies can result in many database round-trips. Consider: - Using projections to select only needed columns - Breaking complex relations into separate queries - Using `NoTracking = true` for read-only scenarios For complex queries involving multiple relations, consider using `RoomSharp.QueryExtensions` with explicit JOINs for better control over query execution. --- ## RelationLoader `RoomSharp.Relations.RelationLoader` can populate relation properties after a query finishes. Extension methods `WithRelationsAsync` exist for single entities and lists: ```csharp public class UserWithPosts { [Embedded] public User User { get; set; } = default!; // Expose key used by RelationLoader public long Id => User.Id; [Relation(Entity = typeof(Post), ParentColumn = "Id", EntityColumn = "UserId")] public List Posts { get; set; } = new(); } var user = await db.UserDao.GetByIdAsync(id); var hydrated = await user.WithRelationsAsync(db.GetConnection(), logger); ``` --- ## Paging & LiveData `RoomSharp.Paging` offers `PagingSource`, `Pager`, and helper DTOs for incremental loading using callbacks that RoomSharp DAOs can feed. ```csharp var pagingSource = new PagingSource((page, size) => { // Delegate to DAO using LIMIT/OFFSET query return db.TodoDao.GetPage(page * size, size); }); await foreach (var page in new Pager(pagingSource).GetPagesAsync()) { Render(page); } ``` ### LiveData `RoomSharp.Core.LiveData` provides observer-based in-memory streams. It works well together with paging or relation loaders for simple reactive scenarios. ```csharp var liveData = new LiveData(() => db.TodoDao.GetActiveCountAsync().GetAwaiter().GetResult()); liveData.Observe(count => Console.WriteLine($"Active todos: {count}")); await liveData.RefreshAsync(); ``` For more advanced reactive scenarios, see the [Reactive Queries](/docs/v0.5.3/reactive-queries) page.