Raiqub.Expressions.Reading
2.0.4
Prefix Reserved
See the version list below for details.
dotnet add package Raiqub.Expressions.Reading --version 2.0.4
NuGet\Install-Package Raiqub.Expressions.Reading -Version 2.0.4
<PackageReference Include="Raiqub.Expressions.Reading" Version="2.0.4" />
paket add Raiqub.Expressions.Reading --version 2.0.4
#r "nuget: Raiqub.Expressions.Reading, 2.0.4"
// Install Raiqub.Expressions.Reading as a Cake Addin #addin nuget:?package=Raiqub.Expressions.Reading&version=2.0.4 // Install Raiqub.Expressions.Reading as a Cake Tool #tool nuget:?package=Raiqub.Expressions.Reading&version=2.0.4
Expressions
Raiqub.Expressions is a library that provides abstractions for creating specifications and query strategies using LINQ expressions. It also supports querying and writing to databases using various providers.
🏃 Quickstart | 📗 Guide | 🔄 Migration
<hr />
Features
- Abstractions for creating specifications and query strategies
- Abstractions for querying and writing to databases
- Supports Entity Framework Core and Marten providers
- Built with .NET Standard 2.0, 2.1, and .NET Core 6.0
NuGet Packages
- Raiqub.Expressions: abstractions for creating specifications
- Raiqub.Expressions.Reading: abstractions for creating query strategies and query sessions and querying from database (defines IDbQuerySessionFactory and IDbQuerySession interfaces)
- Raiqub.Expressions.Writing: abstractions for creating write sessions and writing to database (defines IDbSessionFactory and IDbSession interfaces)
- Raiqub.Expressions.EntityFrameworkCore: implementation of sessions and factories using Entity Framework Core
- Raiqub.Expressions.Marten: implementation of sessions and factories using Marten library
Prerequisites
Before you begin, you'll need the following:
- .NET Standard 2.0 or 2.1, or .NET Core 6.0 installed on your machine
- An IDE such as Visual Studio, Visual Studio Code, or JetBrains Rider
- A database to query against (if using the reading package) or write to (if using the writing package)
Quickstart
To use Raiqub.Expressions in your project, follow these steps:
Entity Framework Core
Install the required NuGet package(s) for the database provider you'll be using, such as `Microsoft.EntityFrameworkCore.SqlServer`
Install the `Raiqub.Expressions.EntityFrameworkCore` NuGet package
Register your DbContext by using `AddDbContextFactory` extension method
services.AddDbContextFactory<YourDbContext>();
Register the session and session factories using the appropriate extension method(s) for your database provider:
services.AddEntityFrameworkExpressions() .AddSingleContext<YourDbContext>();
Marten
Install the `Marten` NuGet package
Install the `Raiqub.Expressions.Marten` NuGet package
Register the session and session factories using the appropriate extension method(s) for your database provider:
services.AddMartenExpressions() .AddSingleContext();
Using
Inject the appropriate session interface (`IDbQuerySession` for read sessions, `IDbSession` for write sessions) into your services, and use it read and write from/to database.
You can also create specifications and query strategies. Here's an example of how to create a simple specification:
public class CustomerIsActive : Specification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return customer => customer.IsActive;
}
}
And here's an example of how to use the specification:
// session is of type IDbSession or IDbQuerySession and can be injected
var query = session.Query(new CustomerIsActive());
var customers = await query.ToListAsync();
Guide
Creating Specifications
The Specification Pattern is a behavioral design pattern used to encapsulate business rules into composable, reusable and testable objects. This pattern is often used in domains where queries or validation rules need to be expressed in a more readable and maintainable form.
The `Raiqub.Expressions` package provides the `Specification<T>` base class for creating specifications. It is optimized to allow ORM frameworks to evaluate and translate it into SQL queries.
Here's an example of creating a specification by inheriting from `Specification<T>` base class:
public class ProductIsInStock : Specification<Product>
{
public override Expression<Func<Product, bool>> ToExpression()
{
return product => product.AvailableQuantity > 0;
}
}
or, you can create a static class to provide the specifications of an object:
public static class ProductSpecification
{
public static Specification<Product> IsInStock { get; } =
Specification.Create<Product>(product => product.AvailableQuantity > 0);
public static Specification<Product> IsDiscountAvailable(DateTime now) =>
Specification.Create<Product>(product => product.DiscountStartDate <= now && now <= product.DiscountEndDate);
}
The specifications can be combined using the available extension methods or the logical operators:
public static Specification<Incident> IsClosed { get; } =
Specification.Create<Incident>(incident => incident.Status == IncidentStatus.Closed);
public static Specification<Incident> IsResolved { get; } =
Specification.Create<Incident>(incident => incident.Status == IncidentStatus.Resolved);
// =======================
// Using extension methods
// =======================
public static Specification<Incident> IsNotResolved { get; } =
IsResolved.Not();
public static Specification<Incident> IsResolvedOrClosed { get; } =
IsResolved.Or(IsClosed);
// =======================
// Using logical operators
// =======================
public static Specification<Incident> IsNotResolved { get; } =
!IsResolved;
public static Specification<Incident> IsResolvedOrClosed { get; } =
IsResolved | IsClosed;
Creating Query Strategies
The query strategy is based on the Strategy Pattern by defining a strategy for querying the database allowing better concern separation, maintainability and reusability than the repository pattern.
The `Raiqub.Expressions.Reading` package provides abstractions for creating query strategies. You can create a new query strategy by choosing one of several ways available to implement a query strategy.
Single Entity Query
The most common strategy is querying a single entity and for that purpose the interface `IEntityQueryStrategy<TSource, TResult>` was created and its abstract class implementation `EntityQueryStrategy<TSource, TResult>`.
Here's an example of a entity query strategy that filters a list of entities based on a set of conditions:
public class GetProductNameQueryStrategy : EntityQueryStrategy<Product, ProductName>
{
protected override IQueryable<ProductName> ExecuteCore(IQueryable<Product> source)
{
return source
.Where(ProductSpecification.IsInStock)
.OrderBy(e => e.Name)
.Select(e => new ProductName { Id = e.Id, Name = e.Name });
}
}
or, you can define only the preconditions:
public class GetProductInStockQueryStrategy : EntityQueryStrategy<Product>
{
protected override IEnumerable<Specification<Product>> GetPreconditions()
{
yield new ProductIsInStock();
}
}
or yet, you can create a static class as a provider of query strategies:
public static class ProductQueryStrategy
{
public static IEntityQueryStrategy<Product, ProductName> GetName() =>
QueryStrategy.CreateForEntity(
(IQueryable<Product> source) => source
.Where(ProductSpecification.IsInStock)
.OrderBy(e => e.Name)
.Select(e => new ProductName { Id = e.Id, Name = e.Name });
}
Multiple Entities Query
For the cases where multiple entities need to be queried the interface `IQueryStrategy<TResult>` was created.
You can implement the interface directly, as the example below:
public class GetProductNameOfOpenStoreQueryStrategy : IQueryStrategy<ProductName>
{
public IQueryable<TResult> Execute(IQuerySource source) =>
source => from product in source.GetSetUsing(ProductSpecification.IsInStock)
join store in source.GetSetUsing(StoreSpecification.IsOpen) on
product.StoreId equals store.Id
orderby product.Name
select new ProductName { Id = e.Id, Name = e.Name };
}
or, can create a static class as a provider of query strategies:
public static class ProductQueryStrategy
{
public static IQueryStrategy<ProductName> GetNameOfOpenStore() =>
QueryStrategy.Create(
source => from product in source.GetSetUsing(ProductSpecification.IsInStock)
join store in source.GetSetUsing(StoreSpecification.IsOpen) on
product.StoreId equals store.Id
orderby product.Name
select new ProductName { Id = e.Id, Name = e.Name });
}
Creating Query Sessions and Querying Data
To create a query session and query data using a query strategy, follow these steps:
- Inject an instance of `IDbQuerySessionFactory` into your service or controller.
- Use the `Create()` method of the `IDbQuerySessionFactory` interface to create a new query session.
- Call the `Query()` method on the query session, passing in your query strategy or specification instance.
- Call one of the methods on the resulting `IDbQuery<T>` interface to execute the query and retrieve the results.
await using (var session = querySessionFactory.Create())
{
IDbQuery<Customer> query = session.Query(new CustomerIsActive());
IReadOnlyList<Customer> customers = await query.ToListAsync();
}
Creating Write Sessions and Writing Data
To create a write session and write data to the database, follow these steps:
- Inject an instance of `IDbSessionFactory` into your service or controller.
- Use the `Create()` method of the `IDbSessionFactory` interface to create a new write session.
- Call the appropriate methods on the write session to perform insert, update, or delete operations on your entities.
- Call the `SaveChangesAsync()` method on the write session to persist your changes to the database.
await using (var session = sessionFactory.Create())
{
var blog = new Blog { Url = "https://example.com" };
session.Add(blog);
await session.SaveChangesAsync();
}
Defining custom SQL query for entity (Entity Framework)
To define a custom SQL query for retrieving a entity from database, follow these steps:
- Create a new class implementing the `ISqlProvider<TEntity>` interface;
- Implement the `GetQuerySql()` method returning a raw or an interpolated SQL string;
- Register the created class as a implementation of ISqlProvider interface for dependency injection using the singleton lifetime.
Example implementing a custom SQL query for Blog entity:
private class BlogSqlProvider : ISqlProvider<Blog>
{
public SqlString GetQuerySql() => SqlString.FromSqlInterpolated($"SELECT \"Id\", \"Name\" FROM \"Blog\"");
}
And then, registering it:
services.AddSingleton<ISqlProvider, BlogSqlProvider>();
Supported Databases
Currently, Raiqub.Expressions supports the following ORM libraries:
- Entity Framework Core
- Marten
If you need to use another ORM library, you will need to implement your own database session factory and database session implementing `IDbSessionFactory` and `IDbSession` interfaces.
Migration Guide
Key Changes in 2.0.0
The V2 release renamed the `IQueryModel`-related interfaces and classes to `IQueryStrategy`. This is the list of relevant renames:
V1 | V2 |
---|---|
IQueryModel<TResult> | IQueryStrategy<TResult> |
IEntityQueryModel<TResult> | IEntityQueryStrategy<TResult> |
EntityQueryModel<TSource, TResult> | EntityQueryStrategy<TSource, TResult> |
QueryModel | QueryStrategy |
Additionally, the `IEntityQueryStrategy` interface extends the `IQueryStrategy` interface.
Contributing
If something is not working for you or if you think that the source file should change, feel free to create an issue or Pull Request. I will be happy to discuss and potentially integrate your ideas!
License
This library is licensed under the MIT License.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. 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 was computed. 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. |
.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 is compatible. |
.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
- Microsoft.Bcl.AsyncInterfaces (>= 6.0.0)
- Raiqub.Expressions (>= 2.0.4)
-
.NETStandard 2.1
- Raiqub.Expressions (>= 2.0.4)
-
net6.0
- Raiqub.Expressions (>= 2.0.4)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Raiqub.Expressions.Reading:
Package | Downloads |
---|---|
Raiqub.Expressions.Writing
Provides abstractions for creating write sessions and writing to database (defines IDbSessionFactory and IDbSession interfaces). |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.3.206 | 126 | 10/17/2024 |
2.3.176 | 211 | 8/31/2024 |
2.3.123 | 219 | 6/9/2024 |
2.3.117 | 223 | 6/4/2024 |
2.3.99 | 169 | 5/19/2024 |
2.3.69 | 238 | 4/24/2024 |
2.3.53 | 280 | 4/6/2024 |
2.2.39 | 574 | 1/7/2024 |
2.2.5 | 587 | 11/11/2023 |
2.1.50 | 568 | 11/2/2023 |
2.1.8 | 618 | 10/6/2023 |
2.1.7 | 584 | 10/1/2023 |
2.0.4 | 616 | 9/24/2023 |
1.1.3 | 627 | 9/24/2023 |
1.0.34 | 590 | 9/7/2023 |
1.0.19-gc5aee6dbbe | 591 | 7/30/2023 |
1.0.16-gc38afc643f | 615 | 7/29/2023 |
1.0.3-g5a9a3695a8 | 626 | 4/20/2023 |
1.0.0-pre20230410-0253 | 573 | 4/10/2023 |
Rename IQueryModel to IQueryStrategy