Purlin.PData.Search
2.1.8
Prefix Reserved
dotnet add package Purlin.PData.Search --version 2.1.8
NuGet\Install-Package Purlin.PData.Search -Version 2.1.8
<PackageReference Include="Purlin.PData.Search" Version="2.1.8" />
paket add Purlin.PData.Search --version 2.1.8
#r "nuget: Purlin.PData.Search, 2.1.8"
// Install Purlin.PData.Search as a Cake Addin #addin nuget:?package=Purlin.PData.Search&version=2.1.8 // Install Purlin.PData.Search as a Cake Tool #tool nuget:?package=Purlin.PData.Search&version=2.1.8
Dynamic sql query generator from condition model
PData is giving functionality for creating predicate expression from specified condition models for using EF Core. It also has paging functionality with ordering by given property name and direction.
Definition
public enum Operator
{
/// <summary>
/// Equal operator
/// </summary>
Eq,
/// <summary>
/// Greater than operator
/// </summary>
Gt,
/// <summary>
/// Greater than or equal operator
/// </summary>
Ge,
/// <summary>
/// Less than operator
/// </summary>
Lt,
/// <summary>
/// Less than or equal operator
/// </summary>
Le,
/// <summary>
/// In Operator
/// </summary>
In,
/// <summary>
/// Starts with function for string
/// </summary>
Sw,
/// <summary>
/// Ends with function for string
/// </summary>
Ew,
/// <summary>
/// Contains function for string
/// </summary>
Like,
/// <summary>
/// Null check operator
/// </summary>
IsNull,
/// <summary>
/// Any operator for collections
/// </summary>
Any,
/// <summary>
/// All operator for collections
/// </summary>
All
}
public enum Condition
{
/// <summary>
/// And condition
/// </summary>
And,
/// <summary>
/// Or condition
/// </summary>
Or
}
public class ConditionModel
{
/// <summary>
/// Gets or sets the operator
/// </summary>
public virtual Operator Operator { get; set; }
/// <summary>
/// Gets or sets the value to be filtered
/// </summary>
public virtual string Value { get; set; }
/// <summary>
/// Gets or sets the property name for filtering
/// </summary>
public virtual string Property { get; set; }
/// <summary>
/// Gets or sets the another property name for filtering instead of using constant value
/// </summary>
public virtual string NextProperty { get; set; }
/// <summary>
/// Gets or sets the negate flag
/// </summary>
public virtual bool IsNot { get; set; }
/// <summary>
/// Gets or sets the sub conditions
/// </summary>
public virtual List<ConditionModel> SubConditions { get; } = new();
/// <summary>
/// Gets or sets the condition to be combined with sub conditions
/// </summary>
public virtual Condition? SubCondition { get; set; }
/// <summary>
/// Gets or sets the condition to be combined with next same level conditions
/// </summary>
public virtual Condition? NextCondition { get; set; }
}
public class OrderingModel
{
/// <summary>
/// Gets or sets ordering property
/// </summary>
public string Property { get; set; }
/// <summary>
/// Gets or sets ordering asc/desc direction
/// </summary>
public bool IsDescending { get; set; }
}
public class Query
{
public bool Count { get; set; }
public string Filter { get; set; }
public List<ConditionModel> Conditions { get; set; }
public List<OrderingModel> Orderings { get; set; }
public int? Skip { get; set; }
public int? Take { get; set; }
public List<string> Fields { get; set; } = new();
}
Register
Add this line of code somewhere you are registering DbContext
...
services.AddDbContextPool<AppDbContext>(o => o.UseSqlServer(ConnectionString).UsePData());
This will read all configurations you've made in your context class when configuring entities.
Usage
Assume we have this three classes in our DbContext class
public class Order
{
public long Id { get; set; }
public decimal Price { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Person> Persons { get; set; }
}
public class Person
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
public ICollection<Order> Orders { get; set; }
}
Inject or create instance of your context class somewhere you want.
public class PersonManager
{
public async Task<Person> GetPersons()
{
var query = new Query
{
Count = true
Skip = 10,
Take = 5,
};
using var context = new AppContext();
var persons = context.Persons.ApplyQuery(query, out var totalCountTask);
var totalCount = await totalCountTask;
var page = query.ToListAsync();
}
}
This query will be translated to sql as shown where @__p_0 is 0 and @__p_1 is 10.
SELECT [p].[Id], [p].[Age], [p].[Name], [p].[Surname]
FROM [Persons] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
Be careful await the totalCountTask before starting any other query on database to avoid from "A second operation started on this context before a previous operation completed" InvalidOperationException. Please use ApplyOptions after all 'where' statements because total count may be changed because of adding new condition.
In case of when you want also order the data use Orderings.
public async Task<Person> GetPersons()
{
var query = new Query
{
Count = true
Skip = 10,
Take = 5,
Orderings = new List<OrderingModel>{ new OrderingModel { Property = "Id" , IsDecending = true}}
};
using var context = new AppContext();
var persons = context.Persons.ApplyQuery(query, out var totalCountTask);
var totalCount = await totalCountTask;
var page = query.ToListAsync();
}
This query will be translated to sql as
SELECT [p].[Id], [p].[Age], [p].[Name], [p].[Surname]
FROM [Persons] AS [p]
ORDER BY [p].[Id] DESC
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
In case of when you want also filter the data add condition models or use more user friendly syntax.
public async Task<Person> GetPersons()
{
var query = new Query
{
Count = true
Skip = 10,
Take = 5,
Filter = "Age eq \"15\"", //Filter is equivalnt to Conditions = new List<ConditionModel>{ new ConditionModel { Property = "Age", Operator = Operator.Eq, Value = "15"}}
Orderings = new List<OrderingModel>{ new OrderingModel { Property = "Id" , IsDecending = true}}
};
using var context = new AppContext();
var persons = context.Persons.ApplyQuery(query, out var totalCountTask);
var totalCount = await totalCountTask;
var page = query.ToListAsync();
}
This query will be translated to sql as shown where @__Parse_0 is 15.
SELECT [p].[Id], [p].[Age], [p].[Name], [p].[Surname]
FROM [Persons] AS [p]
WHERE [p].[Age] = @__Parse_0
ORDER BY [p].[Id] DESC
OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY
Property can be used with '.' (dots) for example "Country.Name" this will join related table and order or filter data by related table column.
For using two properties in expression there is NextProperty in ConditionModel that has higher priority "Value" will be ignored.
Example for multiple conditions combined by and/or operators and parenthesis` Assume there is condition such as
a eq \"10\" and b eq \"5\" and (c eq \"1\" or d eq \"2\") and (e eq \"2\" or (f eq \"1\" and (g eq \"10\" or h eq \"22\") and (p eq \"9\" or q eq \"7\"))) and m not in [\"a\",\"b\"]
this is equivalent to this json array which items is conditionModels
[
{
"Operator": 0,
"Value": "10",
"Property": "a",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": 0
},
{
"Operator": 0,
"Value": "5",
"Property": "b",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": 0
},
{
"Operator": 0,
"Value": "1",
"Property": "c",
"IsNot": false,
"SubConditions": [
{
"Operator": 0,
"Value": "2",
"Property": "d",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
],
"SubCondition": 1,
"NextCondition": 0
},
{
"Operator": 0,
"Value": "2",
"Property": "e",
"IsNot": false,
"SubConditions": [
{
"Operator": 0,
"Value": "1",
"Property": "f",
"IsNot": false,
"SubConditions": [
{
"Operator": 0,
"Value": "10",
"Property": "g",
"IsNot": false,
"SubConditions": [
{
"Operator": 0,
"Value": "22",
"Property": "h",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
],
"SubCondition": 1,
"NextCondition": 0
},
{
"Operator": 0,
"Value": "9",
"Property": "p",
"IsNot": false,
"SubConditions": [
{
"Operator": 0,
"Value": "7",
"Property": "q",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
],
"SubCondition": 1,
"NextCondition": null
}
],
"SubCondition": 0,
"NextCondition": null
}
],
"SubCondition": 1,
"NextCondition": 0
},
{
"Operator": 5,
"Value": "[\"a\",\"b\"]",
"Property": "m",
"IsNot": true,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
]
If we will use 'a gt b' instead of 'a gt "b"', 'b' will assumed as another property of that type and will must have same type which has 'a'
{
"Operator": 1,
"Value": null,
"Property": "a",
"NextProperty": "b",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
Any and All operators have special syntax, assume we need filter persons that have any order price above 1000. In this case we have to specify 'value' as condition such as
{
"Operator": 0,
"Value": "Price gt \"1000\"",
"Property": "Orders",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
IsNull operator doesn't require any value. Value part must be skipped in filter - Name isNull/ Name not isNull for string usage or json below for model usage.
{
"Operator": 9,
"Value": null ,
"Property": "Name",
"IsNot": false,
"SubConditions": [],
"SubCondition": null,
"NextCondition": null
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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 is compatible. 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. |
-
net7.0
- Microsoft.EntityFrameworkCore (>= 7.0.20)
- Microsoft.EntityFrameworkCore.Relational (>= 7.0.20)
- Purlin.PData.Search.Model (>= 2.1.8)
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.6)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.6)
- Purlin.PData.Search.Model (>= 2.1.8)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Purlin.PData.Search:
Package | Downloads |
---|---|
Purlin.HomeMatcher.Integration.Model
Purlin HomeMatcher Integration Models |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.1.8 | 429 | 6/12/2024 |
2.1.7 | 234 | 3/21/2024 |
2.1.6 | 152 | 3/4/2024 |
2.1.5 | 170 | 2/22/2024 |
2.1.4 | 201 | 1/24/2024 |
2.1.3 | 157 | 1/24/2024 |
2.1.2 | 190 | 1/12/2024 |
2.0.11 | 514 | 12/11/2023 |
2.0.10 | 196 | 12/11/2023 |
2.0.9 | 274 | 11/21/2023 |
2.0.8 | 361 | 11/21/2023 |
2.0.7 | 420 | 10/31/2023 |
2.0.5 | 229 | 10/30/2023 |
2.0.4 | 244 | 10/23/2023 |
2.0.3 | 268 | 10/19/2023 |
2.0.1.1 | 367 | 9/12/2023 |
2.0.1 | 1,499 | 8/24/2023 |
1.8.0 | 594 | 7/20/2023 |
1.7.0 | 419 | 7/7/2023 |
1.5.0 | 405 | 6/27/2023 |
1.4.0 | 2,254 | 3/27/2023 |
1.3.0 | 458 | 3/24/2023 |
1.2.0 | 482 | 3/23/2023 |
1.1.0 | 469 | 3/21/2023 |
Initial release of the package.