Kvr.Query
1.0.0
dotnet add package Kvr.Query --version 1.0.0
NuGet\Install-Package Kvr.Query -Version 1.0.0
<PackageReference Include="Kvr.Query" Version="1.0.0" />
<PackageVersion Include="Kvr.Query" Version="1.0.0" />
<PackageReference Include="Kvr.Query" />
paket add Kvr.Query --version 1.0.0
#r "nuget: Kvr.Query, 1.0.0"
#:package Kvr.Query@1.0.0
#addin nuget:?package=Kvr.Query&version=1.0.0
#tool nuget:?package=Kvr.Query&version=1.0.0
đ Kvr.Query
Copyright Š 2025 Kvr.Query. All rights reserved.
A lightweight extension for Dapper that provides type-safe querying of related entities with support for one-to-many and one-to-one relationships.
đ Table of Contents
- ⨠Features
- đĻ Installation
- đ Quick Start Guide
- đ Usage
- đ Extension Methods
- đ Fluent API
- đ Automatic Deduplication
- đ Key Detection
- đĄ Best Practices
- â ī¸ Limitations
- đ§ Supported Frameworks
- đ Version History
- đ License
- đ¤ Contributing
- đĻ Dependencies
- đŦ Support
⨠Features
- đ Fluent API for querying related entities
- đ Automatic key detection (primary & foreign)
- đ Support for one-to-many and one-to-one relationships
- đĻ Nested relationship querying (ThenInclude)
- đĄī¸ Type-safe property selection
- đ WHERE and ORDER BY clause support
- đ Minimal boilerplate code
- đˇī¸ Support for Data Annotations
đĻ Installation
You can install the package via NuGet Package Manager:
dotnet add package Kvr.Query
đ Quick Start Guide
Here is a quick start guide to get you up and running with Kvr.Query.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
public int UserProfileId { get; set; }
public UserProfile UserProfile { get; set; }
}
public class Order
{
public int Id { get; set; }
public int UserId { get; set; }
public string Description { get; set; }
public User User { get; set; }
public PaymentMethod PaymentMethod { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class Address
{
public int Id { get; set; }
public int UserId { get; set; }
public string Street { get; set; }
public User User { get; set; }
}
public class PaymentMethod
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Method { get; set; }
public Order Order { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Description { get; set; }
public Order Order { get; set; }
}
// Query all orders for a user with id 1
// Orders include User, PaymentMethod and OrderItems
// User includes Address
var orders = await connection.Select<Order>()
.IncludeOne(o => o.User)
.ThenIncludeOne(u => u.Address)
.ThenIncludeOne(u => u.UserProfile)
.IncludeOne(o => o.PaymentMethod)
.IncludeMany(o => o.OrderItems)
.Where<User>(u => u.Name, "@userId")
.QueryAsync(new { userId = 1 });
The above query will return all orders for a user with id 1, including the user's address and payment method, and the order items.
It will use primary keys and foreign keys conventions to detect the relationships.
If you want to use attributes to define the relationships, you can use the Key
, ForeignKey
, InverseProperty
attributes same as in Entity Framework.
Or you could use expressions to define the relationships.
var orders = await connection.Select<Order>(o => o.Id)
.Include(o => o.User, o => o.UserId, u => u.Id)
.ThenInclude(u => u.Address, u => u.AddressId, a => a.Id)
.ThenInclude(u => u.UserProfile, u => u.UserProfileId, up => up.Id)
.Include(o => o.PaymentMethod, o => o.PaymentMethodId, p => p.Id)
.Include(o => o.OrderItems, oi => oi.OrderId, i => i.Id)
.Where<User>(u => u.Name, "@userId")
.QueryAsync(new { userId = 1 });
đ Usage
đ Extension Methods to start a query for a specific entities
- đ
IDbConnection.Select<T>()
- Creates a query for an entity - đ
IDbConnection.Select<T>(Expression<Func<T, object>> keySelector)
- Creates a query with a specific key selector- đ
keySelector
: The key selector to use for the query.
- đ
đ Fluent API to include related entities
- đ One-to-many relationships
IncludeMany<TChild>(Expression<Func<TParent, ICollection<TChild>>> navigationProperty, Expression<Func<TChild, object>>? foreignKeyProperty = null, Expression<Func<TChild, object>>? childPrimaryKeyProperty = null, Expression<Func<TChild, object>>[]? excludeColumns = null)
- đ
navigationProperty
: The navigation property to include. - đ
foreignKeyProperty
: Optional. The foreign key property to use for the relationship. If not provided, the foreign key property will be detected by convention and attribute. - đ
childPrimaryKeyProperty
: Optional. The primary key property to use for the relationship. If not provided, the primary key property will be detected by convention and attribute. - â
excludeColumns
: Optional. The columns to exclude from the query, normally not needed, but can be used to exclude columns from the query.
- đ
- đ One-to-one or many-to-one relationships
- đĨ
IncludeOne<TChild>(Expression<Func<TParent, TChild>> navigationProperty, Expression<Func<TParent, object>>? navigationForeignKeyProperty = null, Expression<Func<TChild, object>>? childPrimaryKeyProperty = null, bool includeNavigationKeyInSql = false, Expression<Func<TChild, object>>[]? excludeColumns = null)
- đ
navigationProperty
: The navigation property to include. - đ
navigationForeignKeyProperty
: Optional. The foreign key property to use for the relationship. If not provided, the foreign key property will be detected by convention and attribute. - đ
childPrimaryKeyProperty
: Optional. The primary key property to use for the relationship. If not provided, the primary key property will be detected by convention and attribute. - âĄ
includeNavigationKeyInSql
: Optional. If true, the navigation key will be included in the SQL query. - â
excludeColumns
: Optional. The columns to exclude from the query, normally not needed, but can be used to exclude columns from the query.
- đ
- đĨ
- đŗ Nested relationships
- â ī¸ only supports 2 levels of nested relationships.
- 1ī¸âŖ first level is defined by using
IncludeOne
one-to-one relationship method orIncludeMany
one-to-many relationship method. - 2ī¸âŖ second level only supports
ThenIncludeOne
one-to-one relationship method. - đ could include multiple
ThenIncludeOne
methods to define same level of nested relationship to one entity (e.g.ThenInclude(u => u.UserProfile).ThenInclude(up => up.User)
) - âšī¸ It is different from EF, it does not support
ThenIncludeOne
to higher level nested relationships. And not needed to callInclude
method to return to parent entity then callThenInclude
method to include another child entity. ThenIncludeOne<TGrandChild>(Expression<Func<TChild, TGrandChild>> navigationProperty, Expression<Func<TChild, object>>? navigationKeyProperty = null, Expression<Func<TGrandChild, object>>? grandChildPrimaryKeyProperty = null)
- đ
navigationProperty
: The navigation property to include. - đ
navigationKeyProperty
: Optional. The foreign key property to use for the relationship. If not provided, the foreign key property will be detected by convention and attribute. - đ
grandChildPrimaryKeyProperty
: Optional. The primary key property to use for the relationship. If not provided, the primary key property will be detected by convention and attribute.
- đ
// Entity Framework Include and ThenInclude
var orders = await context.Orders
.Include(o => o.User)
.ThenInclude(u => u.Address)
.Include(o => o.User)
.ThenInclude(a => a.UserProfile)
....
// Kvr.Query IncludeOne/IncludeMany and ThenIncludeOne
var orders = await connection.Select<Order>()
.IncludeOne(o => o.User)
.ThenIncludeOne(u => u.Address)
.ThenIncludeOne(u => u.UserProfile)
....
đ Automatic remove duplication children entities
- đ If an entity includes multiple
IncludeMany
methods, the cartesian cross product of child entities will be returned fromDapper
query (e.g. If order has multipleOrderItems
and multiplePaymentMethods
, the query will return the cartesian cross product ofOrderItems
andPaymentMethods
). - âģī¸
QueryAsync
method will automatically remove duplication children entities usingDistinct
method by primary key property.
đ Primary Key and Foreign Key Detection order
- đģ Expressions to define primary key and foreign key properties on
IncludeOne
,IncludeMany
,ThenIncludeOne
methods: - đˇī¸ Attribute to define the primary key and foreign key properties entities:
- đ
Key
attribute is used to define the primary key. - đ
ForeignKey
attribute is used to define the foreign key. - âŠī¸
InverseProperty
attribute is used to define the inverse property.
- đ
- đ Convention to define the primary key and foreign key properties on entities:
- đ Primary key property fields:
Id
.$"{typeof(T).Name}Id"
is used as the primary key.
- đ Foreign key property fields:
$"{navigationPropertyInfo.Name}Id"
.$"{navigationPropertyInfo.PropertyType.Name}Id"
is used as the foreign key.
- đ Primary key property fields:
- đ Please see the test case to detect the primary key and foreign key properties: UtilsTests.cs
đ Best Practices
- đ Use conventions to automatically detect the primary keys and foreign keys in relationship navigation properties.
- đˇī¸ Use attributes to define the relationships if you want to use Data Annotations.
- đ
Key
attribute is used to define the primary key. - đ
ForeignKey
attribute is used to define the foreign key. - âŠī¸
InverseProperty
attribute is used to define the inverse property.
- đ
- ⥠Use expression to define keys explicitly.
đĢ Limitations
- Only supports 2 levels of relationships.
- First level relationships are supported by using
IncludeOne
method andIncludeMany
method. - Second level relationships are only supported by using
ThenIncludeOne
method.
- First level relationships are supported by using
- Only includes the same navigation property once, not detection for duplicate navigation properties.
Key
andForeignKey
attributes only support one property, not supporting composite keys.- Lazy loading is not supported, all requests for related entities are eager loading.
đ§ Supported Frameworks
- .NET Standard 2.0+
- .NET 5.0+
- .NET 6.0+
- .NET 7.0+
đ Version History
- 1.0.0
- Initial release with support for one-to-many and one-to-one relationships by using conventions, attributes and expressions.
đ License
Apache License 2.0 - see LICENSE
đ¤ Contributing
Contributions welcome! Please read our Contributing Guide
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
đĻ Dependencies
- DapperRelMapper - Relationship mapping extension for Dapper
- SqlBuilder
đŦ Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
đī¸ Build Status
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Kvr.DapperRelMapper (>= 1.2.4)
- Kvr.SqlBuilder (>= 1.2.7.1)
-
net5.0
- Kvr.DapperRelMapper (>= 1.2.4)
- Kvr.SqlBuilder (>= 1.2.7.1)
-
net6.0
- Kvr.DapperRelMapper (>= 1.2.4)
- Kvr.SqlBuilder (>= 1.2.7.1)
-
net7.0
- Kvr.DapperRelMapper (>= 1.2.4)
- Kvr.SqlBuilder (>= 1.2.7.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.0 | 126 | 12/19/2024 |