Simple and Easy to Use
Gridify offers a powerful string-based dynamic LINQ query language that is both simple and easy to use.
diff --git a/404.html b/404.html new file mode 100644 index 00000000..bf6568f6 --- /dev/null +++ b/404.html @@ -0,0 +1,34 @@ + + +
+ + +=0)c=r.activeElement;else{var f=i.tabbableGroups[0],p=f&&f.firstTabbableNode;c=p||h("fallbackFocus")}if(!c)throw new Error("Your focus-trap needs to have at least one focusable element");return c},v=function(){if(i.containerGroups=i.containers.map(function(c){var f=br(c,a.tabbableOptions),p=wr(c,a.tabbableOptions),C=f.length>0?f[0]:void 0,I=f.length>0?f[f.length-1]:void 0,M=p.find(function(m){return le(m)}),P=p.slice().reverse().find(function(m){return le(m)}),z=!!f.find(function(m){return se(m)>0});return{container:c,tabbableNodes:f,focusableNodes:p,posTabIndexesFound:z,firstTabbableNode:C,lastTabbableNode:I,firstDomTabbableNode:M,lastDomTabbableNode:P,nextTabbableNode:function(x){var $=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,K=f.indexOf(x);return K<0?$?p.slice(p.indexOf(x)+1).find(function(Q){return le(Q)}):p.slice(0,p.indexOf(x)).reverse().find(function(Q){return le(Q)}):f[K+($?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(c){return c.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(c){return c.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},y=function w(c){var f=c.activeElement;if(f)return f.shadowRoot&&f.shadowRoot.activeElement!==null?w(f.shadowRoot):f},b=function w(c){if(c!==!1&&c!==y(document)){if(!c||!c.focus){w(d());return}c.focus({preventScroll:!!a.preventScroll}),i.mostRecentlyFocusedNode=c,Ar(c)&&c.select()}},E=function(c){var f=h("setReturnFocus",c);return f||(f===!1?!1:c)},g=function(c){var f=c.target,p=c.event,C=c.isBackward,I=C===void 0?!1:C;f=f||Ae(p),v();var M=null;if(i.tabbableGroups.length>0){var P=l(f,p),z=P>=0?i.containerGroups[P]:void 0;if(P<0)I?M=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:M=i.tabbableGroups[0].firstTabbableNode;else if(I){var m=ft(i.tabbableGroups,function(B){var U=B.firstTabbableNode;return f===U});if(m<0&&(z.container===f||_e(f,a.tabbableOptions)&&!le(f,a.tabbableOptions)&&!z.nextTabbableNode(f,!1))&&(m=P),m>=0){var x=m===0?i.tabbableGroups.length-1:m-1,$=i.tabbableGroups[x];M=se(f)>=0?$.lastTabbableNode:$.lastDomTabbableNode}else ge(p)||(M=z.nextTabbableNode(f,!1))}else{var K=ft(i.tabbableGroups,function(B){var U=B.lastTabbableNode;return f===U});if(K<0&&(z.container===f||_e(f,a.tabbableOptions)&&!le(f,a.tabbableOptions)&&!z.nextTabbableNode(f))&&(K=P),K>=0){var Q=K===i.tabbableGroups.length-1?0:K+1,q=i.tabbableGroups[Q];M=se(f)>=0?q.firstTabbableNode:q.firstDomTabbableNode}else ge(p)||(M=z.nextTabbableNode(f))}}else M=h("fallbackFocus");return M},S=function(c){var f=Ae(c);if(!(l(f,c)>=0)){if(ye(a.clickOutsideDeactivates,c)){s.deactivate({returnFocus:a.returnFocusOnDeactivate});return}ye(a.allowOutsideClick,c)||c.preventDefault()}},T=function(c){var f=Ae(c),p=l(f,c)>=0;if(p||f instanceof Document)p&&(i.mostRecentlyFocusedNode=f);else{c.stopImmediatePropagation();var C,I=!0;if(i.mostRecentlyFocusedNode)if(se(i.mostRecentlyFocusedNode)>0){var M=l(i.mostRecentlyFocusedNode),P=i.containerGroups[M].tabbableNodes;if(P.length>0){var z=P.findIndex(function(m){return m===i.mostRecentlyFocusedNode});z>=0&&(a.isKeyForward(i.recentNavEvent)?z+1
j)for(;E<=B;)Le(u[E],b,S,!0),E++;else{const q=E,z=E,ee=new Map;for(E=z;E<=j;E++){const be=d[E]=R?Ge(d[E]):Ae(d[E]);be.key!=null&&ee.set(be.key,E)}let Q,ae=0;const Te=j-z+1;let ht=!1,zr=0;const St=new Array(Te);for(E=0;E {const{el:S,type:O,transition:x,children:R,shapeFlag:E}=u;if(E&6){tt(u.component.subTree,d,g,v);return}if(E&128){u.suspense.move(d,g,v);return}if(E&64){O.move(u,d,g,dt);return}if(O===_e){r(S,d,g);for(let B=0;B Any kind of contribution is welcome. start contributing to the project by submitting pull requests Gridify documentation is powered by vuePress, All the markdown source files are placed in by running the following commands, you can check the documentation site locally: check out the github contributing guide Thank you to everyone who has contributed to the Gridify codebase. We appreciate you! When working with ASP.NET APIs, especially when you need to apply string-based filtering conditions, sorting based on field names, or implementing pagination functionality, the Gridify library is a valuable tool. It can be used in any .NET project and with any type of collection, not just limited to ASP.NET projects. To demonstrate the core concepts of Gridify, let's look at a simple implementation in the following example. Imagine you have an API that returns a list of users. You want to use this API in your client-side application to display a list of users. However, there are a few challenges: Implementing these features can be complex and messy. If you want to support multiple properties, you would need to write a lot of code with if-else statements. This is where Gridify comes in. With Gridify, you can simplify your code and implement the required features in just a few lines: Gridify handles all the complexity behind the scenes. The The Learn more about GridifyQuery. Please note that this URL is not encoded. Always remember to encode query strings before passing them to your APIs. Alternatively, you can ignore the TIP Gridify is completely compatible with AutoMapper. Also, these two packages can help each other nicely. We can use Gridify for filtering, sorting, and paging and AutoMapper for the projection. Filtering, Ordering, Paging, and Projection are all done with Gridify library does not have a built-in You can access Gridify generated expressions using the WARNING You should only use a compiled expression (delegate) if you are not using Gridify alongside an ORM like Entity-Framework. This is the performance improvement example when you use a compiled expression. Gridify offers a powerful feature that enables you to streamline data mapping and configurations in your application by integrating with the Dependency Injection (DI) container. By registering your mapping profiles with DI, you can achieve cleaner, more maintainable code and improved separation of concerns. This section provides an overview of how to register your GridifyMapper configurations with DI. Registering Gridify mapping with DI is a straightforward process. You'll define mapping profiles for your models and then register them in the DI container. Follow these steps to get started: Create mapping profiles by inheriting from Example: Utilize the Example: Once you've registered the mapping profiles, you can inject the corresponding Example: The Gridify library adds the below extension methods to All Gridify extension methods can accept GridifyQuery and GridifyMapper as a parameter. Make sure to check out the documentation of these classes for more information. TIP If you want to use Gridify extension methods on an You can use this method if you want to only apply filtering on an this is completely equivalent to the below LINQ query: In the Check out the Filtering Operators section for more information. You can use this method if you want to only apply ordering on an this is completely equivalent to the below LINQ query: Check out the Ordering section for more information. You can use this method if you want to only apply paging on an this is completely equivalent to the below LINQ query: You can use this method if you want to apply filtering and ordering on an You can use this method if you want to apply ordering and paging on an You can use this method if you want to apply filtering and ordering and paging on an Like ApplyFilteringOrderingPaging but it returns a This is an ALL-IN-ONE package, it accepts Gridify.Elasticsearch is an extension of Gridify, that provides an ability to generate Elasticsearch DSL queries. The Specifies how field names are inferred from CLR property names. By default, Elastic.Clients.Elasticsearch uses camel-case property names. this will make the next Elasticsearch query: this will make the next Elasticsearch query: By default, Elasticsearch converts property names to camel-case for document fields. That's Gridify.Elasticsearch extensions work by default. But if it's necessary to apply a custom naming policy, it can also be customized. WARNING Gridify.Elasticsearch package does not support all of the advanced Gridify features like NestedFiltering The If you want to use gridify with Entity Framework, you should enable the compatibility layer: Enabling the compatibility layer provides the following features: Setting this property to Simply sets the EntityFrameworkCompatibilityLayer property to Simply sets the EntityFrameworkCompatibilityLayer property to The Gridify Client library is a lightweight JavaScript and TypeScript library designed to simplify the creation of dynamic queries on the client side. This library facilitates the construction of queries that can be seamlessly integrated with your server-side APIs, leveraging the powerful features of Gridify. To integrate the Gridify Client library into your project, install it using npm: The The following table describes the methods available in the GridifyQueryBuilder interface for constructing dynamic queries. We can use the Below is a basic example demonstrating the usage of the Gridify Client library in TypeScript. This example constructs a dynamic query for pagination, ordering, and filtering: Output: Gridify supports the following filtering operators: TIP If you don't specify any value after this is equivalent to the bellow LINQ query: Using logical operators we easily can create complex queries. The '/i' operator can be use after string values for case insensitive searches. You should only use this operator after the search value. Example: this query matches with JOHN - john - John - jOHn ... Gridify have five special operators JavaScript escape example: Csharp escape example: Starting from version Gridify enables filtering on elements of sub-collections, arrays, and dictionary properties by specifying the index or key within square brackets Here’s an example: In this example: This new capability makes Gridify a versatile and robust tool for handling complex data structures in your filtering logic. Check out Defining Mappings for Indexable Properties for more information. Sometimes the default Gridify operators are not enough, For example, if you need an operator for regex matching or when you are using the EntityFramework, you may want to use To define a custom operator, you need to create a class that implements the TIP Custom operators must be start with the Registration Example: There are three packages available for gridify in the nuget repository: TIP If you are using the Entity Framework in your project, you should install the This package provides the same functionality as the TIP To use Gridify with Elasticsearch, it's necessary to install If you are developing in a JavaScript or TypeScript environment and need to create dynamic queries on the client side for server-side operations, Gridify also offers a lightweight client library. gridify-client After installing the package, you can use the There are two ways to start using Gridify: Using this class you can change the default behavior and configuration of the Gridify library. The default page size for the paging methods when no page size is specified. By default mappings are case insensitive. For example, This option enables the If true, in filtering and ordering operations, gridify doesn't return any exceptions when a mapping is not defined for the given field. On nested collections by default gridify adds a null checking condition to prevent the null reference exceptions e.g some ORMs like NHibernate don't support this. You can disable this behavior by setting this option to true. By default, Gridify uses the Using the To learn more about custom operators, see Custom operator Internally Gridify is using an auto generated mapper that maps your string base field names to actual properties in your entities, but sometimes we don't want to support filtering or sorting on a specific field. If you want to control what field names are mapped to what properties, you can create a custom mapper. To get a better understanding of how this works, consider the following example: In this example we want to: In the following, we will become more familiar with the above methods This method generates mappings for the properties of the entity, including top-level public properties and properties of nested classes up to the specified nesting depth. TIP Another alternative to generate default mappings for top-level public properties is by passing true to the GridifyMapper constructor. This generates mappings without considering nesting depth. This method removes mapping from the mapper. Usually you will use this method after you have generated the mappings to ignore some properties that you don't want to be supported by Gridify filtering or ordering actions. This method adds a mapping to the mapper. If you need to change your search values before the filtering operation you can use this feature, the third parameter of the GridifyMapper AddMap method accepts a function that you can use to convert the input values. in the above example we want to convert the userName value to lowercase before the filtering operation. This method checks if the mapper has a mapping for the given field name. This method clears the list of mappings This method returns list of current mappings. By default mapper is By setting this to By setting this to By setting this property to a You can use LINQ In this example, we have 3 nested collections, but filtering will apply to the if you have only two-level nesting, you don't need to use Starting from version You can define a mapping to a specific index in an array or sub-collection by specifying the index within square brackets Similarly, you can define a mapping to a specific key in a dictionary or in a navigation property. If your dictionary key is not a string, you can use the generic overload of the For more information on filtering using these mappings, refer to the Using Indexers. This method returns the selector expression that you can use it in LINQ queries. This method returns the selector expression that you can use it in LINQ queries. This extension method, checks if the Optionally you can pass a custom mapper to check if the This extension method, creates a lambda expression using the Greetings! Gridify is an exceptional dynamic LINQ library designed to simplify the process of converting strings into LINQ queries. It offers remarkable performance and ease-of-use, making it effortless to apply filtering, sorting, and pagination utilizing text-based data. Here are some notable features of Gridify: To provide a better understanding of Gridify's functionality, we have prepared a few examples: Filtering is the most expensive feature in Gridify. The following benchmark compares filtering in various popular dynamic LINQ libraries. Interestingly, Gridify outperforms even Native LINQ in terms of speed. It's worth mentioning that other features like Pagination and Sorting in Gridify have minimal impact on performance. BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.2715/22H2/2022Update/SunValley2) 12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores .NET SDK 8.0.100 [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 This Benchmark is available Here The ordering query expression can be built with a comma-delimited ordered list of field/property names followed by By default, if you don't add these keywords, Gridify assumes you need Ascending ordering. Sometimes we need to order by nullable types, for example: to support this behavior, you can use Gridify special characters ( e.g: To achieve the and for WARNING These nullable characters will only work on the nullable types. The DANGER Gridify version 1.x.x is no longer maintained. you should consider upgrading to the latest version. The source code of this version is available on version-1.x Easy and optimized way to apply Filtering, Sorting and pagination using text-based data. The best use case of this library is Asp-net APIs. when you need to get some string base filtering conditions to filter data or sort it by a field name or apply pagination concepts to your lists and return a pageable, data grid ready information, from any repository or database. complete request sample: also we can totally ignore GridifyQuery GridifyQuery is a simple class for configuring Filtering,Paging,Sorting. Also, if you don't need paging and sorting features simply use Filtering is the most expensive feature in gridify. the below benchmark is comparing filtering in the most known dynamic linq libraries. as you can see, gridify has the closest result to the native linq. also, I Should note other features like Pagination and Sorting has almost zero overhead in Gridify. BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1110 (21H1/May2021Update) 11th Gen Intel Core i5-11400F 2.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=5.0.301 [Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT Install the Gridify NuGet Package. Package Manager Console .NET Core CLI The library adds below extension methods to TIP: but for example, if you need to just filter your data without paging or sorting options you can use we can easily create complex queries using Parenthesis Escape character hint: Filtering has four special character JavaScript escape example: Csharp escape example: By default Gridify is using a by default if you need to use gridify async feature for entityFramework Core, use this package have two additional Any Contribution to improve documentation and library is appreciated feel free to send pull-Request. ❤️ Any kind of contribution is welcome. start contributing to the project by submitting pull requests Gridify documentation is powered by vuePress, All the markdown source files are placed in by running the following commands, you can check the documentation site locally: check out the github contributing guide Thank you to everyone who has contributed to the Gridify codebase. We appreciate you! When working with ASP.NET APIs, especially when you need to apply string-based filtering conditions, sorting based on field names, or implementing pagination functionality, the Gridify library is a valuable tool. It can be used in any .NET project and with any type of collection, not just limited to ASP.NET projects. To demonstrate the core concepts of Gridify, let's look at a simple implementation in the following example. Imagine you have an API that returns a list of users. You want to use this API in your client-side application to display a list of users. However, there are a few challenges: Implementing these features can be complex and messy. If you want to support multiple properties, you would need to write a lot of code with if-else statements. This is where Gridify comes in. With Gridify, you can simplify your code and implement the required features in just a few lines: Gridify handles all the complexity behind the scenes. The The Learn more about GridifyQuery. Please note that this URL is not encoded. Always remember to encode query strings before passing them to your APIs. Alternatively, you can ignore the TIP DANGER Gridify version 1.x.x is no longer maintained. you should consider upgrading to the latest version. The source code of this version is available on version-1.x Easy and optimized way to apply Filtering, Sorting and pagination using text-based data. The best use case of this library is Asp-net APIs. when you need to get some string base filtering conditions to filter data or sort it by a field name or apply pagination concepts to your lists and return a pageable, data grid ready information, from any repository or database. complete request sample: also we can totally ignore GridifyQuery GridifyQuery is a simple class for configuring Filtering,Paging,Sorting. Also, if you don't need paging and sorting features simply use Filtering is the most expensive feature in gridify. the below benchmark is comparing filtering in the most known dynamic linq libraries. as you can see, gridify has the closest result to the native linq. also, I Should note other features like Pagination and Sorting has almost zero overhead in Gridify. BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1110 (21H1/May2021Update) 11th Gen Intel Core i5-11400F 2.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=5.0.301 [Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT Install the Gridify NuGet Package. Package Manager Console .NET Core CLI The library adds below extension methods to TIP: but for example, if you need to just filter your data without paging or sorting options you can use we can easily create complex queries using Parenthesis Escape character hint: Filtering has four special character JavaScript escape example: Csharp escape example: By default Gridify is using a by default if you need to use gridify async feature for entityFramework Core, use this package have two additional Any Contribution to improve documentation and library is appreciated feel free to send pull-Request. ❤️Contribute to the project
Todos
Documentation
docs
directory.yarn install
+yarn dev
npm install
+npm run dev
How to contribute
Contributors
Using Gridify in API Controllers
Describing the Scenario
// UserController
+// ...
+public IEnumerable<User> GetUsers()
+{
+ // context can be Entity Framework, a repository, or whatever.
+ return context.Users.ToList();
+}
Solving Problems Using Gridify
public Paging<User> GetUsers([FromQuery] GridifyQuery query)
+{
+ return context.Users.Gridify(query);
+}
What is the Paging Return Value?
Paging
class is a generic Data Transfer Object (DTO) that has two properties:public int Count { get; set; }
+public IEnumerable<T> Data { get; set; }
Count
property indicates the total number of records, while the Data
property contains the records on the current page.What is GridifyQuery?
GridifyQuery
is a class that represents the query parameters passed to the Gridify
method.Sample Request Query String
http://exampleDomain.com/api/GetUsers?
+ pageSize=100&
+ page=1&
+ orderBy=FirstName&
+ filter=Age>10
GridifyQuery
and use the default pagination values, which are pageSize=20
and page=1
.http://exampleDomain.com/api/GetUsers
More Information
GridifyMapper
instance as a parameter.AutoMapper
// AutoMapper ProjectTo + Filtering Only, example
+var query = personRepo.ApplyFiltering(gridifyQuery);
+var result = query.ProjectTo<PersonDTO>().ToList();
// AutoMapper ProjectTo + Filtering + Ordering + Paging, example
+QueryablePaging<Person> qp = personRepo.GridifyQueryable(gridifyQuery);
+var result = new Paging<Person>(qp.Count, qp.Query.ProjectTo<PersonDTO>().ToList());
GridifyTo!
GridifyTo
.GridifyTo
extension method because we don't want to have AutoMapper dependency. but if you are using AutoMapper in your project, I recommend you to add the bellow extension method to your project.public static Paging<TDestination> GridifyTo<TSource, TDestination>(
+ this IQueryable<TSource> query, IMapper autoMapper, IGridifyQuery gridifyQuery, IGridifyMapper<TSource> mapper = null)
+{
+ var res = query.GridifyQueryable(gridifyQuery, mapper);
+ return new Paging<TDestination>(res.Count, res.Query.ProjectTo<TDestination>(autoMapper.ConfigurationProvider).ToList());
+}
// only if you have Gridify.EntityFramework package installed.
+public static async Task<Paging<TDestination>> GridifyToAsync<TSource, TDestination>(
+ this IQueryable<TSource> query, IMapper autoMapper, IGridifyQuery gridifyQuery, IGridifyMapper<TSource> mapper = null)
+{
+ var res = await query.GridifyQueryableAsync(gridifyQuery, mapper);
+ return new Paging<TDestination>(res.Count, await res.Query.ProjectTo<TDestination>(autoMapper.ConfigurationProvider).ToListAsync());
+}
Compile and Reuse
GetFilteringExpression
of GridifyQuery or BuildCompiled
methods of QueryBuilder class, by storing an expression you can use it multiple times without having any overheads, also if you store a compiled expression you get a massive performance boost.// eg.1 - using GridifyQuery - Compiled - where only
+var gq = new GridifyQuery() { Filter = "name=John" };
+var expression = gq.GetFilteringExpression<Person>();
+var compiledExpression = expression.Compile();
+var result = persons.Where(compiledExpression);
// eg.2 - using QueryBuilder - Compiled - where only
+var compiledExpression = new QueryBuilder<Person>()
+ .AddCondition("name=John")
+ .BuildFilteringExpression()
+ .Compile();
+var result = persons.Where(compiledExpression);
// eg.3 - using QueryBuilder - BuildCompiled
+var func = new QueryBuilder<Person>()
+ .AddCondition("name=John")
+ .BuildCompiled();
+var result = func(persons);
Performance
`,9),l=[n];function h(p,k,r,d,g,o){return t(),i("div",null,l)}const c=s(e,[["render",h]]);export{E as __pageData,c as default};
diff --git a/assets/guide_compile.md.oQKQvwx7.lean.js b/assets/guide_compile.md.oQKQvwx7.lean.js
new file mode 100644
index 00000000..40a11497
--- /dev/null
+++ b/assets/guide_compile.md.oQKQvwx7.lean.js
@@ -0,0 +1 @@
+import{_ as s,c as i,o as t,a4 as a}from"./chunks/framework.Cmp6zcjC.js";const E=JSON.parse('{"title":"Compile and Reuse","description":"","frontmatter":{},"headers":[],"relativePath":"guide/compile.md","filePath":"guide/compile.md"}'),e={name:"guide/compile.md"},n=a("",9),l=[n];function h(p,k,r,d,g,o){return t(),i("div",null,l)}const c=s(e,[["render",h]]);export{E as __pageData,c as default};
diff --git a/assets/guide_dependency-injection.md.D-nStdXD.js b/assets/guide_dependency-injection.md.D-nStdXD.js
new file mode 100644
index 00000000..765e8378
--- /dev/null
+++ b/assets/guide_dependency-injection.md.D-nStdXD.js
@@ -0,0 +1,60 @@
+import{_ as s,c as i,o as a,a4 as n}from"./chunks/framework.Cmp6zcjC.js";const y=JSON.parse('{"title":"Dependency Injection","description":"","frontmatter":{},"headers":[],"relativePath":"guide/dependency-injection.md","filePath":"guide/dependency-injection.md"}'),e={name:"guide/dependency-injection.md"},p=n(`Method Mean Ratio RatioSD Gen 0 Gen 1 Allocated GridifyCompiled 1.008 us 0.001 0.00 0.1564 - 984 B Gridify 689.329 us 1.000 0.00 5.8594 2.9297 39,924 B NativeLINQ 736.854 us 1.019 0.01 5.8594 2.9297 37,392 B Dependency Injection
Register GridifyMapper with DI
1. Define Mapping Profiles
GridifyMapper<T>
, where T
represents the type you want to map. Configure your mappings within these profile classes.public class WeatherForecastGridifyMapper : GridifyMapper<WeatherForecast>
+{
+ public WeatherForecastGridifyMapper()
+ {
+ // Define your mappings here
+ AddMap("summary", q => q.Summary);
+ AddMap("temp", q => q.TemperatureC);
+
+ // optionally you can customize the configuration for each mapper
+ Configuration.CaseSensitive = false;
+ Configuration.AllowNullSearch = true;
+ Configuration.IgnoreNotMappedFields = true;
+
+ }
+}
2. Register Mapping Profiles
AddGridifyMappers
extension method available on the IServiceCollection to scan your assembly and register all mapping profiles.using Gridify; // Make sure to include the necessary namespace
+// ...
+
+public void ConfigureServices(IServiceCollection services)
+{
+ // Other service registrations
+
+ services.AddGridifyMappers(typeof(Program).Assembly);
+}
3. Inject and Use Mappers
IGridifyMapper<T>
interfaces into your services or controllers.public class WeatherForecastController : ControllerBase
+{
+ private readonly IGridifyMapper<WeatherForecast> _mapper;
+
+ public WeatherForecastController(IGridifyMapper<WeatherForecast> mapper)
+ {
+ _mapper = mapper;
+ }
+
+ [HttpGet(Name = "GetWeatherForecast")]
+ public Paging<WeatherForecast> Get([FromQuery] GridifyQuery query)
+ {
+ IQueryable<WeatherForecast> result = GetWeatherForecasts();
+
+ // You can pass the mapper to the GridifyExtension
+ return result.Gridify(query, _mapper);
+ }
+}
public class WeatherForecastController : ControllerBase
+{
+ private readonly IGridifyMapper<WeatherForecast> _mapper;
+
+ public WeatherForecastController(IGridifyMapper<WeatherForecast> mapper)
+ {
+ _mapper = mapper;
+ }
+
+ [HttpGet(Name = "GetWeatherForecast")]
+ public IEnumerable<WeatherForecast> Get([FromQuery] GridifyQuery query)
+ {
+ var result = GetWeatherForecasts();
+
+ var queryBuilder = new QueryBuilder<WeatherForecast>()
+ .UseCustomMapper(_mapper)
+ .AddQuery(query);
+
+ return queryBuilder.Build(result);
+ }
+}
Extensions
IQueryable
objects.IEnumerable
object, use .AsQueryable()
first.ApplyFiltering
IQueriable
or DbSet
.var query = personsRepo.ApplyFiltering("name = John");
var query = personsRepo.Where(p => p.Name == "John");
ApplyFiltering
method, we can use a raw string to filter the data, which can be generated dynamically or passed by the end-user for example through an API client or console input, but using the Linq Where
method, we always have to hard code the query for the supported fields.ApplyOrdering
IQueriable
collection or DbSet
.var query = personsRepo.ApplyOrdering("name, age desc");
var query = personsRepo.OrderBy(x => x.Name).ThenByDescending(x => x.Age);
ApplyPaging
IQueryable
collection or DbSet
.var query = personsRepo.ApplyPaging(3, 20);
var query = personsRepo.Skip((3-1) * 20).Take(20);
ApplyFilteringAndOrdering
IQueryable
collection or DbSet
. This method accepts IGridifyQuery
.ApplyOrderingAndPaging
IQueryable
collection or DbSet
. This method accepts IGridifyQuery
.ApplyFilteringOrderingPaging
IQueryable
collection or DbSet
. This method accepts IGridifyQuery
.GridifyQueryable
QueryablePaging<T>
that has an extra int Count
value that can be used for pagination.Gridify
IGridifyQuery
, applies filtering, ordering, and paging, and returns a Paging<T>
object. This method is completely optimized to be used with any Grid component.Elasticsearch
Gridify.Elasticsearch Package
Gridify.Elasticsearch
package has a bunch of extension methods that allow to convert Gridify filters and sortings to Elasticsearch DSL queries using Elastic.Clients.Elasticsearch .NET client.Installation
Package Manager
',6),r={class:"language-shell vp-adaptive-theme"},E=s("button",{title:"Copy Code",class:"copy"},null,-1),d=s("span",{class:"lang"},"shell",-1),c={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},g={class:"line"},o=s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"Install-Package",-1),u=s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," Gridify.Elasticsearch",-1),y=s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}}," -Version",-1),b={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},F=s("h3",{id:"net-cli",tabindex:"-1"},[e(".NET CLI "),s("a",{class:"header-anchor",href:"#net-cli","aria-label":'Permalink to ".NET CLI"'},"")],-1),m={class:"language-shell vp-adaptive-theme"},C=s("button",{title:"Copy Code",class:"copy"},null,-1),q=s("span",{class:"lang"},"shell",-1),B={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},v={class:"line"},A=i('dotnet add package Gridify.Elasticsearch --version',5),_={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},f=i(`Configurations
CustomElasticsearchNamingAction
null
(default behavior) CLR property EmailAddress
will be inferred as emailAddress
Elasticsearch document field name.p => p
, the CLR property EmailAddress
will be inferred as EmailAddress
Elasticsearch document field name.Default
await client.SearchAsync<User>(s => s
+ .Index("users")
+ .ApplyFiltering("emailAddress = test@test.com"));
GET users/_search
+{
+ "query": {
+ "term": {
+ "emailAddress.keyword": {
+ "value": "test@test.com"
+ }
+ }
+ }
+}
Customized:
GridifyGlobalConfiguration.CustomElasticsearchNamingAction = p => $"_{p}_";
+
+await client.SearchAsync<User>(s => s
+ .Index("users")
+ .ApplyFiltering("emailAddress = test@test.com"));
GET users/_search
+{
+ "query": {
+ "term": {
+ "_EmailAddress_.keyword": {
+ "value": "test@test.com"
+ }
+ }
+ }
+}
Examples of usage
Without pre-initialized mapper
var gq = new GridifyQuery()
+{
+ Filter = "FirstName=John",
+ Page = 1,
+ PageSize = 20,
+ OrderBy = "Age"
+};
+
+var response = await client.SearchAsync<User>(s => s
+ .Index("users")
+ .ApplyPaging(gq)
+ .ApplyFiltering(gp)
+ .ApplyOrdering(gp));
+
+return response.Documents;
GET users/_search
+{
+ "query": {
+ "term": {
+ "firstName.keyword": {
+ "value": "John"
+ }
+ }
+ },
+ "from": 0,
+ "size": 20,
+ "sort": [{
+ "age": {
+ "order": "asc"
+ }
+ }]
+}
With custom mapping
var gq = new GridifyQuery()
+{
+ Filter = "name=John, surname=Smith, age=30, totalOrderPrice=45",
+ Page = 1,
+ PageSize = 20,
+ OrderBy = "Age"
+};
+
+var mapper = new GridifyMapper<User>()
+ .AddMap("name", x => x.FirstName)
+ .AddMap("surname", x => x.LastName)
+ .AddMap("age", x => x.Age)
+ .AddMap("totalOrderPrice", x => x.Order.TotalSum);
+
+var response = await client.SearchAsync<User>(s => s
+ .Index("users")
+ .ApplyFilteringOrderingPaging(gq));
+
+return response.Documents;
GET users/_search
+{
+ "query": {
+ "bool": {
+ "must": [
+ {
+ "term": {
+ "firstName.keyword": {
+ "value": "John"
+ }
+ }
+ },
+ {
+ "term": {
+ "lastName.keyword": {
+ "value": "Smith"
+ }
+ }
+ },
+ {
+ "term": {
+ "age": {
+ "value": 30
+ }
+ }
+ },
+ {
+ "term": {
+ "order.totalSum": {
+ "value": 45
+ }
+ }
+ }
+ ]
+ }
+ },
+ "from": 0,
+ "size": 20,
+ "sort": [{
+ "age": {
+ "order": "asc"
+ }
+ }]
+}
With CustomElasticsearchNamingAction initialized
Func<string, string>? namingAction = p => $"_{p}_";
+var mapper = new GridifyMapper<TestClass>(autoGenerateMappings: true)
+{
+ Configuration = { CustomElasticsearchNamingAction = namingAction }
+};
+
+var gq = new GridifyQuery()
+{
+ Filter = "FirstName=John",
+ Page = 1,
+ PageSize = 20,
+ OrderBy = "Age"
+};
+
+var response = await client.SearchAsync<User>(s => s
+ .Index("users")
+ .ApplyFilteringOrderingPaging(gq));
GET users/_search
+{
+ "query": {
+ "term": {
+ "_FirstName_.keyword": {
+ "value": "John"
+ }
+ }
+ },
+ "from": 0,
+ "size": 20,
+ "sort": [{
+ "_Age_": {
+ "order": "asc"
+ }
+ }]
+}
Entity Framework
Gridify.EntityFramework Package
Gridify.EntityFramework
package has two additional GridifyAsync()
and GridifyQueryableAsync()
methods.Installation
Package Manager
',5),p={class:"language-shell vp-adaptive-theme"},d=i("button",{title:"Copy Code",class:"copy"},null,-1),k=i("span",{class:"lang"},"shell",-1),y={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},c={class:"line"},m=i("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"Install-Package",-1),E=i("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," Gridify.EntityFramework",-1),g=i("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}}," -Version",-1),_={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},b=i("h3",{id:"net-cli",tabindex:"-1"},[n(".NET CLI "),i("a",{class:"header-anchor",href:"#net-cli","aria-label":'Permalink to ".NET CLI"'},"")],-1),u={class:"language-shell vp-adaptive-theme"},F=i("button",{title:"Copy Code",class:"copy"},null,-1),f=i("span",{class:"lang"},"shell",-1),C={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},w={class:"line"},v=a('dotnet add package Gridify.EntityFramework --version',5),P={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},T=a(`Compatibility layer
GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer();
DECLARE @__Value_0 nvarchar(4000) = N'vahid';
+
+SELECT [u].[Id], [u].[CreateDate], [u].[FkGuid], [u].[Name]
+FROM [Users] AS [u]
+WHERE [u].[Name] = @__Value_0
Configuration
EntityFrameworkCompatibilityLayer
true
Enables the EntityFramework Compatibility layer to make the generated expressions compatible with entity framework.bool
false
EnableEntityFrameworkCompatibilityLayer()
true
.DisableEntityFrameworkCompatibilityLayer()
false
.Gridify Client Library
Installation
npm i gridify-client
GridifyQueryBuilder
GridifyQueryBuilder
interface represents the methods available for constructing dynamic queries using the Gridify Client library.Method Parameter Description setPage page: number Set the page number for pagination. setPageSize pageSize: number Set the page size for pagination. addOrderBy field: string, descending?: boolean Add ordering based on a field. If descending
is true
, the ordering is in descending order (default is ascending).addCondition field, operator, value, caseSensitive, escapeValue Add filtering conditions. caseSensitive
and escapeValue
are optional parameters.startGroup - Start a logical grouping of conditions. endGroup - End the current logical group. and - Add the logical AND operator. or - Add the logical OR operator. build - Build and retrieve the constructed query. Conditional Operators
ConditionalOperator
enum to access supported operators. we can pass these operators to the second parameter of addCondition
methodOperator Description Equal
Equality NotEqual
Inequality LessThan
Less Than GreaterThan
Greater Than GreaterThanOrEqual
Greater Than or Equal LessThanOrEqual
Less Than or Equal Contains
String Contains (LIKE) NotContains
String Does Not Contain (NOT LIKE) StartsWith
String Starts With NotStartsWith
String Does Not Start With EndsWith
String Ends With NotEndsWith
String Does Not End With Usage Example
import { GridifyQueryBuilder, ConditionalOperator as op } from "gridify-client";
+
+const query = new GridifyQueryBuilder()
+ .setPage(2)
+ .setPageSize(10)
+ .addOrderBy("name", true)
+ .startGroup()
+ .addCondition("age", op.LessThan, 50)
+ .or()
+ .addCondition("name", op.StartsWith, "A")
+ .endGroup()
+ .and()
+ .addCondition("isActive", op.Equal, true)
+ .build();
+
+console.log(query);
{
+ "page": 2,
+ "pageSize": 10,
+ "orderBy": "name desc",
+ "filter": "(age<50|name^A),isActive=true"
+}
Filtering
Conditional Operators
Name Operator Usage example Equal =
"FieldName = Value"
NotEqual !=
"FieldName !=Value"
LessThan <
"FieldName < Value"
GreaterThan >
"FieldName > Value"
GreaterThanOrEqual >=
"FieldName >=Value"
LessThanOrEqual <=
"FieldName <=Value"
Contains - Like =*
"FieldName =*Value"
NotContains - NotLike !*
"FieldName !*Value"
StartsWith ^
"FieldName ^ Value"
NotStartsWith !^
"FieldName !^ Value"
EndsWith $
"FieldName $ Value"
NotEndsWith !$
"FieldName !$ Value"
=
or !=
operators, Gridify search for the default
and null
values.var x = personsRepo.ApplyFiltering("name=");
var x = personsRepo.Where(p =>
+ p.Name is null ||
+ p.Name == string.Empty );
Special Operators
Logical Operators
Name Operator Usage example AND ,
"FirstName = Value, LastName = Value2"
OR |
"FirstName=Value|LastName=Value2"
Parenthesis ()
"(FirstName=*Jo,Age<30)|(FirstName!=Hn,Age>30)"
Case Insensitive Operator - /i
var x = personsRepo.ApplyFiltering("FirstName=John/i");
Escaping
, | ( ) /i
to handle complex queries and case-insensitive searches. If you want to use these characters in your query values (after conditional operator), you should add a backslash \\
before them. having this regex could be helpful ([(),|]|\\/i)
.let esc = (v) => v.replace(/([(),|\\\\]|\\/i)/g, '\\\\$1')
var value = "(test,test2)";
+var esc = Regex.Replace(value, "([(),|\\\\]|\\/i)", "\\\\$1" );
+// esc = \\(test\\,test2\\)
Using Indexers
v2.15.0
, Gridify supports filtering on any property that is indexable, including sub-collections, arrays, and dictionaries. This feature allows you to create dynamic and flexible queries for a wide range of data structures.Passing Index/Key to Sub-Collections, Arrays, and Dictionaries
[ ]
.var gm = new GridifyMapper<TargetType>()
+ .AddMap("arrayProp", (target, index) => target.MyArray[index].Prop)
+ .AddMap("dictProp", (target, key) => target.MyDictionary[key]);
+
+var gq = new GridifyQuery
+{
+ Filter = "arrayProp[8] > 10 AND dictProp[name] = John"
+};
arrayProp[8] > 10
filters on the 8th element of an array property. dictProp[name] = John
filters on the value associated with the name key in a dictionary property.Custom Operators
EF.Functions.FreeText
rather than a LIKE with wildcards. In this case, you can define your own operators. (added in v2.6.0
)IGridifyOperator
interface. then you need to register it through the global CustomOperators configuration.#
character.class FreeTextOperator : IGridifyOperator
+{
+ public string GetOperator() => "#=";
+ public Expression<OperatorParameter> OperatorHandler()
+ {
+ return (prop, value) => EF.Functions.FreeText(prop, value.ToString());
+ }
+}
class RegexMatchOperator : IGridifyOperator
+{
+ public string GetOperator() => "#%";
+ public Expression<OperatorParameter> OperatorHandler()
+ {
+ return (prop, value) => Regex.IsMatch(prop.ToString(), value.ToString());
+ }
+}
class InOperator: IGridifyOperator
+{
+ public string GetOperator()
+ {
+ return "#In";
+ }
+
+ public Expression<OperatorParameter> OperatorHandler()
+ {
+ return (prop, value) => value.ToString()
+ .Split(";",StringSplitOptions.RemoveEmptyEntries)
+ .Contains(prop.ToString());
+ }
+}
+
+// usage: .ApplyFiltering("name #In John;David;Felipe")
GridifyGlobalConfiguration.CustomOperators.Register<FreeTextOperator>();
+ GridifyGlobalConfiguration.CustomOperators.Register<RegexMatchOperator>();
+ GridifyGlobalConfiguration.CustomOperators.Register<InOperator>();
Getting Started
Gridify.EntityFramework
package instead of Gridify
.Gridify
package but is designed to be more compatible with Entity Framework.Gridify.Elasticsearch
.Installation
',11),h={class:"vp-code-group vp-adaptive-theme"},d=e('',1),c={class:"blocks"},p={class:"language-shell vp-adaptive-theme active"},_=i("button",{title:"Copy Code",class:"copy"},null,-1),k=i("span",{class:"lang"},"shell",-1),g={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},u={class:"line"},y=e('dotnet add package Gridify --version',5),f={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},F={class:"language-shell vp-adaptive-theme"},b=i("button",{title:"Copy Code",class:"copy"},null,-1),m=i("span",{class:"lang"},"shell",-1),C={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},T={class:"line"},v=e('dotnet add package Gridify.EntityFramework --version',5),E={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},S={class:"language-shell vp-adaptive-theme"},w=i("button",{title:"Copy Code",class:"copy"},null,-1),G=i("span",{class:"lang"},"shell",-1),B={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},A={class:"line"},P=e('dotnet add package Gridify.Elasticsearch --version',5),I={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},V=e('npm i gridify-client
Namespace
Gridify
namespace to access the package classes and static Extension methods.using Gridify;
How to use
',6);function N(s,q,D,K,R,$){return l(),n("div",null,[o,i("div",h,[d,i("div",c,[i("div",p,[_,k,i("pre",g,[i("code",null,[i("span",u,[y,i("span",f," "+t(s.$version),1)])])])]),i("div",F,[b,m,i("pre",C,[i("code",null,[i("span",T,[v,i("span",E," "+t(s.$version),1)])])])]),i("div",S,[w,G,i("pre",B,[i("code",null,[i("span",A,[P,i("span",I," "+t(s.$version),1)])])])]),V])]),x])}const H=a(r,[["render",N]]);export{z as __pageData,H as default};
diff --git a/assets/guide_getting-started.md.DJX3p0eS.lean.js b/assets/guide_getting-started.md.DJX3p0eS.lean.js
new file mode 100644
index 00000000..5ebf948c
--- /dev/null
+++ b/assets/guide_getting-started.md.DJX3p0eS.lean.js
@@ -0,0 +1 @@
+import{_ as a,c as n,j as i,t,a4 as e,o as l}from"./chunks/framework.Cmp6zcjC.js";const z=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started.md","filePath":"guide/getting-started.md"}'),r={name:"guide/getting-started.md"},o=e("",11),h={class:"vp-code-group vp-adaptive-theme"},d=e("",1),c={class:"blocks"},p={class:"language-shell vp-adaptive-theme active"},_=i("button",{title:"Copy Code",class:"copy"},null,-1),k=i("span",{class:"lang"},"shell",-1),g={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},u={class:"line"},y=e("",5),f={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},F={class:"language-shell vp-adaptive-theme"},b=i("button",{title:"Copy Code",class:"copy"},null,-1),m=i("span",{class:"lang"},"shell",-1),C={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},T={class:"line"},v=e("",5),E={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},S={class:"language-shell vp-adaptive-theme"},w=i("button",{title:"Copy Code",class:"copy"},null,-1),G=i("span",{class:"lang"},"shell",-1),B={class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},A={class:"line"},P=e("",5),I={style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},V=e("",1),x=e("",6);function N(s,q,D,K,R,$){return l(),n("div",null,[o,i("div",h,[d,i("div",c,[i("div",p,[_,k,i("pre",g,[i("code",null,[i("span",u,[y,i("span",f," "+t(s.$version),1)])])])]),i("div",F,[b,m,i("pre",C,[i("code",null,[i("span",T,[v,i("span",E," "+t(s.$version),1)])])])]),i("div",S,[w,G,i("pre",B,[i("code",null,[i("span",A,[P,i("span",I," "+t(s.$version),1)])])])]),V])]),x])}const H=a(r,[["render",N]]);export{z as __pageData,H as default};
diff --git a/assets/guide_gridifyGlobalConfiguration.md.CPK3zDE-.js b/assets/guide_gridifyGlobalConfiguration.md.CPK3zDE-.js
new file mode 100644
index 00000000..324a14db
--- /dev/null
+++ b/assets/guide_gridifyGlobalConfiguration.md.CPK3zDE-.js
@@ -0,0 +1 @@
+import{_ as e,c as a,o as i,a4 as o}from"./chunks/framework.Cmp6zcjC.js";const g=JSON.parse('{"title":"GridifyGlobalConfiguration","description":"","frontmatter":{},"headers":[],"relativePath":"guide/gridifyGlobalConfiguration.md","filePath":"guide/gridifyGlobalConfiguration.md"}'),t={name:"guide/gridifyGlobalConfiguration.md"},l=o('GridifyGlobalConfiguration
General configurations
DefaultPageSize
int
20
CaseSensitiveMapper
name=John
and Name=John
are considered equal. You can change this behavior by setting this property to true
.bool
false
AllowNullSearch
null
keyword in filtering operations, for example, name=null
searches for all records with a null value for the name
field not the string "null"
. if you need to search for the string "null"
you can disable this option.bool
true
IgnoreNotMappedFields
bool
false
DisableNullChecks
() => field != null && field....
bool
false
DefaultDateTimeKind
DateTimeKind.Unspecified
when parsing dates. You can change this behavior by setting this property to DateTimeKind.Utc
or DateTimeKind.Local
. This option is useful when you want to use Gridify with a database that requires a specific DateTimeKind
, for example when using npgsql and postgresql.DateTimeKind
null
CustomOperators
Register
method of this property you can add your own custom operators. GridifyGlobalConfiguration.CustomOperators.Register<MyCustomOperator>();
GridifyMapper
// sample Entities
+public class Person
+{
+ public string UserName { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string Password { get; set; }
+ public Contact Contact { get; set; }
+}
+
+public class Contact
+{
+ public string Address { get; set; }
+ public int PhoneNumber { get; set; }
+}
Password
propertyaddress
and mobile
to the Contact propertyvar mapper = new GridifyMapper<Person>()
+ .GenerateMappings()
+ .RemoveMap(nameof(Person.Password))
+ .AddMap("address", p => p.Contact.Address)
+ .AddMap("mobile", p => p.Contact.PhoneNumber)
+ .AddMap("userName", p => p.UserName, v => v.ToLower());
GenerateMappings
var mapper = new GridifyMapper<Person>()
+ .GenerateMappings();
maxNestingDepth
parameter. This parameter limits how deep the mappings will be generated for nested classes and navigation properties (added in v2.15.0)
. Set it to 0 for no nesting or a positive value to control the depth (added in v2.11.0)
:var mapper = new GridifyMapper<Person>()
+ // Generates mappings for top-level properties and properties of nested classes up to 2 levels deep.
+ .GenerateMappings(2);
var mapper = new GridifyMapper<Person>(true);
+var mapperWithDepth = new GridifyMapper<Person>(true, 2);
RemoveMap
AddMap
Value convertor
mapper = mapper.AddMap("userName", p => p.UserName, value => value.ToLower());
HasMap
ClearMaps
GetCurrentMaps
GridifyMapperConfiguration
var mapperConfig = new GridifyMapperConfiguration()
+{
+ CaseSensitive = false,
+ AllowNullSearch = true,
+ IgnoreNotMappedFields = false
+};
+
+var mapper = new GridifyMapper<Person>(mapperConfig);
CaseSensitive
Case-insensitive
but you can change this behavior if you need Case-Sensitive
mappings.bool
false
var mapper = new GridifyMapper<Person>(q => q.CaseSensitive = true);
IgnoreNotMappedFields
true
Gridify don't throw an exception when a field name is not mapped. for instance, in the above example, searching for password
will not throw an exception.bool
false
var mapper = new GridifyMapper<Person>(q => q.IgnoreNotMappedFields = true);
AllowNullSearch
false
, Gridify don't allow searching on null values using the null
keyword for values.bool
true
var mapper = new GridifyMapper<Person>(q => q.AllowNullSearch = false);
DefaultDateTimeKind
DateTimeKind
value, you can change the default DateTimeKind
used when parsing dates.DateTimeKind
null
var mapper = new GridifyMapper<Person>(q => q.DefaultDateTimeKind = DateTimeKind.Utc);
Filtering on Nested Collections
Select
and SelectMany
methods to filter your data using its nested collections.Property1
of the third level.var mapper = new GridifyMapper<Level1>()
+ .AddMap("prop1", l1 => l1.Level2List
+ .SelectMany(l2 => l2.Level3List)
+ .Select(l3 => l3.Property1));
+// ...
+var query = level1List.ApplyFiltering("prop1 = 123", mapper);
SelectMany
.Defining Mappings for Indexable Properties
v2.15.0
, GridifyMapper's AddMap
method supports filtering on properties that are indexable, such as sub-collections, arrays, and dictionaries. This allows you to create dynamic queries by defining mappings to specific indexes or dictionary keys using square brackets [ ]
.Mapping to Array Indexes
[ ]
.\`var gm = new GridifyMapper<TargetType>()
+ .AddMap("arrayProp", (target, index) => target.MyArray[index].Prop);
+
+var gq = new GridifyQuery
+{
+ // Filters on the 8th element of an array property
+ Filter = "arrayProp[8] > 10"
+};
Mapping to Dictionary Keys
var gm = new GridifyMapper<TargetType>()
+ .AddMap("dictProp", (target, key) => target.MyDictionary[key]);
+
+var gm2 = new GridifyMapper<TargetType>()
+ .AddMap("navProp", (target, key) => target.NavigationProperty.Where(n => n.Key == key).Select(n => n.Value));
+
+var gq = new GridifyQuery
+{
+ // Filters on the value associated with the 'name' key in a dictionary
+ Filter = "dictProp[name] = John"
+};
Generic Overload for Non-String Dictionary Keys
AddMap<T>
method to specify the key type.var gm = new GridifyMapper<TargetType>()
+ .AddMap<Guid>("dictProp", (target, key) => target.MyDictionary[key]);
GetExpression
Expression<Func<Person, object>> selector = mapper.GetExpression(nameof(Person.Name));
GetLambdaExpression
LambdaExpression selector = mapper.GetLambdaExpression(nameof(Person.Name));
GridifyQuery
GridifyQuery
is a simple class for configuring Filtering, Ordering and Paging.var gq = new GridifyQuery()
+{
+ Filter = "FirstName=John",
+ Page = 1,
+ PageSize = 20,
+ OrderBy = "Age"
+};
+
+// Apply Filter, Sort and Paging
+Paging<Person> result = personsRepo.Gridify(gq);
IsValid
GridifyQuery
(Filter
, OrderBy
) is valid to use with a custom mapper or the auto generated mapper and returns true or false.var gq = new GridifyQuery() { Filter = "name=John" , OrderBy = "Age" };
+// true
+bool isValid = gq.IsValid<Person>();
var gq = new GridifyQuery() { Filter = "NonExist=John" , OrderBy = "Age" };
+// false (NonExist is not a property of Person)
+bool isValid = gq.IsValid<Person>();
var gq = new GridifyQuery() { Filter = "@name=!" , OrderBy = "Age" };
+// false (this is not a valid filter)
+bool isValid = gq.IsValid<Person>();
GridifyQuery
is valid for that mapper.var mapper = new GridifyMapper<Person>()
+ .AddMap("name", q => q.Name);
+var gq = new GridifyQuery() { Filter = "name=John" , OrderBy = "Age" };
+
+// false (Age is not mapped)
+bool isValid = gq.IsValid(mapper);
GetFilteringExpression
GridifyQuery.Filter
property that you can use it in the LINQ Where
method to filter the data.var gq = new GridifyQuery() { Filter = "name=John" };
+Expression<Func<T, bool>> expression = gq.GetFilteringExpression<Person>();
+var result = personsRepo.Where(expression);
Introduction
Features
Examples
Performance
Method Mean Error StdDev Ratio Allocated Alloc Ratio Gridify 599.8 us 2.76 us 2.45 us 0.92 36.36 KB 1.11 Native_LINQ 649.9 us 2.55 us 2.38 us 1.00 32.74 KB 1.00 DynamicLinq 734.8 us 13.90 us 13.01 us 1.13 119.4 KB 3.65 Sieve 1,190.9 us 7.41 us 6.93 us 1.83 53.05 KB 1.62 Fop 2,637.6 us 8.59 us 7.61 us 4.06 321.57 KB 9.82 CSharp_Scripting 216,863.8 us 4,295.66 us 6,021.92 us 336.64 23660.26 KB 722.71 Details
Ordering
asc
or desc
keywords.// asc - desc
+var x = personsRepo.ApplyOrdering("Id"); // default ascending its equal to "Id asc"
+var x = personsRepo.ApplyOrdering("Id desc"); // use descending ordering
+
+// multiple orderings example
+var x = personsRepo.ApplyOrdering("Id desc, FirstName asc, LastName");
// asc - desc
+var gq = new GridifyQuery() { OrderBy = "Id" }; // default ascending its equal to "Id asc"
+var gq = new GridifyQuery() { OrderBy = "Id desc" }; // use descending ordering
+
+// multiple orderings example
+var gq = new GridifyQuery() { OrderBy = "Id desc, FirstName asc, LastName" };
var builder = new QueryBuilder<Person>();
+// asc - desc
+builder.AddOrderBy("Id"); // default ascending its equal to "Id asc"
+builder.AddOrderBy("Id desc"); // use descending ordering
+
+// multiple orderings example
+builder.AddOrderBy("Id desc, FirstName asc, LastName");
Order By Nullable types
personsRepo.OrderBy(p => p.BirthDate.HasValue)
?
or !
) after the property name.personsRepo.OrderBy(p => p.BirthDate.HasValue)
query, you can use ?
:var x = personsRepo.ApplyOrdering("BirthDate?");
personsRepo.OrderBy(p => !p.BirthDate.HasValue)
, you can use !
:var x = personsRepo.ApplyOrdering("BirthDate!");
QueryBuilder
QueryBuilder
class is really useful if you want to manually build your query Or when you don't want to use the extension methods.Method Description AddCondition Adds a string base Filtering query AddOrderBy Adds a string base Ordering query ConfigurePaging Configure Page and PageSize AddQuery Accepts a GridifyQuery object to configure filtering,ordering and paging UseCustomMapper Accepts a GridifyMapper to use in build methods UseEmptyMapper Setup an Empty new GridifyMapper without auto generated mappings AddMap Add a single Map to existing mapper RemoveMap Remove a single Map from existing mapper ConfigureDefaultMapper Configuring default mapper when we didn't use AddMapper method IsValid Validates Condition, OrderBy, Query , Mapper and returns a bool Build Applies filtering ordering and paging to a queryable context BuildCompiled Compiles the expressions and returns a delegate for applying filtering ordering and paging to a enumerable collection BuildFilteringExpression Returns filtering expression that can be compiled for later use for enumerable collections BuildEvaluator Returns an evaluator delegate that can be use to evaluate an queryable context BuildCompiledEvaluator Returns an compiled evaluator delegate that can be use to evaluate an enumerable collection BuildWithPaging Applies filtering ordering and paging to a context, and returns paging result BuildWithPagingCompiled Compiles the expressions and returns a delegate for applying filtering ordering and paging to a enumerable collection, that returns paging result BuildWithQueryablePaging Applies filtering ordering and paging to a context, and returns queryable paging result Evaluate Directly Evaluate a context to check if all conditions are valid or not var builder = new QueryBuilder<Person>()
+ .AddCondition("name=John")
+ .AddOrderBy("age, id");
+
+var query = builder.Build(persons);
Gridify (v1)
Introduction
WebApi Simple Usage example
// ApiController
+
+[Produces(typeof(Paging<Person>))]
+public IActionResult GetPersons([FromQuery] GridifyQuery gQuery)
+{
+ // Gridify => Filter,Sort & Apply Paging
+ // in short, Gridify returns data especially for data Grids.
+ return myDbContext.Persons.Gridify(gQuery);
+}
http://exampleDomain.com/api/GetPersons?pageSize=100&page=1&sortBy=FirstName&isSortAsc=false&filter=Age%3D%3D10
http://exampleDomain.com/api/GetPersons
What is GridifyQuery (basic usage example)
// usually, we don't need to create this object manually
+// for example, we get this object as a parameter from our API Controller
+var gQuery = new GridifyQuery()
+{
+ Filter = "FirstName==John",
+ IsSortAsc = false,
+ Page = 1,
+ PageSize = 20,
+ SortBy = "LastName"
+};
+
+Paging<Person> pData =
+ myDbContext.Persons // we can use Any list or repository or EntityFramework context
+ .Gridify(gQuery); // Filter,Sort & Apply Paging
+
+
+// pData.TotalItems => Count persons with 'John', First name
+// pData.Items => First 20 Persons with 'John', First Name
ApplyFiltering
ApplyFiltering
extension instead of Gridify
.var query = myDbContext.Persons.ApplyFiltering("name == John");
+// this is equal to :
+// myDbContext.Persons.Where(p => p.Name == "John");
see more examples in the tests
Performance comparison
Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Allocated Native Linq 869.3 us 10.54 us 9.86 us 1.00 0.00 5.8594 2.9297 36 KB Gridify 928.1 us 13.41 us 11.89 us 1.07 0.02 6.8359 2.9297 46 KB Dynamic Linq 1,068.5 us 10.66 us 9.97 us 1.23 0.02 19.5313 9.7656 122 KB Sieve 1,126.8 us 10.73 us 10.04 us 1.30 0.02 8.7891 3.9063 54 KB Installation
Install-Package Gridify
dotnet add package Gridify
Extensions
IQueryable
:Extension Description ApplyFiltering (string) Apply Filtering using a raw string
and returns an IQueryable<T>
ApplyFiltering (GridifyQuery) Apply Filtering using string Filter
property of GridifyQuery
class and returns an IQueryable<T>
ApplyOrdering Apply Ordering using string SortBy
and bool IsSortAsc
properties of GridifyQuery
class and returns an IQueryable<T>
ApplyPaging Apply paging using short Page
and int PageSize
properties of GridifyQuery
class and returns an IQueryable<T>
ApplyOrderingAndPaging Apply Both Ordering and paging and returns an IQueryable<T>
ApplyFilterAndOrdering Apply Both Filtering and Ordering and returns an IQueryable<T>
ApplyEverything Apply Filtering,Ordering and paging and returns an IQueryable<T>
GridifyQueryable Like ApplyEverything but it returns a QueryablePaging<T>
that have an extra int totalItems
property to use for paginationGridify Receives a GridifyQuery
,Load All requested data and returns Paging<T>
Gridify
function is an ALL-IN-ONE package, that applies filtering and ordering and paging to your data and returns a Paging<T>
,ApplyFiltering
function instead.Supported Filtering Operators
Name Operator Usage example Equal ==
"FieldName ==Value"
NotEqual !=
"FieldName !=Value"
LessThan <
"FieldName < Value"
GreaterThan >
"FieldName > Value"
GreaterThanOrEqual >=
"FieldName >=Value"
LessThanOrEqual <=
"FieldName <=Value"
Contains - Like =*
"FieldName =*Value"
NotContains - NotLike !*
"FieldName !*Value"
StartsWith ^
"FieldName ^ Value"
NotStartsWith !^
"FieldName !^ Value"
EndsWith $
"FieldName $ Value"
NotEndsWith !$
"FieldName !$ Value"
AND - && ,
"FirstName ==Value, LastName ==Value2"
OR - || |
"FirstName==Value|LastName==Value2"
Parenthesis ()
"(FirstName=*Jo,Age<30)|(FirstName!=Hn,Age>30)"
()
with AND (,
) + OR (|
) operators., | ( )
to handle complex queries. if you want to use these characters in your query values (after ==
), you should add a backslash \\
before them.let esc = (v) => v.replace(/([(),|])/g, '\\\\$1')
var value = "(test,test2)";
+var esc = Regex.Replace(value, "([(),|])", "\\\\$1" ); // esc = \\(test\\,test2\\)
Custom Mapping Support
GridifyMapper
object that automatically maps your string based field names to actual properties in your Entities but if you have a custom DTO (Data Transfer Object) you can create a custom instance of GridifyMapper
and use it to create your mappings.// example Entities
+public class Person
+{
+ public string FirstName {get;set;}
+ public string LastName {get;set;}
+ public Contact Contact {get;set;}
+
+}
+public class Contact
+{
+ public string Address {get;set;}
+ public int PhoneNumber {get;set;}
+}
+
+// example DTO
+public class PersonDTO
+{
+ public string FirstName {get;set;}
+ public string LastName {get;set;}
+
+ public string Address {get;set;}
+ public int PhoneNumber {get;set;}
+}
+
+//// GridifyMapper Usage example -------------
+
+var customMappings = new GridifyMapper<Person>()
+ // because FirstName and LastName is exists in both DTO and Entity classes we can Generate them
+ .GenerateMappings()
+ // add custom mappings
+ .AddMap("address", q => q.Contact.Address )
+ .AddMap("PhoneNumber", q => q.Contact.PhoneNumber );
+
+
+// as i mentioned before. usually we don't need create this object manually.
+var gQuery = new GridifyQuery()
+{
+ Filter = "FirstName==John,Address=*st",
+ IsSortAsc = true,
+ SortBy = "PhoneNumber"
+};
+
+// myRepository: could be entity framework context or any other collections
+var gridifiedData = myRepository.Persons.Gridify(gQuery, customMappings);
GridifyMapper
is Case-insensitive
but you can change this behavior if you need Case-Sensitive
mappings.var customMappings = new GridifyMapper<Person>(true); // mapper is case-sensitive now.
Combine Gridify with AutoMapper
//AutoMapper ProjectTo + Filtering Only, example
+var query = myDbContext.Persons.ApplyFiltering(gridifyQuery);
+var result = query.ProjectTo<PersonDTO>().ToList();
+
+// AutoMapper ProjectTo + Filtering + Ordering + Paging, example
+QueryablePaging<Person> qp = myDbContext.Persons.GridifyQueryable(gridifyQuery);
+var result = new Paging<Person> () { Items = qp.Query.ProjectTo<PersonDTO>().ToList (), TotalItems = qp.TotalItems };
EntityFramework integration
Gridify.EntityFramework
package instead.GridifyAsync()
and GridifyQueryableAsync()
functions.dotnet add package Gridify.EntityFramework
Contribution
Contribute to the project
Todos
Documentation
docs
directory.yarn install
+yarn dev
npm install
+npm run dev
How to contribute
Contributors
Using Gridify in API Controllers
Describing the Scenario
// UserController
+// ...
+public IEnumerable<User> GetUsers()
+{
+ // context can be Entity Framework, a repository, or whatever.
+ return context.Users.ToList();
+}
Solving Problems Using Gridify
public Paging<User> GetUsers([FromQuery] GridifyQuery query)
+{
+ return context.Users.Gridify(query);
+}
What is the Paging Return Value?
Paging
class is a generic Data Transfer Object (DTO) that has two properties:public int Count { get; set; }
+public IEnumerable<T> Data { get; set; }
Count
property indicates the total number of records, while the Data
property contains the records on the current page.What is GridifyQuery?
GridifyQuery
is a class that represents the query parameters passed to the Gridify
method.Sample Request Query String
http://exampleDomain.com/api/GetUsers?
+ pageSize=100&
+ page=1&
+ orderBy=FirstName&
+ filter=Age>10
GridifyQuery
and use the default pagination values, which are pageSize=20
and page=1
.http://exampleDomain.com/api/GetUsers
More Information
GridifyMapper
instance as a parameter.Gridify (v1)
Introduction
WebApi Simple Usage example
// ApiController
+
+[Produces(typeof(Paging<Person>))]
+public IActionResult GetPersons([FromQuery] GridifyQuery gQuery)
+{
+ // Gridify => Filter,Sort & Apply Paging
+ // in short, Gridify returns data especially for data Grids.
+ return myDbContext.Persons.Gridify(gQuery);
+}
http://exampleDomain.com/api/GetPersons?pageSize=100&page=1&sortBy=FirstName&isSortAsc=false&filter=Age%3D%3D10
http://exampleDomain.com/api/GetPersons
What is GridifyQuery (basic usage example)
// usually, we don't need to create this object manually
+// for example, we get this object as a parameter from our API Controller
+var gQuery = new GridifyQuery()
+{
+ Filter = "FirstName==John",
+ IsSortAsc = false,
+ Page = 1,
+ PageSize = 20,
+ SortBy = "LastName"
+};
+
+Paging<Person> pData =
+ myDbContext.Persons // we can use Any list or repository or EntityFramework context
+ .Gridify(gQuery); // Filter,Sort & Apply Paging
+
+
+// pData.TotalItems => Count persons with 'John', First name
+// pData.Items => First 20 Persons with 'John', First Name
ApplyFiltering
ApplyFiltering
extension instead of Gridify
.var query = myDbContext.Persons.ApplyFiltering("name == John");
+// this is equal to :
+// myDbContext.Persons.Where(p => p.Name == "John");
see more examples in the tests
Performance comparison
Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Allocated Native Linq 869.3 us 10.54 us 9.86 us 1.00 0.00 5.8594 2.9297 36 KB Gridify 928.1 us 13.41 us 11.89 us 1.07 0.02 6.8359 2.9297 46 KB Dynamic Linq 1,068.5 us 10.66 us 9.97 us 1.23 0.02 19.5313 9.7656 122 KB Sieve 1,126.8 us 10.73 us 10.04 us 1.30 0.02 8.7891 3.9063 54 KB Installation
Install-Package Gridify
dotnet add package Gridify
Extensions
IQueryable
:Extension Description ApplyFiltering (string) Apply Filtering using a raw string
and returns an IQueryable<T>
ApplyFiltering (GridifyQuery) Apply Filtering using string Filter
property of GridifyQuery
class and returns an IQueryable<T>
ApplyOrdering Apply Ordering using string SortBy
and bool IsSortAsc
properties of GridifyQuery
class and returns an IQueryable<T>
ApplyPaging Apply paging using short Page
and int PageSize
properties of GridifyQuery
class and returns an IQueryable<T>
ApplyOrderingAndPaging Apply Both Ordering and paging and returns an IQueryable<T>
ApplyFilterAndOrdering Apply Both Filtering and Ordering and returns an IQueryable<T>
ApplyEverything Apply Filtering,Ordering and paging and returns an IQueryable<T>
GridifyQueryable Like ApplyEverything but it returns a QueryablePaging<T>
that have an extra int totalItems
property to use for paginationGridify Receives a GridifyQuery
,Load All requested data and returns Paging<T>
Gridify
function is an ALL-IN-ONE package, that applies filtering and ordering and paging to your data and returns a Paging<T>
,ApplyFiltering
function instead.Supported Filtering Operators
Name Operator Usage example Equal ==
"FieldName ==Value"
NotEqual !=
"FieldName !=Value"
LessThan <
"FieldName < Value"
GreaterThan >
"FieldName > Value"
GreaterThanOrEqual >=
"FieldName >=Value"
LessThanOrEqual <=
"FieldName <=Value"
Contains - Like =*
"FieldName =*Value"
NotContains - NotLike !*
"FieldName !*Value"
StartsWith ^
"FieldName ^ Value"
NotStartsWith !^
"FieldName !^ Value"
EndsWith $
"FieldName $ Value"
NotEndsWith !$
"FieldName !$ Value"
AND - && ,
"FirstName ==Value, LastName ==Value2"
OR - || |
"FirstName==Value|LastName==Value2"
Parenthesis ()
"(FirstName=*Jo,Age<30)|(FirstName!=Hn,Age>30)"
()
with AND (,
) + OR (|
) operators., | ( )
to handle complex queries. if you want to use these characters in your query values (after ==
), you should add a backslash \
before them.let esc = (v) => v.replace(/([(),|])/g, '\\$1')
var value = "(test,test2)";
+var esc = Regex.Replace(value, "([(),|])", "\\$1" ); // esc = \(test\,test2\)
Custom Mapping Support
GridifyMapper
object that automatically maps your string based field names to actual properties in your Entities but if you have a custom DTO (Data Transfer Object) you can create a custom instance of GridifyMapper
and use it to create your mappings.// example Entities
+public class Person
+{
+ public string FirstName {get;set;}
+ public string LastName {get;set;}
+ public Contact Contact {get;set;}
+
+}
+public class Contact
+{
+ public string Address {get;set;}
+ public int PhoneNumber {get;set;}
+}
+
+// example DTO
+public class PersonDTO
+{
+ public string FirstName {get;set;}
+ public string LastName {get;set;}
+
+ public string Address {get;set;}
+ public int PhoneNumber {get;set;}
+}
+
+//// GridifyMapper Usage example -------------
+
+var customMappings = new GridifyMapper<Person>()
+ // because FirstName and LastName is exists in both DTO and Entity classes we can Generate them
+ .GenerateMappings()
+ // add custom mappings
+ .AddMap("address", q => q.Contact.Address )
+ .AddMap("PhoneNumber", q => q.Contact.PhoneNumber );
+
+
+// as i mentioned before. usually we don't need create this object manually.
+var gQuery = new GridifyQuery()
+{
+ Filter = "FirstName==John,Address=*st",
+ IsSortAsc = true,
+ SortBy = "PhoneNumber"
+};
+
+// myRepository: could be entity framework context or any other collections
+var gridifiedData = myRepository.Persons.Gridify(gQuery, customMappings);
GridifyMapper
is Case-insensitive
but you can change this behavior if you need Case-Sensitive
mappings.var customMappings = new GridifyMapper<Person>(true); // mapper is case-sensitive now.
Combine Gridify with AutoMapper
//AutoMapper ProjectTo + Filtering Only, example
+var query = myDbContext.Persons.ApplyFiltering(gridifyQuery);
+var result = query.ProjectTo<PersonDTO>().ToList();
+
+// AutoMapper ProjectTo + Filtering + Ordering + Paging, example
+QueryablePaging<Person> qp = myDbContext.Persons.GridifyQueryable(gridifyQuery);
+var result = new Paging<Person> () { Items = qp.Query.ProjectTo<PersonDTO>().ToList (), TotalItems = qp.TotalItems };
EntityFramework integration
Gridify.EntityFramework
package instead.GridifyAsync()
and GridifyQueryableAsync()
functions.dotnet add package Gridify.EntityFramework
Contribution