diff --git a/.github/workflows/PullRequest-CI.yml b/.github/workflows/PullRequest-CI.yml index cd8dfb2..c3c8441 100644 --- a/.github/workflows/PullRequest-CI.yml +++ b/.github/workflows/PullRequest-CI.yml @@ -10,7 +10,7 @@ jobs: Run-Lint: runs-on: ubuntu-latest env: - github-token: '${{ secrets.GITHUB_TOKEN }}' + github-token: '${{ secrets.GH_Packages }}' steps: - name: Step-01 Checkout code uses: actions/checkout@v3 @@ -52,7 +52,7 @@ jobs: - name: Step-04 Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Step-05 Restore dependencies run: dotnet restore @@ -85,7 +85,7 @@ jobs: outputs: semVersion: ${{ needs.Build-Release.outputs.semVersion }} env: - github-token: '${{ secrets.GITHUB_TOKEN }}' + github-token: '${{ secrets.GH_Packages }}' nuget-token: '${{ secrets.NUGET_API_KEY }}' working-directory: /home/runner/work/Schemio/Schemio steps: @@ -100,16 +100,16 @@ jobs: - name: Step-03 Publish to Github Packages run: find -name "*.nupkg" -print -exec gpr push -k ${{env.github-token}} {} \; - - - name: Step-03 Release to Nuget Org - if: ${{ startsWith(github.head_ref, 'release/')}} - run: dotnet nuget push ${{env.working-directory}}/src/Schemio/bin/Release/*.nupkg --skip-duplicate --api-key ${{ env.nuget-token }} --source https://api.nuget.org/v3/index.json Release: name: Release to Nuget needs: [Package] if: ${{ startsWith(github.head_ref, 'release/')}} runs-on: ubuntu-latest + env: + github-token: '${{ secrets.GH_Packages }}' + nuget-token: '${{ secrets.NUGET_API_KEY }}' + working-directory: /home/runner/work/Schemio/Schemio steps: - uses: actions/checkout@v2 @@ -145,10 +145,10 @@ jobs: # TAG_FORMAT: v* # API key to authenticate with NuGet server, or a token, issued for GITHUB_USER if you use GPR - # NUGET_KEY: ${{secrets.NUGET_API_KEY}} + NUGET_KEY: ${{secrets.NUGET_API_KEY}} # NuGet server uri hosting the packages, defaults to https://api.nuget.org - # NUGET_SOURCE: https://api.nuget.org + NUGET_SOURCE: https://api.nuget.org/v3/index.json # Flag to toggle pushing symbols along with nuget package to the server, disabled by default # INCLUDE_SYMBOLS: false diff --git a/.github/workflows/PullRequest-CodeQL.yml b/.github/workflows/PullRequest-CodeQL.yml index c1b075e..741c609 100644 --- a/.github/workflows/PullRequest-CodeQL.yml +++ b/.github/workflows/PullRequest-CodeQL.yml @@ -73,4 +73,4 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: - category: "/language:${{matrix.language}}" \ No newline at end of file + category: "/language:${{matrix.language}}" diff --git a/GitVersion.yml b/GitVersion.yml index 89b0a62..280505f 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 1.0.0 +next-version: 2.0.0 tag-prefix: '[vV]' mode: ContinuousDeployment branches: diff --git a/Images/Schemio-Control-Flow.png b/Images/Schemio-Control-Flow.png new file mode 100644 index 0000000..0f3c7db Binary files /dev/null and b/Images/Schemio-Control-Flow.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..daf3b65 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Code Shayk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2d23968..55f3cc9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# ninja Schemio v1.0 +# ninja Schemio v2.0 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/CodeShayk/Schemio/blob/master/LICENSE.md) [![Master-Build](https://github.com/CodeShayk/Schemio/actions/workflows/Build-Master.yml/badge.svg)](https://github.com/CodeShayk/Schemio/actions/workflows/Build-Master.yml) [![GitHub Release](https://img.shields.io/github/v/release/CodeShayk/Schemio?logo=github&sort=semver)](https://github.com/CodeShayk/Schemio/releases/latest) [![Master-CodeQL](https://github.com/CodeShayk/Schemio/actions/workflows/Master-CodeQL.yml/badge.svg)](https://github.com/CodeShayk/Schemio/actions/workflows/Master-CodeQL.yml) -[![.Net 8.0](https://img.shields.io/badge/.Net-8.0-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +[![.Net 9.0](https://img.shields.io/badge/.Net-9.0-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) -- #### Nuget Packages | Package | Latest | Details | @@ -11,12 +11,15 @@ | Schemio.Core|[![NuGet version](https://badge.fury.io/nu/Schemio.Core.svg)](https://badge.fury.io/nu/Schemio.Core) | Provides `core` functionality to configure nested queries and transformers. With ability to map schema paths (XPath/JSONPath) to entity's object graph. `No QueryEngine` provided and requires implementing IQueryEngine to execute IQuery instances. | | Schemio.SQL|[![NuGet version](https://badge.fury.io/nu/Schemio.SQL.svg)](https://badge.fury.io/nu/Schemio.SQL)| Provides schemio with query engine using `Dapper` to execute SQL queries. | | Schemio.EntityFramework|[![NuGet version](https://badge.fury.io/nu/Schemio.EntityFramework.svg)](https://badge.fury.io/nu/Schemio.EntityFramework)| Provides schemio with `Entity Framework` query engine to execute queries using DbContext. | +| Schemio.API|[![NuGet version](https://badge.fury.io/nu/Schemio.Api.svg)](https://badge.fury.io/nu/Schemio.Api)| Provides schemio with `Web Api` query engine to execute apis using HttpClient. | ## Concept ### What is Schemio? -`Schemio` is a data aggregation framework that -- allows fetching `aggregated data` from `heterogeneous` data storages. You could combine queries targetting different data platforms (example. SQL, API, Cache) to return an aggregated data entity. -- allows `conditionally` fetching only `parts` of the data entity. You could retrieve an aggregated data entity with selective sections of its `object graph` populated depending on the context passed with the request. +`Schemio` is a data aggregation framework using queries that can target different data platforms. + +Key benefits: +- allows fetching `aggregated` data from `heterogeneous` data storages. You could combine queries targetting different data platforms (example. `SQL`, `API`, `Cache`) to return an aggregated data `entity`. +- allows `selectively` fetching only `parts` of the aggregated data entity. You could retrieve an aggregated data entity with only `sections` of its `object graph` populated with data depending upon the `schema paths` passed with the request. ### When to use Schemio? Schemio is perfect fit for many use cases. Few examples that require the service tier to dynamically fetch aggregated data with high performance, availability and scalability are @@ -28,7 +31,7 @@ Schemio is perfect fit for many use cases. Few examples that require the service ## Getting Started? ### i. Installation -Install the latest nuget package as appropriate for Core, SQL using Dapper or EntityFramework. +Install the latest nuget package as appropriate for `Core`, `Web API`, `SQL` using `Dapper` or `EntityFramework` using commands below. `Scemio.Core` - for installing schemio for `bespoke` implementation of query engine. ``` @@ -36,13 +39,16 @@ NuGet\Install-Package Schemio.Core ``` `Schemio.SQL` - for installing schemio for SQL with `Dapper` engine. ``` -NuGet\Install-Package Schemio.SQL +NuGet\Install-Package Schemio.SQL ``` `Schemio.EntityFramework` - for installing schemio for SQL with `EntityFramework` engine. ``` NuGet\Install-Package Schemio.EntityFramework ``` - +`Schemio.API` - for installing schemio for Web API with `HttpClient` engine. +``` +NuGet\Install-Package Schemio.API +``` ### ii. Developer Guide Please see [Developer Guide](https://codeshayk.github.io/Schemio/) for details on how to implement schemio in your project. @@ -56,11 +62,11 @@ If you are having problems, please let me know by [raising a new issue](https:// This project is licensed with the [MIT license](LICENSE). ## Version History -The main branch is now on .NET 8.0. The following previous versions are available: +The main branch is now on .NET 9.0. The following previous versions are available: | Version | Release Notes | Developer Guide | | -------- | --------|--------| | [`v1.0.0`](https://github.com/CodeShayk/Schemio/tree/v1.0.0) | [Notes](https://github.com/CodeShayk/Schemio/releases/tag/v1.0.0) | [Guide](https://github.com/CodeShayk/Schemio/blob/v1.0.0/index.md) | -| [`Pre-Release v2.0.0`](https://github.com/CodeShayk/Schemio/tree/v2.0.0) | [Notes](https://github.com/CodeShayk/Schemio/releases/tag/v2.0.0) | [Guide](https://github.com/CodeShayk/Schemio/blob/v2.0.0/index.md) | +| [`v2.0.0`](https://github.com/CodeShayk/Schemio/tree/v2.0.0) | [Notes](https://github.com/CodeShayk/Schemio/releases/tag/v2.0.0) | [Guide](https://github.com/CodeShayk/Schemio/blob/v2.0.0/index.md) | ## Credits Thank you for reading. Please fork, explore, contribute and report. Happy Coding !! :) diff --git a/Schemio.sln b/Schemio.sln index 837bcd6..75e0047 100644 --- a/Schemio.sln +++ b/Schemio.sln @@ -5,22 +5,17 @@ VisualStudioVersion = 17.5.33516.290 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F41DA3D8-A0E9-4A05-8A35-78313C0F5804}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio", "src\Schemio\Schemio.Core.csproj", "{6F017146-B95A-4081-9CC0-B0245F78D72B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{07BAE427-96CF-4F9B-80A9-48CFB0A89CF3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{AF995FEF-BB94-48D0-B02B-6671DA73056B}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore + index.md = index.md LICENSE.md = LICENSE.md README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.SQL", "src\Schemio.SQL\Schemio.SQL.csproj", "{1A0CB973-23C9-4A17-905E-59510CD18932}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.EntityFramework", "src\Schemio.EntityFramework\Schemio.EntityFramework.csproj", "{6B92CC17-B7DB-446F-BF2F-A93696D48B5D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.Tests", "tests\Schemio.Tests\Schemio.Tests.csproj", "{B09236CB-BBD2-4DCF-A698-74CCCAB29FFB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.Core.Tests", "tests\Schemio.Core.Tests\Schemio.Core.Tests.csproj", "{B09236CB-BBD2-4DCF-A698-74CCCAB29FFB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.SQL.Tests", "tests\Schemio.SQL.Tests\Schemio.SQL.Tests.csproj", "{1E319404-8EF0-40A1-A9D7-A404A71A98C4}" EndProject @@ -35,24 +30,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{39FD80 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.EntityFramework.Tests", "tests\Schemio.EntityFramework.Tests\Schemio.EntityFramework.Tests.csproj", "{FE039675-00BE-4DC5-945F-14E7625170D0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.Core", "src\Schemio.Core\Schemio.Core.csproj", "{4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.EntityFramework", "src\Schemio.EntityFramework\Schemio.EntityFramework.csproj", "{E8F8C13E-2E05-4092-84FD-B7B7B12DABBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.SQL", "src\Schemio.SQL\Schemio.SQL.csproj", "{52986844-698F-486B-AEC9-846AAF50CF46}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.API", "src\Schemio.API\Schemio.API.csproj", "{0C1A05D2-653D-4F88-B397-BA53E0BA7281}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.API.Tests", "tests\Schemio.API.Tests\Schemio.API.Tests.csproj", "{D0EE40C1-87EF-413D-A046-DC37D58BC344}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6F017146-B95A-4081-9CC0-B0245F78D72B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F017146-B95A-4081-9CC0-B0245F78D72B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F017146-B95A-4081-9CC0-B0245F78D72B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F017146-B95A-4081-9CC0-B0245F78D72B}.Release|Any CPU.Build.0 = Release|Any CPU - {1A0CB973-23C9-4A17-905E-59510CD18932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A0CB973-23C9-4A17-905E-59510CD18932}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A0CB973-23C9-4A17-905E-59510CD18932}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A0CB973-23C9-4A17-905E-59510CD18932}.Release|Any CPU.Build.0 = Release|Any CPU - {6B92CC17-B7DB-446F-BF2F-A93696D48B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B92CC17-B7DB-446F-BF2F-A93696D48B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B92CC17-B7DB-446F-BF2F-A93696D48B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B92CC17-B7DB-446F-BF2F-A93696D48B5D}.Release|Any CPU.Build.0 = Release|Any CPU {B09236CB-BBD2-4DCF-A698-74CCCAB29FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B09236CB-BBD2-4DCF-A698-74CCCAB29FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B09236CB-BBD2-4DCF-A698-74CCCAB29FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -65,18 +58,40 @@ Global {FE039675-00BE-4DC5-945F-14E7625170D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE039675-00BE-4DC5-945F-14E7625170D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE039675-00BE-4DC5-945F-14E7625170D0}.Release|Any CPU.Build.0 = Release|Any CPU + {4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C}.Release|Any CPU.Build.0 = Release|Any CPU + {E8F8C13E-2E05-4092-84FD-B7B7B12DABBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8F8C13E-2E05-4092-84FD-B7B7B12DABBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8F8C13E-2E05-4092-84FD-B7B7B12DABBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8F8C13E-2E05-4092-84FD-B7B7B12DABBB}.Release|Any CPU.Build.0 = Release|Any CPU + {52986844-698F-486B-AEC9-846AAF50CF46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52986844-698F-486B-AEC9-846AAF50CF46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52986844-698F-486B-AEC9-846AAF50CF46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52986844-698F-486B-AEC9-846AAF50CF46}.Release|Any CPU.Build.0 = Release|Any CPU + {0C1A05D2-653D-4F88-B397-BA53E0BA7281}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C1A05D2-653D-4F88-B397-BA53E0BA7281}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C1A05D2-653D-4F88-B397-BA53E0BA7281}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C1A05D2-653D-4F88-B397-BA53E0BA7281}.Release|Any CPU.Build.0 = Release|Any CPU + {D0EE40C1-87EF-413D-A046-DC37D58BC344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0EE40C1-87EF-413D-A046-DC37D58BC344}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0EE40C1-87EF-413D-A046-DC37D58BC344}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0EE40C1-87EF-413D-A046-DC37D58BC344}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {6F017146-B95A-4081-9CC0-B0245F78D72B} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} - {1A0CB973-23C9-4A17-905E-59510CD18932} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} - {6B92CC17-B7DB-446F-BF2F-A93696D48B5D} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} {B09236CB-BBD2-4DCF-A698-74CCCAB29FFB} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} {1E319404-8EF0-40A1-A9D7-A404A71A98C4} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} {39FD806A-D320-43C9-800F-E9D9BCC90623} = {AF995FEF-BB94-48D0-B02B-6671DA73056B} {FE039675-00BE-4DC5-945F-14E7625170D0} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} + {4F9B6FE3-CAC4-4594-84AD-98D1ECDE180C} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} + {E8F8C13E-2E05-4092-84FD-B7B7B12DABBB} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} + {52986844-698F-486B-AEC9-846AAF50CF46} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} + {0C1A05D2-653D-4F88-B397-BA53E0BA7281} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} + {D0EE40C1-87EF-413D-A046-DC37D58BC344} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C0FF62D6-1374-4939-A546-432862338528} diff --git a/index.md b/index.md index aeb661e..3d66219 100644 --- a/index.md +++ b/index.md @@ -1,518 +1,596 @@ # Developer Guide + ## i. Installation -Install the latest nuget package as appropriate. -`Scemio.Core` - for installing schemio for `bespoke` implementation of query engine. +`Schemio` allows you to aggregate data from heterogeneous data stores offering `SQL` & `API` packages out of the box below. SQL queries are supported by `Dapper` and `EntityFramework` engines. You could also `extend` Schemio to provide your own implementation(s) of `Query` and supporting `Query Engine` to retrieve data from `custom` data store(s). + +Below are the Nuget packages available. + +`Scemio.Core` - Install to extend schemio to implement `custom` querying engine. ``` NuGet\Install-Package Schemio.Core ``` -`Schemio.SQL` - for installing schemio for SQL with `Dapper` engine. +`Schemio.SQL` - Install when you would like to include SQL `Dapper` queries to access SQL database. ``` NuGet\Install-Package Schemio.SQL ``` -`Schemio.EntityFramework` - for installing schemio for SQL with `EntityFramework` engine. +`Schemio.EntityFramework` - Install when you would like to include SQL `EntityFramework` queries to access SQL database. ``` NuGet\Install-Package Schemio.EntityFramework ``` -## ii. Implementation: Usingse Schemio - -To use schemio you need to -> Step 1 - Setup the entity to be fetched. -> -> Step 2 - Construct the `DataProvider` with required dependencies. - -### Step 1. Entity Setup -Setting up an entity includes the following. -* Define the `entity` to be fetched using `DataProvider` - which is basically a class with entire object graph (with nested typed properties). -* Define the `entity schema` which is schema path mapping of the entire entity object graph. Each mapping consists of a `query` and `transformer` pair mapped to a sections of object graph (using XPaths or JsonPath for schema paths) - -#### 1.1 Entity -To mark the class as Entity, implement the class from `IEntity` interface. -Bear in mind this is the root entity to be fetched. - ->Below is an example `Customer` entity. -> -> ``` -> public class Customer : IEntity -> { -> public int CustomerId { get; set; } -> public string CustomerCode { get; set; } -> public string CustomerName { get; set; } -> public Communication Communication { get; set; } -> public Order[] Orders { get; set; } -> } -> ``` +`Schemio.Api` - Install when you would like to include web api queries with `HttpClient` query engine. +``` +NuGet\Install-Package Schemio.Api +``` +## ii. Implementation: Using Schemio + +To use **Schemio** you need to do the below steps +- **Step 1**: Define the aggregated `Entity`. +- **Step 2**: Setup the aggregate `Configuration` comprising of `Query`/`Transformer` hierarchical nested mappings. +- **Step 3**: Construct the `DataProvider` with required dependencies. + +### Step 1. Define Aggregate Entity. +To create an aggregate `Entity`, implement the class from `IEntity` interface. This is the entity that will be returned as aggregated result from multiple queries assembled to execute against homogeneous or heterogeneous data storage's. + +Below is an example `Customer` entity. + +``` +public class Customer : IEntity +{ + public int CustomerId { get; set; } + public string CustomerCode { get; set; } + public string CustomerName { get; set; } + public Communication Communication { get; set; } + public Order[] Orders { get; set; } +} +``` For the customer class, we can see there are three levels of nesting in the object graph. - Level 1 with paths: `Customer` - Level 2 with paths: `Customer.Communication` and `Customer.Orders` - Level 3 with paths: `Customer.Orders.Items` -If we choose XML Schema Definition (XSD) for the object schema of the above Customer class fo mapping with XPATHs -then below is the Customer XSD and XPaths for different nesting levels. - -> Customer XSD is -> ``` -> Coming soon... -> ``` - -> Schema mappings using XPaths are -> ``` -> - Level 1 with XPath: Customer -> - Level 2 with XPaths: Customer/Communication and Customer/Orders -> - Level 3 with XPath: Customer/Orders/Order/Items/Item - -#### 1.2 Entity Schema Definition -Define entity schema definition for the entity in context. - -* `Entity schema definition` is basically a configuration with hierarchy of `query/transformer` pairs mapped to the schema paths pointing to different levels of the entity's object graph. -* `Query` is an implementation to fetch data for a certain section of object graph from an underlying data storage. -* `Transformer` is an implementation to map the data fetched by the linked query to the relevant sections of the entity's object graph. - -To define Entity schema, implement `BaseEntitySchema` interface where T is entity in context. - -> -Example Entity Schema Definition (using XPaths) -> The `Customer` entity with `three` levels of `nesting` is configured below in `CustomerSchema` definition to show `query/transformer` pairs nested accordingly mapping to object graph using the XPath definitions. -> -> ``` -> internal class CustomerSchema : BaseEntitySchema -> { -> public override IEnumerable> GetSchema() -> { -> return CreateSchema.For() -> .Map(For.Paths("customer"), -> customer => customer.Dependents -> .Map(For.Paths("customer/communication")) -> .Map(For.Paths("customer/orders"), -> customerOrders => customerOrders.Dependents -> .Map(For.Paths("customer/orders/order/items"))) -> ).Create(); -> } -> } ->``` - -##### i. Query/Transformer Mapping -Every `Query` type in the `EntitySchema` definition should have a complementing `Transformer` type. -You could map multiple `schema paths` to a given query/transformer pair. Currently, `XPath` and `JSONPath` schema languages are supported. +If we choose XML Schema Definition (XSD) for the Customer entity then XPaths for nesting levels should be. +``` +- Level 1 with XPath: Customer +- Level 2 with XPaths: Customer/Communication and Customer/Orders +- Level 3 with XPath: Customer/Orders/Order/Items/Item +``` +### Step 2: Setup Entity Aggregate Configuration +To define `Entity Configuration`, derive from `EntityConfiguration` class where `TEntity` is aggregate entity in context (ie. `IEntity`). ->Below is the snippet from `CustomerSchema` definition. ->``` -> .Map(For.Paths("customer", "customer/code", "customer/name")) ->``` +The `Entity Configuration` is basically `hierarchies` of `Query` & `Transformer` pairs mapped to the schema `paths` pointing to various `nesting` levels in the entity's object graph. +* `Query` is an implementation to `fetch` data for mapped sections of object graph. +* `Transformer` is an implementation to `map` data fetched by the associated query to the relevant sections of the entity's object graph. -##### ii. Nested Query/Transformer Mappings -* You could nest query/transformer pairs in a `parent/child` hierarchy. In which case the output of the parent query will serve as the input to the child query to resolve its query paramter. -* The query/transformer mappings can be `nested` to `5` levels down. -* When certain `schema paths` are included in the DataProvider `request` to fetch the Entity, the relevant query and transformer pairs get executed in the order of their nesting to hydrate the entity. +Below is an example Entity Configuration for the Customer Entity. ->Example nesting of Communication query under Customer query. ->``` -> .Map(For.Paths("customer"), -- Parent -> customer => customer.Dependents -> .Map(For.Paths("customer/communication")) -- Child ->``` - - -Please see the execution sequence below for queries and transformers nested in CustomerSchema implemented above. - -image - - -`Please Note:` If you need to support custom schema language for mapping the object graph, then see extending schemio section below. - - -#### 1.2.1 Query Class -The purpose of a query class is to execute with supported QueryEngine to fetch data from data storage. - -QueryEngine is an implementation of `IQueryEngine` to execute queries against a supported data storage to return a collection of query results (ie. of type IQueryResult). - -As explained above, You can configure a query in `Parent` or `Child` (nested) mode in nested hierarchies. - -i. Parent Query - -To define a `parent` or `root` query which is usually configured at level 1 to query the root entity, derive from `aseRootQuery` -* `TQueryParameter` is basically the class that holds the `inputs` required by the root query for execution. It is an implementation of `IQueryParameter` type. -* `TQueryResult` is the result that will be returned from executing the root query. It is an implementation of `IQueryResult` type. - -The query parameter needs to be resolved before executing the query with QueryEngine. - -In `parent` mode, the query parameter is resolved using the `IDataContext` parameter passed to data provider class. - - -> See example `CustomerQuery` implemented to be configured and run in parent mode below. -> ``` ->internal class CustomerQuery : BaseRootQuery -> { -> public override void ResolveRootQueryParameter(IDataContext context) -> { -> // Executes as Parent or Level 1 query. -> // The query parameter is resolved using IDataContext parameter of data provider class. -> -> var customer = (CustomerContext)context; -> QueryParameter = new CustomerParameter -> { -> CustomerId = customer.CustomerId -> }; -> } -> } ->``` - -ii. Child Query - -To define a `child` or `dependant` query which is usually configured as child at level below the root query to query, derive from `BaseChildQuery` -* `TQueryParameter` is basically the class that holds the `inputs` required by the child query for execution. It is an implementation of `IQueryParameter` type. -* `TQueryResult` is the result that will be returned by executing the child query. It is an implementation of `IQueryResult` type. - -Similar to Root query, the query parameter of child query needs to be resolved before executing with QueryEngine. - -In `child` mode, the query parameter is resolved using the `query result` of the `parent` query. You can have a maximum of `5` levels of query nestings. - -> See example `CustomerCommunicationQuery` implemented to be configured and run as child or nested query to customer query below. Please see `CustomerSchema` definition above for parent/child configuration setup. ->``` -> internal class CustomerCommunicationQuery : BaseChildQuery -> { -> public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) -> { -> // Execute as child to customer query. -> // The result from parent customer query is used to resolve the query parameter of the nested communication query. -> -> var customer = (CustomerResult)parentQueryResult; -> QueryParameter = new CustomerParameter -> { -> CustomerId = customer.Id -> }; -> } -> } ->``` - -#### Query Engines - -`Please Note:` The above query implementation examples are with respect to parent/child configuration. The actual storage specific query definition should vary with specific implementation of the QueryEngine. -> Please see supported Query engine implementations below. -- `Schemio.SQL` - provides the implementation of IQueryEngine to execute SQL queries. Uses `Dapper` for SQL data acess. -- `Schemio.EntityFramework` - provides implementation of IQueryEngine to execute `Entity Framework` queries. - -`Query using Schemio.SQL` -The SQL query needs to implement `BaseSQLRootQuery` or `BaseSQLChildQuery` based on parent or child implementation. -And, requires implementing `public abstract CommandDefinition GetCommandDefinition()` method to return `command definition` for query to be executed with `Dapper` supported QueryEngine. - -See below example `CustomerQuery` implemented as Root SQL query ->``` -> internal class CustomerQuery : BaseSQLRootQuery -> { -> public override void ResolveRootQueryParameter(IDataContext context) -> { -> // Executes as root or level 1 query. -> var customer = (CustomerContext)context.Entity; -> QueryParameter = new CustomerParameter -> { -> CustomerId = (int)customer.CustomerId -> }; -> } -> -> public override IEnumerable Execute(IDbConnection conn) -> { -> return conn.Query(new CommandDefinition -> ( -> "select CustomerId as Id, " + -> "Customer_Name as Name," + -> "Customer_Code as Code " + -> $"from TCustomer where customerId={QueryParameter.CustomerId}" -> )); -> } -> } ->``` -> -See below example `CustomerOrderItemsQuery` implemented as child SQL query. ->``` ->internal class CustomerOrderItemsQuery : BaseSQLChildQuery -> { -> public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) -> { -> // Execute as child query to order query taking OrderResult to resolve query parameter. -> var ordersResult = (OrderResult)parentQueryResult; -> -> QueryParameter ??= new OrderItemParameter(); -> QueryParameter.OrderIds.Add(ordersResult.OrderId); -> } -> -> public override IEnumerable Execute(IDbConnection conn) -> { -> return conn.Query(new CommandDefinition -> ( -> "select OrderId, " + -> "OrderItemId as ItemId, " + -> "Name, " + -> "Cost " + -> $"from TOrderItem where OrderId in ({QueryParameter.ToCsv()})" -> )); -> } -> } ->``` - -`Query using Schemio.EntityFramework` -The SQL query needs to implement `BaseSQLRootQuery` or `BaseSQLChildQuery` based on parent or child implementation. -And, requires implementing `public abstract IEnumerable Run(DbContext dbContext)` method to implement query using `DbContext` using entity framework. - -See below example `CustomerQuery` implemented as Root Entity framework query ->``` -> internal class CustomerQuery : BaseSQLRootQuery -> { -> public override void ResolveRootQueryParameter(IDataContext context) -> { -> // Executes as root or level 1 query. -> var customer = (CustomerContext)context.Entity; -> QueryParameter = new CustomerParameter -> { -> CustomerId = (int)customer.CustomerId -> }; -> } -> -> public override IEnumerable Run(DbContext dbContext) -> { -> return dbContext.Set() -> .Where(c => c.Id == QueryParameter.CustomerId) -> .Select(c => new CustomerResult -> { -> Id = c.Id, -> Name = c.Name, -> Code = c.Code -> }); -> } -> } ->``` -> -See below example `CustomerOrderItemsQuery` implemented as child Entity framework query. ->``` ->internal class CustomerOrderItemsQuery : BaseSQLChildQuery -> { -> public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) -> { -> // Execute as child query to order query taking OrderResult to resolve query parameter. -> var ordersResult = (CustomerOrderResult)parentQueryResult; -> -> QueryParameter ??= new OrderItemParameter(); -> QueryParameter.OrderIds.Add(ordersResult.OrderId); -> } -> -> public override IEnumerable Run(DbContext dbContext) -> { -> return dbContext.Set() -> .Where(p => QueryParameter.OrderIds.Contains(p.Order.OrderId)) -> .Select(c => new OrderItemResult -> { -> ItemId = c.ItemId, -> Name = c.Name, -> Cost = c.Cost, -> OrderId = c.Order.OrderId -> }); -> ; -> } -> } ->``` - - -#### 2.2 Tranformer Class -The purpose of the transformer class is to transform the data fetched by the linked query class and mapp to the configured object graph of the entity. +``` +internal class CustomerConfiguration : EntityConfiguration +{ + public override IEnumerable> GetSchema() + { + return CreateSchema.For() + .Map(For.Paths("customer"), + customer => customer.Dependents + .Map(For.Paths("customer/communication")) + .Map(For.Paths("customer/orders"), + customerOrders => customerOrders.Dependents + .Map(For.Paths("customer/orders/order/items")))) + .End(); + } +} +``` +`CustomerConfiguration` shows `query/transformer` pairs mapped at three levels of nesting as per the `Customer` entity object graph. +`XPaths` are used to identify the schema paths in the object graph. Alternately, you could use your own representation to name the pairs or map the object graph. However, you would need to provide the `ISchemaPathmatcher` implementation to managing path matching. -To define a transformer class, you need to implement `BaseTransformer` -- where TEntity is Entity implementing `IEntity`. eg. Customer. -- where TQueryResult is Query Result from associated Query. It is an implementation of `IQueryResult` interface. +#### i. Query/Transformer Mapping +Every `Query` type in the Entity `Configuration` definition should have a complementing `Transformer` type. +You could map multiple `schema paths` to a given query/transformer pair. Currently, `XPath` and `JSONPath` schema languages are supported. -Note: It is `important` that the transformer should map data only to the `schema path(s)` pointing `section(s)` of the object graph. +Below is the snippet from `CustomerConfiguration` definition shows that `CustomerQuery` has associated `CustomerTransform` and the pair is mapped to the root `Customer` object. +``` + .Map(For.Paths("customer")) +``` -For the example query/transformer mapping ->``` -> .Map(For.Paths("customer")) ->``` +#### ii. Nested Query/Transformer Mappings +You could nest query/transformer pairs in a `parent/child` hierarchy. In which case the output of the parent query will serve as the input to the child query to resolve its query context. -The customer transformer maps data only to the `customer` xpath mapped object graph of customer class. -ie. - `customer/id`, `customer/customercode`, `customer/customername` +The query/transformer mappings can be `nested` to `5` levels down. + +Below is snippet to show nesting of `CommunicationQuery` as child to `CustomerQuery`. +``` +.Map(For.Paths("customer"), -- Parent + customer => customer.Dependents + .Map(For.Paths("customer/communication")) -- Child +``` -In below transformer example, `CustomerTransformer` is implemented to transform entity `Customer` with `CustomerResult` query result obtained from `CustomerQuery` execution. +Execution Flow +* In parent/child hierarchy, the first parent query executes first, followed by its immediate children. The execution flows in sequence to the last child query in order of its nesting. +* While executing the output of the parent is passed in to the child query to resolve query context and get it ready for execution. +* Transformers are also executed in the same sequence to map data to the Aggregate Entity. +* When a query path for nested query is included for execution, all the parent queries involved in that object graph get included for execution in order of its nesting. -> ->``` ->internal class CustomerTransform : BaseTransformer -> { -> public override Customer Transform(CustomerResult queryResult, Customer entity) -> { -> var customer = entity ?? new Customer(); -> customer.CustomerId = queryResult.Id; -> customer.CustomerName = queryResult.CustomerName; -> customer.CustomerCode = queryResult.CustomerCode; -> return customer; -> } -> } ->``` +Please see the execution sequence below for queries and transformers nested in `CustomerConfiguration` implemented above. -### DataProvider Setup -Data provider needs to setup with required dependencies. Provide implementations of below dependencies to construct the data provider. +image -- `ILogger>` - logger implementation. default no logger. -- `IEntitySchema` - mandatory entity schema definition for entity's object graph. -- `IQueryEngine` - implementation of query engine to execute queries (of type IQuery) with supported data storage. -- `ISchemaPathMatcher` - implementation of schema path matcher to use custom schema paths with entity schema definition. +#### iii. Query Class +`Query` - The purpose of a query class is to execute with supported QueryEngine to fetch data from data storage. -Example constructors: +`QueryEngine` is an implementation of `IQueryEngine` to execute queries with supported data storage to return query result (ie. Result instance of `IQueryResult`). -i. With `EntitySchema` and `QueryEngine` implementations. +Depending on the Nuget package(s) installed, you could implement `SQL` and `API` queries. +* `SQL` queries execute to get data from SQL database using `Dapper` or `EntityFramework` engines. +* `API` query executes web api to call an `endpoint` using `HTTPClient` supported engine to get data. +**Important**: You can combine heterogeneous queries in the Entity configuration to target different data stores. + +Example of SQL & API queries are below. +You need to override the `GetQuery(IDataContext context, IQueryResult parentQueryResult)` method to return query delegate (package specific implementation). +* `IDataContext` is the context parameter passed to DataProvider to get aggregated results (. Aggregated Entity). This parameter is always available for both parent and child queries. +* `IQueryResult` parameter is only available when query is configured in child mode, else will be null. + +##### `Schemio.SQL` - with `Dapper` Query implementation. +To create a SQL query you need to derive from `SQLQuery` where TQueryResult is `IQueryResult` implementation. + +1. Example Parent Query - CustomerQuery ``` - public DataProvider(IEntitySchema entitySchema, params IQueryEngine[] queryEngines) -``` -ii. With `Logger`, `EntitySchema`, `QueryEngine`, and `SchemaPathmMatcher` for custom schema paths mapping in entity schema definition. +public class CustomerQuery : SQLQuery +{ + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Executes as root or level 1 query. + var customer = (CustomerRequest)context.Request; + + return connection => connection.QueryFirstOrDefaultAsync(new CommandDefinition + ( + "select CustomerId as Id, " + + "Customer_Name as Name," + + "Customer_Code as Code " + + $"from TCustomer where customerId={customer.CustomerId}" + )); + } +} ``` - public DataProvider(ILogger> logger, IEntitySchema entitySchema, ISchemaPathMatcher schemaPathMatcher, params IQueryEngine[] queryEngines) - +2. Example Child Query - OrdersQuery +``` +internal class OrdersQuery : SQLQuery> +{ + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerResult)parentQueryResult; + + return async connection => + { + var items = await connection.QueryAsync(new CommandDefinition + ( + "select OrderId, " + + "OrderNo, " + + "OrderDate " + + "from TOrder " + + $"where customerId={customer.Id}" + )); + + return new CollectionResult(items); + }; + } +} ``` -#### Schemio.SQL -Construct DataProvider using `Schemio.SQL.QueryEngine` query engine. +##### `Schemio.EntityFramework` - with `EntityFramework` Query implementation +To create a SQL query you need to derive from `SQLQuery` where TQueryResult is `IQueryResult` implementation. + +1. Example Parent Query - CustomerQuery +``` +public class CustomerQuery : SQLQuery +{ + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Executes as root or level 1 query. parentQueryResult will be null. + var customer = (CustomerRequest)context.Request; + + return async dbContext => + { + var result = await dbContext.Set() + .Where(c => c.Id == customer.CustomerId) + .Select(c => new CustomerResult + { + Id = c.Id, + Name = c.Name, + Code = c.Code + }) + .FirstOrDefaultAsync(); + + return result; + }; + } +} ``` -var provider = new DataProvider(new CustomerSchema(), new Schemio.SQL.QueryEngine(new SQLConfiguration())); +2. Example Child Query - OrdersQuery ``` + internal class OrdersQuery : SQLQuery> + { + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerResult)parentQueryResult; + + return async dbContext => + { + var items = await dbContext.Set() + .Where(p => p.Customer.Id == customer.Id) + .Select(c => new OrderResult + { + CustomerId = c.CustomerId, + OrderId = c.OrderId, + Date = c.Date, + OrderNo = c.OrderNo + }) + .ToListAsync(); + + return new CollectionResult(items); + }; + } + } +``` +##### `Schemio.Api` - with `HttpClient` Query implementation +To create a Web API query you need to derive from `WebQuery` where TQueryResult is `IQueryResult` implementation. -#### Schemio.EntityFramework -Construct DataProvider using `Schemio.EntityFramework.QueryEngine` query engine. +`Important`: If you need to get response headers in the result then TQueryResult should derive from `WebHeaderResult` class. +1. Example Parent Query - CustomerQuery ``` -var provider = new DataProvider(new CustomerSchema(), Schemio.EntityFramework.QueryEngine()); +public class CustomerWebQuery : WebQuery +{ + public CustomerWebQuery() : base(Endpoints.BaseAddress) + { + } + + protected override Func GetQuery(IDataContext context, IQueryResult parentApiResult) + { + // Executes as root or level 1 api. + var customerRequest = (CustomerRequest)context.Request; + + return () => new Uri(string.Format(Endpoints.BaseAddress + Endpoints.Customer, customerRequest.CustomerId), UriKind.Absolute); + } + + /// + /// Override to pass outgoing request headers. + /// + /// + protected override IDictionary GetRequestHeaders() + { + return new Dictionary + { + { "x-meta-branch-code", "London" } + }; + } + + /// + /// Override to subscribe for given Response headers to be added to TQueryResult. + /// For receiving response headers, You need to implement the TQueryResult type from `WebHeaderResult` class instead of IQueryResult. + /// + /// + protected override IEnumerable GetResponseHeaders() + { + return new[] { "x-meta-branch-code" }; + } +} +``` +Note: CustomerResult is above query implements from `WebHeaderResult` class to support response headers. +``` +public class CustomerResult : WebHeaderResult +{ + public int Id { get; set; } + public string Code { get; set; } + public string Name { get; set; } +} ``` -### Using IOC for registrations +2. Example Child Query - OrdersQuery +``` +internal class OrdersQuery : WebQuery> +{ + public OrdersQuery() : base(Endpoints.BaseAddress) + { + } + protected override Func GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer api. + var customer = (CustomerResult)parentQueryResult; + + return ()=> new Uri(string.Format($"v2/customers/{customer.Id}/orders); + } +} +``` -With ServiceCollection, you should call the `services.UseSchemio()` method for IoC registration. +#### iv. Transformer Class +The purpose of the transformer class is to transform the data fetched by the linked query class and map to the configured object graph of the entity. -To configure Data provider with SQL Query engine, use fluent registration apis as shown below - - ``` - services.UseSchemio(With.Schema(c => new CustomerSchema()) - .AddEngine(c => new QueryEngine(new SQLConfiguration { ConnectionSettings = new ConnectionSettings { - Providername = "System.Data.SqlClient", - ConnectionString ="Data Source=Powerstation; Initial Catalog=Customer; Integrated Security=SSPI;" - }})) - .LogWith(c => new Logger>(c.GetService()))); +To define a transformer class, you need to implement `BaseTransformer` +- where TEntity is Aggregate Entity implementing `IEntity`. eg. Customer. +- where TQueryResult is Query Result from associated Query. It is an implementation of `IQueryResult` interface. + +Example transformer - Customer Transformer +``` +internal class CustomerTransform : BaseTransformer +{ + public override Customer Transform(CustomerResult queryResult, Customer entity) + { + var customer = entity ?? new Customer(); + customer.CustomerId = queryResult.Id; + customer.CustomerName = queryResult.CustomerName; + customer.CustomerCode = queryResult.CustomerCode; + + if (queryResult is WebHeaderResult webHeaderResult) + if (webHeaderResult.Headers.TryGetValue("x-meta-branch-code", out var branch)) + customer.Branch = branch; + + return customer; + } +} ``` -To configure Data provider with Entity Framework Query engine, use fluent registration apis shown as below - +**Note**: It is `important` that the transformer should map data only to the `schema path(s)` pointing `section(s)` of the object graph. + +For the example query/transformer mapping +``` +.Map(For.Paths("customer/communication")) +``` +The Communication transformer should map data only to the `customer/communication` xpath mapped object graph of customer class. + +### Step 3. DataProvider Setup +Data provider needs to be setup with required dependencies. Provide implementations of below dependencies to construct the data provider. + +#### Container Registrations + +With ServiceCollection, you need to register the below dependencies. + +``` + // Register core services + services.AddTransient(typeof(IQueryBuilder<>), typeof(QueryBuilder<>)); + services.AddTransient(typeof(IEntityBuilder<>), typeof(EntityBuilder<>)); + services.AddTransient(typeof(IDataProvider<>), typeof(DataProvider<>)); + services.AddTransient(); + + // Register instance of ISchemaPathMatcher - Json, XPath or Custom. + services.AddTransient(c => new XPathMatcher()); + + // Enable logging + services.AddLogging(); + + //For Dapper SQL engine - Schemio.SQL + services.AddTransient(c => new QueryEngine(new SQLConfiguration { ConnectionSettings = new ConnectionSettings { + Providername = "System.Data.SqlClient", + ConnectionString ="Data Source=Powerstation; Initial Catalog=Customer; Integrated Security=SSPI;" + }}); + + // For entity framework engine - Schemio.EntityFramework + services.AddDbContextFactory(options => options.UseSqlServer(YourSqlConnection), ServiceLifetime.Scoped); + services.AddTransient(c => new QueryEngine(c.GetService>()); + + // For HTTPClient Engine for web APIs - Schemio.API + + // Enable HttpClient + services.AddHttpClient(); + services.AddTransient(); + + // Register each entity configuration. eg CustomerConfiguration + services.AddTransient, CustomerConfiguration>(); + ``` - services.AddDbContextFactory(options => options.UseSqlServer(YourSqlConnection), ServiceLifetime.Scoped); - services.AddLogging(); +`Please Note:` You can combine multiple query engines and implement supporting types of queries to execute on target data platforms. + + +#### Using Fluent interface for registrations + +i. Example registration: Schemio.SQL - services.UseSchemio(With.Schema(c => new CustomerSchema()) - .AddEngine(c => new QueryEngine(c.GetService>())) - .LogWith(c => new Logger>(c.GetService()))); +``` +// Enable DbProviderFactory. + DbProviderFactories.RegisterFactory(DbProviderName, SqliteFactory.Instance); + + var connectionString = $"DataSource={Environment.CurrentDirectory}//Customer.db;mode=readonly;cache=shared"; + + var configuration = new SQLConfiguration { ConnectionSettings = new ConnectionSettings { ConnectionString = connectionString, ProviderName = DbProviderName } }; + +// Enable logging + services.AddLogging(); + + services.UseSchemio() + .WithEngine(c => new QueryEngine(configuration)) + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); +``` + +ii. Example registration: Schemio.EntityFramework ``` + var connectionString = $"DataSource={Environment.CurrentDirectory}//Customer.db;mode=readonly;cache=shared"; + +// Enable DBContext Factory + services.AddDbContextFactory(options => + options.UseSqlite(connectionString)); + +// Enable logging + services.AddLogging(); + + services.UseSchemio() + .WithEngine(c => new QueryEngine(c.GetService>())) + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); + +``` +iii. Example registration: Schemio.API +``` + // Enable logging + services.AddLogging(); + + // Enable HttpClient + services.AddHttpClient(); + + services.UseSchemio() + .WithEngine() + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); + +``` +iv. Example registration: Multiple Engines +``` + // Enable logging + services.AddLogging(); + + // Enable HttpClient + services.AddHttpClient(); + + var connectionString = $"DataSource={Environment.CurrentDirectory}//Customer.db;mode=readonly;cache=shared"; + +// Enable DBContext Factory + services.AddDbContextFactory(options => + options.UseSqlite(connectionString)); + + services.UseSchemio() + .WithEngine() + .WithEngine(c => new QueryEngine(c.GetService>())) + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); +``` + +#### Use Data Provider + +##### i. Dependency Inject - IDataProvider -`Please Note:` You can combine multiple query engines and implement different types of queries to execute on different supported platforms. +To use Data provider, Inject `IDataProvider` where T is IEntity, using constructor & property injection method or explicitly Resolve using service provider ie. `IServiceProvider.GetService(typeof(IDataProvider))` -To use Data provider, Inject IDataProvider using constructor & property injection method or explicity Resolve using service provider ie. `IServiceProvider.GetService(typeof(IDataProvider<>))` +##### ii. Call DataProvider.GetData(IEntityRequest context) method. +You need to call the `GetData()` method with an instance of parameter class derived from `IEntityRequest` interface. -## Extend Schemio -### Custom Query Engine -To provide custom query engine and query implementations, you need to extend the base interfaces as depicted below -- IQueryEngine interface to implement the custom query engine to be used with schemio. +The `IEntityRequest` provides a `SchemaPaths` property, which is a list of schema paths to include for the given request to fetch aggregated data. +- When `no` paths are passed in the parameter then entire aggregated entity for all configured queries is returned. +- When list of schema paths are included in the request then the returned aggregated data entity only includes query results from included queries. + +When nested path for a nested query is included (eg. customer/orders/order/items) then all parent queries in the respective parent paths also get included for execution. + +Example - Control Flow + +image + + +## Extending Schemio + +You could extend Schemio by providing your own custom implementation of the query engine (`IQueryEngine`) and query (`IQuery`) to execute queries on custom target data platform. + +To do this, you need to extend the base interfaces as depicted below. +### i. IQueryEngine +Implement `IQueryEngine` interface to provide the custom query engine to be used with schemio. ``` public interface IQueryEngine - { - /// - /// Detrmines whether an instance of query can be executed with this engine. - /// - /// instance of IQuery. - /// Boolean; True when supported. - bool CanExecute(IQuery query); - - /// - /// Executes a list of queries returning a list of aggregated results. - /// - /// List of IQuery instances. - /// List of query results. Instances of IQueryResult. - IEnumerable Execute(IEnumerable queries); - } +{ + /// + /// Detrmines whether an instance of query can be executed with this engine. + /// + /// instance of IQuery. + /// Boolean; True when supported. + bool CanExecute(IQuery query); + + /// + /// Executes a given query returning query result. + /// + /// Custom instance of IQuery. + /// Task of IQueryResult. + Task Execute(IQuery> query); +} ``` Example Entity Framework implementation is below ``` public class QueryEngine : IQueryEngine where T : DbContext +{ + private readonly IDbContextFactory _dbContextFactory; + + public QueryEngine(IDbContextFactory _dbContextFactory) { - private readonly IDbContextFactory _dbContextFactory; + this._dbContextFactory = _dbContextFactory; + } + + public bool CanExecute(IQuery query) => query != null && query is ISQLQuery; - public QueryEngine(IDbContextFactory _dbContextFactory) + public Task Execute(IQuery query) + { + using (var dbcontext = _dbContextFactory.CreateDbContext()) { - this._dbContextFactory = _dbContextFactory; + var result = ((ISQLQuery)query).Run(dbcontext); + return result; } + } +} +``` +### ii. IQuery +With the Query Engine implementation, you also need to provide custom implementation of `IQuery` for executing the query with custom query engine. - public bool CanExecute(IQuery query) => query != null && query is ISQLQuery; +To do this, you need to extend `BaseQuery` where TQueryResult is `IQueryResult`. And, provide overrides for below methods. +- `bool IsContextResolved()` +Engine calls this method to confirm whether the query is ready for execution. Return true when query context is resolved. +- `void ResolveQuery(IDataContext context, IQueryResult parentQueryResult)` +This method is invoked by schemio to resolve the query context required for execution ith supporting query engine. `IQueryResult` parameter is only available when the custom query is configured in nested or child mode. - public IEnumerable Execute(IEnumerable queries) - { - var output = new List(); - using (var dbcontext = _dbContextFactory.CreateDbContext()) - { - foreach (var query in queries) - { - var results = ((ISQLQuery)query).Run(dbcontext); +Example - EntityFramework Supported query implementation is shown below. +``` + public abstract class SQLQuery + : BaseQuery, ISQLQuery + where TQueryResult : IQueryResult + { + private Func> QueryDelegate = null; + + public override bool IsContextResolved() => QueryDelegate != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + QueryDelegate = GetQuery(context, parentQueryResult); + } + + async Task ISQLQuery.Run(DbContext dbContext) + { + return await QueryDelegate(dbContext); + } + + /// + /// Get query delegate to return query result. + /// + /// + /// + /// + protected abstract Func> GetQuery(IDataContext context, IQueryResult parentQueryResult); + } +``` - if (results == null) - continue; +### iii. Custom Schema Paths - output.AddRange(results); - } +Additionally, You can use your own schema language instead of XPath/JSONPath to map aggregated entity's object graph, and register with schemio. - return output.ToArray(); - } - } - } -``` -- Provide base implementation supporting IQuery, IRootQuery & IChildQuery interfaces. -- You can implement the parent and child base class implementations to construct for queries to be executed with custom engine implementation above. +To do this you need to follow the below steps: +#### i. Use schema paths in Entity Configuration. -For Parent Query base implementation, see example below. -``` -public abstract class BaseSQLRootQuery - : BaseRootQuery, ISQLQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Get query delegate with implementation to return query result. - /// Delegate returns a collection from db. - /// - /// Func> - public abstract IEnumerable Run(DbContext dbContext); - } -``` -For Child Query implementation, see example below. +Provide entity schema definition with query/transformer pairs using custom schema language paths. + +Example - with Dummy schema mapping ``` -public abstract class BaseSQLChildQuery - : BaseChildQuery, ISQLQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Get query delegate with implementation to return query result. - /// Delegate returns a collection from db. - /// - /// Func> - public abstract IEnumerable Run(DbContext dbContext); - } +.Map(For.Paths("customer$orders")) ``` -### Custom Schema Language -You can provide your own schema language support for use in entity schema definition to map sections of object graph. +#### ii. ISchemaPathMatcher +Provide implementation of `ISchemaPathMatcher` interface and implement `IsMatch()` method to provide logic for matching custom paths. -To do this you need to follow the below steps -* Provide entity schema definition with query/transformer pairs using custom schema language paths -* Provide implementation of `ISchemaPathMatcher` interface and implement `IsMatch()` method to provide logic for matching custom paths. This matcher is used by query builder to pick queries for matched paths against the configured p in Entity schema definition. +`Important`: This matcher is used by query builder to filter queries based matched paths, to include only required queries for execution to optimize performance. ``` public interface ISchemaPathMatcher - { - bool IsMatch(string inputPath, ISchemaPaths configuredPaths); - } +{ + bool IsMatch(string inputPath, ISchemaPaths configuredPaths); +} ``` -Example implementation of XPath matcher is below. +Example implementation of XPathMatcher is below. ``` public class XPathMatcher : ISchemaPathMatcher { @@ -534,4 +612,3 @@ public class XPathMatcher : ISchemaPathMatcher } } ``` - diff --git a/src/Schemio.API/IWebQuery.cs b/src/Schemio.API/IWebQuery.cs new file mode 100644 index 0000000..81fc2d3 --- /dev/null +++ b/src/Schemio.API/IWebQuery.cs @@ -0,0 +1,12 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Schemio.Core; + +namespace Schemio.API +{ + public interface IWebQuery : IQuery + { + Task Run(IHttpClientFactory httpClientFactory, ILogger logger = null); + } +} \ No newline at end of file diff --git a/src/Schemio.API/QueryEngine.cs b/src/Schemio.API/QueryEngine.cs new file mode 100644 index 0000000..c6326b1 --- /dev/null +++ b/src/Schemio.API/QueryEngine.cs @@ -0,0 +1,32 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Schemio.Core; +using Schemio.Core.Helpers; + +namespace Schemio.API +{ + public class QueryEngine : IQueryEngine + { + private readonly ILogger logger; + private readonly IHttpClientFactory httpClientFactory; + + public QueryEngine(IHttpClientFactory httpClientFactory, ILogger logger = null) + { + this.httpClientFactory = httpClientFactory; + this.logger = logger; + + Constraints.NotNull(httpClientFactory); + } + + public bool CanExecute(IQuery query) => query != null && query is IWebQuery; + + public async Task Execute(IQuery query) + { + if (query == null || !(query is IWebQuery q)) + return null; + + return await q.Run(httpClientFactory, logger); + } + } +} \ No newline at end of file diff --git a/src/Schemio.API/Schemio.API.csproj b/src/Schemio.API/Schemio.API.csproj new file mode 100644 index 0000000..31f291e --- /dev/null +++ b/src/Schemio.API/Schemio.API.csproj @@ -0,0 +1,50 @@ + + + + net9.0 + true + enable + Schemio with EntityFramework + Code Shayk + Code Shayk + .Net Library to hydrate data entities by object graph using schema paths (supports XPath & JSONPath). Supports Web API using HttpClient. + Copyright (c) 2024 Code Shayk + https://github.com/CodeShayk/Schemio/wiki/i.-Home + ninja-icon-16.png + README.md + https://github.com/CodeShayk/Schemio + git + graphql json-schema xsd data-mapping query-engine data-mapper data-schema schema-mapping object-tree-query object-graph-query schema-mapper xsd-data-object object-graph-data entity-data entity-data-fetch hydrate-object object-hydration object-data object-fetch schemio-Api HttpCliet + LICENSE.md + True + True + snupkg + True + 2.0.0 + + + + True + + + + + True + \ + + + True + \ + + + True + \ + + + + + + + + + diff --git a/src/Schemio.API/WebHeaderResult.cs b/src/Schemio.API/WebHeaderResult.cs new file mode 100644 index 0000000..00f798b --- /dev/null +++ b/src/Schemio.API/WebHeaderResult.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Schemio.Core; + +namespace Schemio.API +{ + /// + /// Implement to return web query response with headers. + /// + public abstract class WebHeaderResult : IQueryResult + { + public IDictionary Headers { get; internal set; } + } +} \ No newline at end of file diff --git a/src/Schemio.API/WebQuery.cs b/src/Schemio.API/WebQuery.cs new file mode 100644 index 0000000..ad6b34b --- /dev/null +++ b/src/Schemio.API/WebQuery.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Schemio.Core; +using Schemio.Core.Helpers; + +namespace Schemio.API +{ + public abstract class WebQuery : BaseQuery, IWebQuery + where TQueryResult : IQueryResult + { + protected Uri BaseAddress; + + protected WebQuery() : this(string.Empty) + { + } + + protected WebQuery(string baseAddress) + { + if (!string.IsNullOrEmpty(baseAddress)) + BaseAddress = new Uri(baseAddress); + } + + private Func UriDelegate = null; + + public override bool IsContextResolved() => UriDelegate != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + UriDelegate = GetQuery(context, parentQueryResult); + } + + /// + /// Override to pass custom outgoing headers with the api request. + /// + /// + protected virtual IDictionary GetRequestHeaders() + { + return new Dictionary(); + } + + /// + /// Override to get custom incoming headers with the api response. + /// The headers collection will be present on `WebHeaderResult.Headers` when api response includes any of the headers defined in this method. + /// + /// + protected virtual IEnumerable GetResponseHeaders() + { return []; } + + /// + /// Implement to construct the api web query. + /// + /// Request Context. Always available. + /// Result from parent Query. Only available when configured as nested web query. Else will be null. + /// + protected abstract Func GetQuery(IDataContext context, IQueryResult parentApiResult = null); + + async Task IWebQuery.Run(IHttpClientFactory httpClientFactory, ILogger logger) + { + Constraints.NotNull(httpClientFactory); + + logger?.LogInformation($"Run api: {GetType().Name}"); + + var Uri = UriDelegate(); + + if (Uri == null) + return null; + + using (var client = httpClientFactory.CreateClient()) + { + logger?.LogInformation($"Executing web api on thread {Thread.CurrentThread.ManagedThreadId} (task {Task.CurrentId})"); + + try + { + HttpResponseMessage result; + + try + { + if (BaseAddress != null) + client.BaseAddress = BaseAddress; + + var requestHeaders = GetRequestHeaders(); + + if (requestHeaders != null && requestHeaders.Any()) + foreach (var header in requestHeaders) + client.DefaultRequestHeaders.Add(header.Key, header.Value); + + result = await client.GetAsync(Uri); + + var raw = result.Content.ReadAsStringAsync().Result; + + if (!string.IsNullOrWhiteSpace(raw)) + logger?.LogInformation($"Result.Content of executing web api: {Uri.AbsolutePath} is {raw}"); + + if (!result.IsSuccessStatusCode) + { + logger?.LogInformation($"Result of executing web api {Uri.AbsolutePath} is not success status code"); + return null; + } + + if (typeof(TQueryResult).UnderlyingSystemType != null && typeof(TQueryResult).UnderlyingSystemType.Name.Equals(typeof(CollectionResult<>).Name)) + { + var typeArgs = typeof(TQueryResult).GetGenericArguments(); + var arrType = typeArgs[0].MakeArrayType(); + var arrObject = raw.ToObject(arrType); + if (arrObject != null) + { + var resultType = typeof(CollectionResult<>); + var collectionType = resultType.MakeGenericType(typeArgs); + var collectionResult = (TQueryResult)Activator.CreateInstance(collectionType, arrObject); + + SetResponseHeaders(result, collectionResult); + + return collectionResult; + } + } + else + { + var obj = raw.ToObject(typeof(TQueryResult)); + if (obj != null) + { + var resObj = (TQueryResult)obj; + SetResponseHeaders(result, resObj); + return resObj; + } + } + } + catch (TaskCanceledException ex) + { + logger?.LogWarning(ex, $"An error occurred while sending the request. Query URL: {Uri.AbsolutePath}"); + } + catch (HttpRequestException ex) + { + logger?.LogWarning(ex, $"An error occurred while sending the request. Query URL: {Uri.AbsolutePath}"); + } + } + catch (AggregateException ex) + { + logger?.LogInformation($"Web api {GetType().Name} failed"); + foreach (var e in ex.InnerExceptions) + logger?.LogError(e, ""); + } + } + + return null; + } + + private void SetResponseHeaders(HttpResponseMessage response, TQueryResult? result) + { + if (response.Headers == null || result == null) + return; + + var headers = GetResponseHeaders(); + + if (headers == null || !headers.Any()) + return; + + if (!(result is WebHeaderResult webResult)) + throw new InvalidOperationException($"{typeof(TQueryResult).Name} should implement from WebHeaderResult for response Headers"); + + foreach (var header in headers) + { + if (!response.Headers.Any(r => r.Key == header)) + continue; + + var responseHeader = response.Headers.First(r => r.Key == header); + + var value = responseHeader.Value != null && responseHeader.Value.Any() + ? responseHeader.Value.ElementAt(0) + : string.Empty; + + webResult.Headers ??= new Dictionary(); + + webResult.Headers.Add(responseHeader.Key, value); + } + } + } +} \ No newline at end of file diff --git a/src/Schemio/AssemblyInfo.cs b/src/Schemio.Core/AssemblyInfo.cs similarity index 88% rename from src/Schemio/AssemblyInfo.cs rename to src/Schemio.Core/AssemblyInfo.cs index 4005317..462744b 100644 --- a/src/Schemio/AssemblyInfo.cs +++ b/src/Schemio.Core/AssemblyInfo.cs @@ -11,8 +11,9 @@ // attribute to true on that type. [assembly: ComVisible(false)] -[assembly: InternalsVisibleTo("Schemio.Tests")] +[assembly: InternalsVisibleTo("Schemio.Core.Tests")] [assembly: InternalsVisibleTo("Schemio.SQL.Tests")] +[assembly: InternalsVisibleTo("Schemio.EntityFramework.Tests")] // The following GUID is for the ID of the typelib if this project is exposed to COM. diff --git a/src/Schemio.Core/BaseQuery.cs b/src/Schemio.Core/BaseQuery.cs new file mode 100644 index 0000000..11fb055 --- /dev/null +++ b/src/Schemio.Core/BaseQuery.cs @@ -0,0 +1,46 @@ +namespace Schemio.Core +{ + /// + /// Implement this base class to create a data provider query. + /// + /// + public abstract class BaseQuery : IQuery + where TQueryResult : IQueryResult + { + /// + /// Children queries dependent on this query. + /// + public List Children { get; set; } + + /// + /// Get the result type for the query + /// + public Type ResultType + { + get { return typeof(TQueryResult); } + } + + /// + /// Implement to determine whether the query context is resolved and ready to execute with supported engine. + /// + /// Boolean + public abstract bool IsContextResolved(); + + /// + /// Implement to resolve query context for execution with supporting query engine. + /// + /// data context passed to the data provider. + /// query result from parent query (when configured as nested query). Can be null. + public abstract void ResolveQuery(IDataContext context, IQueryResult parentQueryResult); + + /// + /// Run query with supporting IQueryEngine instance. + /// + /// IQueryEngine Instance + /// Instance of IQueryResult + public Task Run(IQueryEngine engine) + { + return engine.Execute(this); + } + } +} \ No newline at end of file diff --git a/src/Schemio/BaseTransformer.cs b/src/Schemio.Core/BaseTransformer.cs similarity index 98% rename from src/Schemio/BaseTransformer.cs rename to src/Schemio.Core/BaseTransformer.cs index 4bb2176..3669abf 100644 --- a/src/Schemio/BaseTransformer.cs +++ b/src/Schemio.Core/BaseTransformer.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public abstract class BaseTransformer : ITransformer, ITransformerContext, ITransformerQueryResult diff --git a/src/Schemio/CacheResultAttribute.cs b/src/Schemio.Core/CacheResultAttribute.cs similarity index 72% rename from src/Schemio/CacheResultAttribute.cs rename to src/Schemio.Core/CacheResultAttribute.cs index 0b32eb8..d989780 100644 --- a/src/Schemio/CacheResultAttribute.cs +++ b/src/Schemio.Core/CacheResultAttribute.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public class CacheResultAttribute : Attribute { } diff --git a/src/Schemio/ChildrenQueries.cs b/src/Schemio.Core/ChildrenQueries.cs similarity index 87% rename from src/Schemio/ChildrenQueries.cs rename to src/Schemio.Core/ChildrenQueries.cs index 42b30d9..9906175 100644 --- a/src/Schemio/ChildrenQueries.cs +++ b/src/Schemio.Core/ChildrenQueries.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public class ChildrenQueries { diff --git a/src/Schemio.Core/CollectionResult.cs b/src/Schemio.Core/CollectionResult.cs new file mode 100644 index 0000000..d5600b1 --- /dev/null +++ b/src/Schemio.Core/CollectionResult.cs @@ -0,0 +1,13 @@ +namespace Schemio.Core +{ + public class CollectionResult : List, IQueryResult + { + public CollectionResult(IEnumerable list) : base(list) + { + } + + public CollectionResult() + { + } + } +} \ No newline at end of file diff --git a/src/Schemio/CreateSchema.cs b/src/Schemio.Core/CreateSchema.cs similarity index 96% rename from src/Schemio/CreateSchema.cs rename to src/Schemio.Core/CreateSchema.cs index 309e324..8b0b7bc 100644 --- a/src/Schemio/CreateSchema.cs +++ b/src/Schemio.Core/CreateSchema.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { #region Helpers @@ -76,7 +76,7 @@ public IMapOrComplete Map(ISchemaPa public Mappings GetMappings => this; - public IEnumerable> Create() => this; + public IEnumerable> End() => this; } public class Mapping : @@ -125,7 +125,7 @@ public interface IMapOrComplete : IMap> Create(); + IEnumerable> End(); } public interface IWithDependents diff --git a/src/Schemio/DataContext.cs b/src/Schemio.Core/DataContext.cs similarity index 55% rename from src/Schemio/DataContext.cs rename to src/Schemio.Core/DataContext.cs index edb4020..2e528fe 100644 --- a/src/Schemio/DataContext.cs +++ b/src/Schemio.Core/DataContext.cs @@ -1,14 +1,14 @@ -namespace Schemio +namespace Schemio.Core { internal class DataContext : IDataContext { - public DataContext(IEntityContext entityContext) + public DataContext(IEntityRequest request) { - Entity = entityContext; + Request = request; Cache = new Dictionary(); } public Dictionary Cache { get; set; } - public IEntityContext Entity { get; set; } + public IEntityRequest Request { get; set; } } } \ No newline at end of file diff --git a/src/Schemio/BaseEntitySchema.cs b/src/Schemio.Core/EntityConfiguration.cs similarity index 77% rename from src/Schemio/BaseEntitySchema.cs rename to src/Schemio.Core/EntityConfiguration.cs index d4b1b42..4e2c3d0 100644 --- a/src/Schemio/BaseEntitySchema.cs +++ b/src/Schemio.Core/EntityConfiguration.cs @@ -1,14 +1,14 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement to configure schema path mappings for an Entity. /// /// Entity type - public abstract class BaseEntitySchema : IEntitySchema where TEntity : IEntity + public abstract class EntityConfiguration : IEntityConfiguration where TEntity : IEntity { public IEnumerable> Mappings { get; } - public BaseEntitySchema() + public EntityConfiguration() { Mappings = GetSchema(); } diff --git a/src/Schemio/Helpers/ArrayUtil.cs b/src/Schemio.Core/Helpers/ArrayUtil.cs similarity index 95% rename from src/Schemio/Helpers/ArrayUtil.cs rename to src/Schemio.Core/Helpers/ArrayUtil.cs index 01d6bd8..b9a7ae6 100644 --- a/src/Schemio/Helpers/ArrayUtil.cs +++ b/src/Schemio.Core/Helpers/ArrayUtil.cs @@ -1,4 +1,4 @@ -namespace Schemio.Helpers +namespace Schemio.Core.Helpers { public static class ArrayUtil { diff --git a/src/Schemio.Core/Helpers/Constraints.cs b/src/Schemio.Core/Helpers/Constraints.cs new file mode 100644 index 0000000..a6351a1 --- /dev/null +++ b/src/Schemio.Core/Helpers/Constraints.cs @@ -0,0 +1,11 @@ +namespace Schemio.Core.Helpers +{ + public static class Constraints + { + public static void NotNull(this T value) + { + if (value == null) + throw new ArgumentNullException(typeof(T).Name); + } + } +} \ No newline at end of file diff --git a/src/Schemio/Helpers/EnumerableExtentions.cs b/src/Schemio.Core/Helpers/EnumerableExtentions.cs similarity index 96% rename from src/Schemio/Helpers/EnumerableExtentions.cs rename to src/Schemio.Core/Helpers/EnumerableExtentions.cs index 5e7f7b8..d455d10 100644 --- a/src/Schemio/Helpers/EnumerableExtentions.cs +++ b/src/Schemio.Core/Helpers/EnumerableExtentions.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Schemio.Helpers +namespace Schemio.Core.Helpers { public static class EnumerableExtentions { diff --git a/src/Schemio.Core/Helpers/JsonExtensions.cs b/src/Schemio.Core/Helpers/JsonExtensions.cs new file mode 100644 index 0000000..d841e5e --- /dev/null +++ b/src/Schemio.Core/Helpers/JsonExtensions.cs @@ -0,0 +1,11 @@ +using System.Text.Json; + +namespace Schemio.Core.Helpers +{ + public static class JsonExtensions + { + public static string? ToJson(this object value) => value != null ? JsonSerializer.Serialize(value) : null; + + public static object? ToObject(this string value, Type type) => !string.IsNullOrEmpty(value) ? JsonSerializer.Deserialize(value, type) : null; + } +} \ No newline at end of file diff --git a/src/Schemio/Helpers/StringExtensions.cs b/src/Schemio.Core/Helpers/StringExtensions.cs similarity index 83% rename from src/Schemio/Helpers/StringExtensions.cs rename to src/Schemio.Core/Helpers/StringExtensions.cs index cce4dbf..2cefe2d 100644 --- a/src/Schemio/Helpers/StringExtensions.cs +++ b/src/Schemio.Core/Helpers/StringExtensions.cs @@ -1,4 +1,4 @@ -namespace Schemio.Helpers +namespace Schemio.Core.Helpers { public static class StringExtensions { diff --git a/src/Schemio/Helpers/Xml/XDocumentExts.cs b/src/Schemio.Core/Helpers/Xml/XDocumentExts.cs similarity index 96% rename from src/Schemio/Helpers/Xml/XDocumentExts.cs rename to src/Schemio.Core/Helpers/Xml/XDocumentExts.cs index ad04e7d..936e178 100644 --- a/src/Schemio/Helpers/Xml/XDocumentExts.cs +++ b/src/Schemio.Core/Helpers/Xml/XDocumentExts.cs @@ -1,6 +1,6 @@ using System.Xml.Linq; -namespace Schemio.Helpers.Xml +namespace Schemio.Core.Helpers.Xml { public static class XDocumentExts { diff --git a/src/Schemio/Helpers/Xml/XmlHelper.cs b/src/Schemio.Core/Helpers/Xml/XmlHelper.cs similarity index 94% rename from src/Schemio/Helpers/Xml/XmlHelper.cs rename to src/Schemio.Core/Helpers/Xml/XmlHelper.cs index 1f1f9a4..b38db24 100644 --- a/src/Schemio/Helpers/Xml/XmlHelper.cs +++ b/src/Schemio.Core/Helpers/Xml/XmlHelper.cs @@ -2,7 +2,7 @@ using System.Xml; using System.Xml.Serialization; -namespace Schemio.Helpers.Xml +namespace Schemio.Core.Helpers.Xml { public static class XmlHelper { diff --git a/src/Schemio/Helpers/Xml/XmlSanitizer.cs b/src/Schemio.Core/Helpers/Xml/XmlSanitizer.cs similarity index 96% rename from src/Schemio/Helpers/Xml/XmlSanitizer.cs rename to src/Schemio.Core/Helpers/Xml/XmlSanitizer.cs index 4b16ef1..24f3b1a 100644 --- a/src/Schemio/Helpers/Xml/XmlSanitizer.cs +++ b/src/Schemio.Core/Helpers/Xml/XmlSanitizer.cs @@ -2,7 +2,7 @@ using System.Text.RegularExpressions; using System.Xml; -namespace Schemio.Helpers.Xml +namespace Schemio.Core.Helpers.Xml { public static class XmlSanitizer { diff --git a/src/Schemio/IDataContext.cs b/src/Schemio.Core/IDataContext.cs similarity index 52% rename from src/Schemio/IDataContext.cs rename to src/Schemio.Core/IDataContext.cs index d86f11f..e53967c 100644 --- a/src/Schemio/IDataContext.cs +++ b/src/Schemio.Core/IDataContext.cs @@ -1,7 +1,7 @@ -namespace Schemio +namespace Schemio.Core { public interface IDataContext : IEntityContextCache { - IEntityContext Entity { get; } + IEntityRequest Request { get; } } } \ No newline at end of file diff --git a/src/Schemio/IDataProvider.cs b/src/Schemio.Core/IDataProvider.cs similarity index 53% rename from src/Schemio/IDataProvider.cs rename to src/Schemio.Core/IDataProvider.cs index cb6c7ba..ef3899b 100644 --- a/src/Schemio/IDataProvider.cs +++ b/src/Schemio.Core/IDataProvider.cs @@ -1,7 +1,7 @@ -namespace Schemio +namespace Schemio.Core { public interface IDataProvider where TEntity : IEntity { - TEntity GetData(IEntityContext context); + TEntity GetData(IEntityRequest request); } } \ No newline at end of file diff --git a/src/Schemio/IEntity.cs b/src/Schemio.Core/IEntity.cs similarity index 89% rename from src/Schemio/IEntity.cs rename to src/Schemio.Core/IEntity.cs index 6319a64..efe74ed 100644 --- a/src/Schemio/IEntity.cs +++ b/src/Schemio.Core/IEntity.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement Entity required to be hydrated (using query/transformer). diff --git a/src/Schemio.Core/IEntityBuilder.cs b/src/Schemio.Core/IEntityBuilder.cs new file mode 100644 index 0000000..d375e1b --- /dev/null +++ b/src/Schemio.Core/IEntityBuilder.cs @@ -0,0 +1,7 @@ +namespace Schemio.Core +{ + public interface IEntityBuilder where TEntity : IEntity + { + TEntity Build(IDataContext context, IList results); + } +} \ No newline at end of file diff --git a/src/Schemio/IEntitySchema.cs b/src/Schemio.Core/IEntityConfiguration.cs similarity index 76% rename from src/Schemio/IEntitySchema.cs rename to src/Schemio.Core/IEntityConfiguration.cs index aa96154..a7e34c1 100644 --- a/src/Schemio/IEntitySchema.cs +++ b/src/Schemio.Core/IEntityConfiguration.cs @@ -1,10 +1,10 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement to configure schema path mappings for an Entity. /// /// Entity type - public interface IEntitySchema where TEntity : IEntity + public interface IEntityConfiguration where TEntity : IEntity { /// /// Entity schema mappings. diff --git a/src/Schemio/IEntityContextCache.cs b/src/Schemio.Core/IEntityContextCache.cs similarity index 91% rename from src/Schemio/IEntityContextCache.cs rename to src/Schemio.Core/IEntityContextCache.cs index 60a5955..b4bbd9a 100644 --- a/src/Schemio/IEntityContextCache.cs +++ b/src/Schemio.Core/IEntityContextCache.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IEntityContextCache { diff --git a/src/Schemio.Core/IEntityContextValidator.cs b/src/Schemio.Core/IEntityContextValidator.cs new file mode 100644 index 0000000..62f3d29 --- /dev/null +++ b/src/Schemio.Core/IEntityContextValidator.cs @@ -0,0 +1,7 @@ +namespace Schemio.Core +{ + public interface IEntityContextValidator + { + public void Validate(IEntityRequest context); + } +} \ No newline at end of file diff --git a/src/Schemio/IEntityContext.cs b/src/Schemio.Core/IEntityRequest.cs similarity index 73% rename from src/Schemio/IEntityContext.cs rename to src/Schemio.Core/IEntityRequest.cs index f15c465..f26d15f 100644 --- a/src/Schemio/IEntityContext.cs +++ b/src/Schemio.Core/IEntityRequest.cs @@ -1,6 +1,6 @@ -namespace Schemio +namespace Schemio.Core { - public interface IEntityContext + public interface IEntityRequest { /// /// Entity schema paths for data retrieval. diff --git a/src/Schemio/IPolymorphicResult.cs b/src/Schemio.Core/IPolymorphicResult.cs similarity index 75% rename from src/Schemio/IPolymorphicResult.cs rename to src/Schemio.Core/IPolymorphicResult.cs index 6e0181c..7d505a5 100644 --- a/src/Schemio/IPolymorphicResult.cs +++ b/src/Schemio.Core/IPolymorphicResult.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IPolymorphicResult : IQueryResult { diff --git a/src/Schemio/IQuery.cs b/src/Schemio.Core/IQuery.cs similarity index 59% rename from src/Schemio/IQuery.cs rename to src/Schemio.Core/IQuery.cs index 5da4225..9fe0b58 100644 --- a/src/Schemio/IQuery.cs +++ b/src/Schemio.Core/IQuery.cs @@ -1,14 +1,16 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement IQuery to fetch data using API or database. /// - public interface IQuery + public interface IQuery : IQueryRunner { List Children { get; set; } Type ResultType { get; } bool IsContextResolved(); + + void ResolveQuery(IDataContext context, IQueryResult parentQueryResult = null); } } \ No newline at end of file diff --git a/src/Schemio/IQueryBuilder.cs b/src/Schemio.Core/IQueryBuilder.cs similarity index 81% rename from src/Schemio/IQueryBuilder.cs rename to src/Schemio.Core/IQueryBuilder.cs index 61223cc..8d7686d 100644 --- a/src/Schemio/IQueryBuilder.cs +++ b/src/Schemio.Core/IQueryBuilder.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryBuilder { diff --git a/src/Schemio/IQueryEngine.cs b/src/Schemio.Core/IQueryEngine.cs similarity index 66% rename from src/Schemio/IQueryEngine.cs rename to src/Schemio.Core/IQueryEngine.cs index a2e2535..acfb7cb 100644 --- a/src/Schemio/IQueryEngine.cs +++ b/src/Schemio.Core/IQueryEngine.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryEngine { @@ -12,8 +12,8 @@ public interface IQueryEngine /// /// Executes a list of queries returning a list of aggregated results. /// - /// List of IQuery instances. - /// List of query results. Instances of IQueryResult. - IEnumerable Execute(IEnumerable queries); + /// Instance of IQuery. + /// Instance of IQueryResult. + Task Execute(IQuery query); } } \ No newline at end of file diff --git a/src/Schemio/IQueryExecutor.cs b/src/Schemio.Core/IQueryExecutor.cs similarity index 84% rename from src/Schemio/IQueryExecutor.cs rename to src/Schemio.Core/IQueryExecutor.cs index 3085279..13ee57a 100644 --- a/src/Schemio/IQueryExecutor.cs +++ b/src/Schemio.Core/IQueryExecutor.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryExecutor { diff --git a/src/Schemio/IQueryList.cs b/src/Schemio.Core/IQueryList.cs similarity index 94% rename from src/Schemio/IQueryList.cs rename to src/Schemio.Core/IQueryList.cs index 82794d8..31c9244 100644 --- a/src/Schemio/IQueryList.cs +++ b/src/Schemio.Core/IQueryList.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryList { diff --git a/src/Schemio/IQueryParameter.cs b/src/Schemio.Core/IQueryParameter.cs similarity index 69% rename from src/Schemio/IQueryParameter.cs rename to src/Schemio.Core/IQueryParameter.cs index 68c0ed0..eda20dd 100644 --- a/src/Schemio/IQueryParameter.cs +++ b/src/Schemio.Core/IQueryParameter.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryParameter { diff --git a/src/Schemio/IQueryResult.cs b/src/Schemio.Core/IQueryResult.cs similarity index 68% rename from src/Schemio/IQueryResult.cs rename to src/Schemio.Core/IQueryResult.cs index fbfa3a6..41f1ba4 100644 --- a/src/Schemio/IQueryResult.cs +++ b/src/Schemio.Core/IQueryResult.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public interface IQueryResult { diff --git a/src/Schemio.Core/IQueryRunner.cs b/src/Schemio.Core/IQueryRunner.cs new file mode 100644 index 0000000..13abbbb --- /dev/null +++ b/src/Schemio.Core/IQueryRunner.cs @@ -0,0 +1,7 @@ +namespace Schemio.Core +{ + public interface IQueryRunner + { + Task Run(IQueryEngine engine); + } +} \ No newline at end of file diff --git a/src/Schemio.Core/ISchemaPathMatcher.cs b/src/Schemio.Core/ISchemaPathMatcher.cs new file mode 100644 index 0000000..51e71de --- /dev/null +++ b/src/Schemio.Core/ISchemaPathMatcher.cs @@ -0,0 +1,13 @@ +namespace Schemio.Core +{ + public interface ISchemaPathMatcher + { + /// + /// Determines whether there is a match for given input path vs configured paths for entity's object graph. + /// + /// Input path from IEntityRequest.SchemaPaths + /// Configured paths from EntityConfiguration + /// + bool IsMatch(string inputPath, ISchemaPaths configuredPaths); + } +} \ No newline at end of file diff --git a/src/Schemio/ITransformer.cs b/src/Schemio.Core/ITransformer.cs similarity index 95% rename from src/Schemio/ITransformer.cs rename to src/Schemio.Core/ITransformer.cs index 9bbbb0b..76ae8ef 100644 --- a/src/Schemio/ITransformer.cs +++ b/src/Schemio.Core/ITransformer.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement transformer to map data from supported query result to entity in context. diff --git a/src/Schemio/ITransformerContext.cs b/src/Schemio.Core/ITransformerContext.cs similarity index 93% rename from src/Schemio/ITransformerContext.cs rename to src/Schemio.Core/ITransformerContext.cs index dee4f46..df6a1a9 100644 --- a/src/Schemio/ITransformerContext.cs +++ b/src/Schemio.Core/ITransformerContext.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement to set transform with data context. diff --git a/src/Schemio/ITransformerQueryResult.cs b/src/Schemio.Core/ITransformerQueryResult.cs similarity index 92% rename from src/Schemio/ITransformerQueryResult.cs rename to src/Schemio.Core/ITransformerQueryResult.cs index c9758c5..893fce6 100644 --- a/src/Schemio/ITransformerQueryResult.cs +++ b/src/Schemio.Core/ITransformerQueryResult.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { /// /// Implement to get supported Query result. diff --git a/src/Schemio/Impl/DataProvider.cs b/src/Schemio.Core/Impl/DataProvider.cs similarity index 75% rename from src/Schemio/Impl/DataProvider.cs rename to src/Schemio.Core/Impl/DataProvider.cs index 9fe3c2a..484c758 100644 --- a/src/Schemio/Impl/DataProvider.cs +++ b/src/Schemio.Core/Impl/DataProvider.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; -using Schemio.PathMatchers; +using Schemio.Core.PathMatchers; -namespace Schemio.Impl +namespace Schemio.Core.Impl { public class DataProvider : IDataProvider where TEntity : IEntity, new() @@ -9,23 +9,23 @@ public class DataProvider : IDataProvider private readonly ILogger> logger; private readonly IQueryExecutor queryExecutor; private readonly IQueryBuilder queryBuilder; - private readonly ITransformExecutor transformExecutor; + private readonly IEntityBuilder entityBuilder; public DataProvider( - IEntitySchema entitySchema, + IEntityConfiguration entitySchema, params IQueryEngine[] queryEngines) : this(null, new QueryBuilder(entitySchema, new XPathMatcher()), - new QueryExecutor(queryEngines), new TransformExecutor(entitySchema)) + new QueryExecutor(queryEngines), new EntityBuilder(entitySchema)) { } public DataProvider( ILogger> logger, - IEntitySchema entitySchema, + IEntityConfiguration entitySchema, ISchemaPathMatcher schemaPathMatcher, params IQueryEngine[] queryEngines) : this(logger, new QueryBuilder(entitySchema, schemaPathMatcher), - new QueryExecutor(queryEngines), new TransformExecutor(entitySchema)) + new QueryExecutor(queryEngines), new EntityBuilder(entitySchema)) { } @@ -33,17 +33,17 @@ public DataProvider( ILogger> logger, IQueryBuilder queryBuilder, IQueryExecutor queryExecutor, - ITransformExecutor transformExecutor) + IEntityBuilder entityBuilder) { this.logger = logger; this.queryBuilder = queryBuilder; this.queryExecutor = queryExecutor; - this.transformExecutor = transformExecutor; + this.entityBuilder = entityBuilder; } - public TEntity GetData(IEntityContext entityContext) + public TEntity GetData(IEntityRequest request) { - var context = new DataContext(entityContext); + var context = new DataContext(request); return GetData(context); } @@ -52,6 +52,7 @@ internal TEntity GetData(IDataContext context) // Build queries for the data source based on the included xPaths var watch = System.Diagnostics.Stopwatch.StartNew(); var queries = queryBuilder.Build(context); + watch.Stop(); logger?.LogInformation("Query builder executed in " + watch.ElapsedMilliseconds + " ms"); @@ -63,7 +64,7 @@ internal TEntity GetData(IDataContext context) // Executes configured transformers to map query results to target entity watch = System.Diagnostics.Stopwatch.StartNew(); - var entity = transformExecutor.Execute(context, results); + var entity = entityBuilder.Build(context, results); watch.Stop(); logger?.LogInformation("Transform executor executed in " + watch.ElapsedMilliseconds + " ms"); diff --git a/src/Schemio/Impl/TransformExecutor.cs b/src/Schemio.Core/Impl/EntityBuilder.cs similarity index 71% rename from src/Schemio/Impl/TransformExecutor.cs rename to src/Schemio.Core/Impl/EntityBuilder.cs index ed55eb0..48e6427 100644 --- a/src/Schemio/Impl/TransformExecutor.cs +++ b/src/Schemio.Core/Impl/EntityBuilder.cs @@ -1,10 +1,10 @@ -namespace Schemio.Impl +namespace Schemio.Core.Impl { - public class TransformExecutor : ITransformExecutor where T : IEntity, new() + public class EntityBuilder : IEntityBuilder where T : IEntity, new() { - private readonly IEntitySchema entitySchema; + private readonly IEntityConfiguration entitySchema; - public TransformExecutor(IEntitySchema entitySchema) + public EntityBuilder(IEntityConfiguration entitySchema) { this.entitySchema = entitySchema; } @@ -15,7 +15,7 @@ public TransformExecutor(IEntitySchema entitySchema) /// Entity Context /// List of Query results /// - public T Execute(IDataContext context, IList queryResults) + public T Build(IDataContext context, IList queryResults) { var entity = new T { /*Version = entitySchemaMapping.Version*/ }; @@ -39,11 +39,16 @@ public T Execute(IDataContext context, IList queryResults) .ToList(); foreach (var queryResult in queryResults) - transformers.Where(transformer => (transformer as ITransformerQueryResult)?.SupportedQueryResult == queryResult.GetType()).ToList() + transformers.Where(transformer => IsMatch(((ITransformerQueryResult)transformer).SupportedQueryResult, queryResult.GetType())).ToList() .ForEach(supportedtransformer => supportedtransformer?.Transform(queryResult, entity)); } return entity; + + static bool IsMatch(Type transformer, Type queryResult) + { + return transformer == queryResult; + } } } } \ No newline at end of file diff --git a/src/Schemio/Impl/EventAggregator.cs b/src/Schemio.Core/Impl/EventPublisher.cs similarity index 82% rename from src/Schemio/Impl/EventAggregator.cs rename to src/Schemio.Core/Impl/EventPublisher.cs index 4628367..f135e55 100644 --- a/src/Schemio/Impl/EventAggregator.cs +++ b/src/Schemio.Core/Impl/EventPublisher.cs @@ -1,10 +1,10 @@ -namespace Schemio.Impl +namespace Schemio.Core.Impl { - public class EventAggregator + public class EventPublisher { private readonly ISubscriber subscriber; - public EventAggregator(ISubscriber subscriber) + public EventPublisher(ISubscriber subscriber) { this.subscriber = subscriber; } diff --git a/src/Schemio/Impl/EventSubscriber.cs b/src/Schemio.Core/Impl/EventSubscriber.cs similarity index 88% rename from src/Schemio/Impl/EventSubscriber.cs rename to src/Schemio.Core/Impl/EventSubscriber.cs index 33baf8e..34a2668 100644 --- a/src/Schemio/Impl/EventSubscriber.cs +++ b/src/Schemio.Core/Impl/EventSubscriber.cs @@ -1,4 +1,4 @@ -namespace Schemio.Impl +namespace Schemio.Core.Impl { public class EventSubscriber : ISubscriber { @@ -28,8 +28,8 @@ public void OnEventHandler(IDataContext context, ExecutorResultArgs args) if (unresolved.ParentQueryResultType != result.GetType()) continue; - foreach (var query in unresolved.Queries.Cast()) - query.ResolveChildQueryParameter(context, queryResult); + foreach (var query in unresolved.Queries) + query.ResolveQuery(context, queryResult); } } @@ -48,8 +48,8 @@ public void OnEventHandler(IDataContext context, ExecutorResultArgs args) !unresolved.ParentQueryResultType.IsAssignableFrom(result.GetType())) continue; - foreach (var query in unresolved.Queries.Cast()) - query.ResolveChildQueryParameter(context, queryResult); + foreach (var query in unresolved.Queries) + query.ResolveQuery(context, queryResult); } } } diff --git a/src/Schemio/Impl/QueryBuilder.cs b/src/Schemio.Core/Impl/QueryBuilder.cs similarity index 68% rename from src/Schemio/Impl/QueryBuilder.cs rename to src/Schemio.Core/Impl/QueryBuilder.cs index c79f3c2..2ed81c2 100644 --- a/src/Schemio/Impl/QueryBuilder.cs +++ b/src/Schemio.Core/Impl/QueryBuilder.cs @@ -1,11 +1,11 @@ -namespace Schemio.Impl +namespace Schemio.Core.Impl { public class QueryBuilder : IQueryBuilder where T : IEntity { - private readonly IEntitySchema entitySchema; + private readonly IEntityConfiguration entitySchema; private readonly ISchemaPathMatcher schemaPathMatcher; - public QueryBuilder(IEntitySchema entitySchema, ISchemaPathMatcher schemaPathMatcher) + public QueryBuilder(IEntityConfiguration entitySchema, ISchemaPathMatcher schemaPathMatcher) { this.entitySchema = entitySchema; this.schemaPathMatcher = schemaPathMatcher; @@ -18,10 +18,12 @@ public QueryBuilder(IEntitySchema entitySchema, ISchemaPathMatcher schemaPath /// public IQueryList Build(IDataContext context) { - var queries = GetMappedQueries(entitySchema.Mappings.ToList(), context); + var mappings = entitySchema.Mappings.ToList(); - foreach (var query in queries.Queries.Cast()) - query.ResolveRootQueryParameter(context); + var queries = GetMappedQueries(mappings, context); + + foreach (var query in queries.Queries) + query.ResolveQuery(context); return new QueryList(queries.Queries); } @@ -32,16 +34,17 @@ private QueryList GetMappedQueries(IReadOnlyCollection> for (var index = 1; index <= queryDependencyDepth; index++) { - var maps = mappings.Where(x => x.Order == index); + var maps = mappings.Where(x => x.Order == index).ToList(); foreach (var map in maps) { var dependentQueries = mappings.Where(x => x.Order == index + 1 && x.DependentOn != null && x.DependentOn.GetType() == map.Query.GetType()).ToList(); - map.Query.Children ??= new List(); + map.Query.Children = new List(); - map.Query.Children.AddRange(FilterByPaths(context, dependentQueries)); + var filtered = FilterByPaths(context, dependentQueries); + map.Query.Children.AddRange(filtered); } } @@ -54,8 +57,8 @@ private QueryList GetMappedQueries(IReadOnlyCollection> private IEnumerable FilterByPaths(IDataContext context, IEnumerable> mappings) { - var matchedMappings = context.Entity.SchemaPaths != null - ? mappings.Where(mapping => context.Entity.SchemaPaths.Any(Path => schemaPathMatcher.IsMatch(Path, mapping.SchemaPaths))) + var matchedMappings = context.Request?.SchemaPaths != null + ? mappings.Where(mapping => context.Request.SchemaPaths.Any(Path => schemaPathMatcher.IsMatch(Path, mapping.SchemaPaths))) .ToList() : mappings; diff --git a/src/Schemio/Impl/QueryExecutor.cs b/src/Schemio.Core/Impl/QueryExecutor.cs similarity index 76% rename from src/Schemio/Impl/QueryExecutor.cs rename to src/Schemio.Core/Impl/QueryExecutor.cs index 41bbbdf..4991f17 100644 --- a/src/Schemio/Impl/QueryExecutor.cs +++ b/src/Schemio.Core/Impl/QueryExecutor.cs @@ -1,4 +1,4 @@ -namespace Schemio.Impl +namespace Schemio.Core.Impl { public class QueryExecutor : IQueryExecutor { @@ -24,7 +24,7 @@ public IList Execute(IDataContext context, IQueryList queries) private IQueryList Process(IDataContext context, IQueryList queries, List globalResults) { var subscriber = new EventSubscriber(queries.GetChildrenQueries()); - var eventAggregator = new EventAggregator(subscriber); + var eventAggregator = new EventPublisher(subscriber); var results = RunQueries(queries); @@ -42,7 +42,7 @@ private static void CacheResults(IDataContext context, List result if (context.Cache == null) return; - foreach (var cacheResult in results.Where(result => result.GetType() + foreach (var cacheResult in results.Where(result => result != null && result.GetType() .GetCustomAttributes(typeof(CacheResultAttribute), false) .Any())) if (!context.Cache.ContainsKey(cacheResult.GetType().Name)) @@ -55,12 +55,19 @@ private List RunQueries(IQueryList queryList) foreach (var engine in queryEngines) { - var queries = queryList.Queries.Where(x => engine.CanExecute(x)); - - var results = engine.Execute(queries); - - if (results != null) - output.AddRange(results); + var tasks = queryList.Queries + .Where(x => engine.CanExecute(x)) + .Select(s => s.Run(engine)) + .ToList(); + + Task.WhenAll(tasks); + + foreach (var task in tasks) + { + var result = task.Result; + if (result != null) + output.Add(result); + } } return output; diff --git a/src/Schemio/Schemio.Core.csproj b/src/Schemio.Core/Motley - Backup.Core.csproj similarity index 87% rename from src/Schemio/Schemio.Core.csproj rename to src/Schemio.Core/Motley - Backup.Core.csproj index bc47a73..8d8a202 100644 --- a/src/Schemio/Schemio.Core.csproj +++ b/src/Schemio.Core/Motley - Backup.Core.csproj @@ -1,11 +1,10 @@ - + net8.0 enable enable true - True Code Shayk Code Shayk Copyright (c) 2024 Code Shayk @@ -14,10 +13,14 @@ README.md ninja-icon-16.png .Net Library to hydrate data entities by object graph using schema paths (supports XPath & JSONPath). - graphql json-schema xsd data-mapping query-engine data-mapper data-schema schema-mapping object-tree-query object-graph-query schema-mapper xsd-data-object object-graph-data entity-data entity-data-fetch hydrate-object object-hydration object-data object-fetch schemio schemio-core + graphql json-schema xsd data-mapping query-engine data-mapper data-schema schema-mapping object-tree-query object-graph-query schema-mapper xsd-data-object object-graph-data entity-data entity-data-fetch hydrate-object object-hydration object-data object-fetch motley motley-core https://github.com/CodeShayk/Schemio/wiki/i.-Home LICENSE.md Schemio (No Query Engine Provided) + True + True + snupkg + True diff --git a/src/Schemio/PathMatchers/JPathMatcher.cs b/src/Schemio.Core/PathMatchers/JPathMatcher.cs similarity index 67% rename from src/Schemio/PathMatchers/JPathMatcher.cs rename to src/Schemio.Core/PathMatchers/JPathMatcher.cs index 56c9946..fa2d03d 100644 --- a/src/Schemio/PathMatchers/JPathMatcher.cs +++ b/src/Schemio.Core/PathMatchers/JPathMatcher.cs @@ -1,11 +1,11 @@ -using Schemio.Helpers; +using Schemio.Core.Helpers; -namespace Schemio.PathMatchers +namespace Schemio.Core.PathMatchers { public class JPathMatcher : ISchemaPathMatcher { public bool IsMatch(string inputXPath, ISchemaPaths configuredXPaths) => - // Does the template xpath contain any of the mapping xpaths? + // Does the template path contain any of the mapping xpaths? inputXPath.IsNotNullOrEmpty() && configuredXPaths.Paths.Any(x => inputXPath.ToLower().Contains(x.ToLower())); } diff --git a/src/Schemio/PathMatchers/XPathMatcher.cs b/src/Schemio.Core/PathMatchers/XPathMatcher.cs similarity index 95% rename from src/Schemio/PathMatchers/XPathMatcher.cs rename to src/Schemio.Core/PathMatchers/XPathMatcher.cs index b22fc2f..77e18d0 100644 --- a/src/Schemio/PathMatchers/XPathMatcher.cs +++ b/src/Schemio.Core/PathMatchers/XPathMatcher.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Schemio.PathMatchers +namespace Schemio.Core.PathMatchers { public class XPathMatcher : ISchemaPathMatcher { diff --git a/src/Schemio/QueryComparer.cs b/src/Schemio.Core/QueryComparer.cs similarity index 92% rename from src/Schemio/QueryComparer.cs rename to src/Schemio.Core/QueryComparer.cs index 463eab4..85a1e11 100644 --- a/src/Schemio/QueryComparer.cs +++ b/src/Schemio.Core/QueryComparer.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public class QueryComparer : IEqualityComparer { diff --git a/src/Schemio/QueryList.cs b/src/Schemio.Core/QueryList.cs similarity index 98% rename from src/Schemio/QueryList.cs rename to src/Schemio.Core/QueryList.cs index 8b9c732..bc9422b 100644 --- a/src/Schemio/QueryList.cs +++ b/src/Schemio.Core/QueryList.cs @@ -1,4 +1,4 @@ -namespace Schemio +namespace Schemio.Core { public class QueryList : IQueryList { diff --git a/src/Schemio.Core/Schemio.Core.csproj b/src/Schemio.Core/Schemio.Core.csproj new file mode 100644 index 0000000..3157a7e --- /dev/null +++ b/src/Schemio.Core/Schemio.Core.csproj @@ -0,0 +1,53 @@ + + + + net9.0 + enable + enable + true + Code Shayk + Code Shayk + Copyright (c) 2024 Code Shayk + https://github.com/CodeShayk/Schemio + git + README.md + ninja-icon-16.png + .Net Library to hydrate data entities by object graph using schema paths (supports XPath & JSONPath). + graphql json-schema xsd data-mapping query-engine data-mapper data-schema schema-mapping object-tree-query object-graph-query schema-mapper xsd-data-object object-graph-data entity-data entity-data-fetch hydrate-object object-hydration object-data object-fetch schemio schemio-core + https://github.com/CodeShayk/Schemio/wiki/i.-Home + LICENSE.md + Schemio (No Query Engine Provided) + True + True + snupkg + True + 2.0.0 + Changes +- `Entity Schema` renamed to `Entity Configuration` and requires implementing `EntityContfiguration<TEntity>`. +- `IRootQuery`, `IChildQuery`, `BaseRootQuery<TParameter, TResult>` & `BaseChildQuery<TParameter,TResult>` removed. +- Both Parent and child queries need to implement `BaseQuery<TResult>` and provide override for `isContextResolved()` and `ResolveQuery()` methods. +- `IoC` registration streamlined with `fluent interface` for container configuration. +- Renamed `IEntityContext` to `IEntityRequest`. + + + + + True + \ + + + True + \ + + + True + \ + + + + + + + + + diff --git a/src/Schemio.Core/ServicesExtensions.cs b/src/Schemio.Core/ServicesExtensions.cs new file mode 100644 index 0000000..263be61 --- /dev/null +++ b/src/Schemio.Core/ServicesExtensions.cs @@ -0,0 +1,104 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Schemio.Core.Impl; +using Schemio.Core.PathMatchers; + +namespace Schemio.Core +{ + public static class ServicesExtensions + { + public static SchemioOptionsBuilder UseSchemio(this IServiceCollection services) + { + services.AddTransient(typeof(IQueryBuilder<>), typeof(QueryBuilder<>)); + services.AddTransient(typeof(IEntityBuilder<>), typeof(EntityBuilder<>)); + services.AddTransient(typeof(IDataProvider<>), typeof(DataProvider<>)); + services.AddTransient(); + services.AddTransient(); + + return new SchemioOptionsBuilder(services); + } + } + + public class SchemioOptionsBuilder : ISchemioOptions + { + public SchemioOptionsBuilder(IServiceCollection services) + { + Services = services; + } + + public IServiceCollection Services { get; } + + public ISchemioOptions WithEngine(Func queryEngine) + { + if (queryEngine != null) + { + Services.AddTransient(c => queryEngine(c)); + } + + return this; + } + + public ISchemioOptions WithEngine() where TEngine : IQueryEngine + { + Services.AddTransient(typeof(IQueryEngine), typeof(TEngine)); + + return this; + } + + public ISchemioOptions WithEngines(Func queryEngines) + { + if (queryEngines != null) + { + Services.AddTransient(c => queryEngines(c)); + } + + return this; + } + + /// + /// Register an instance of ISchemaPathMatcher. Default is XPathMatcher. + /// + /// + /// + public ISchemioOptions WithPathMatcher(Func pathMatcher) + { + if (pathMatcher != null) + { + Services.RemoveAll(); + Services.AddTransient(c => pathMatcher(c)); + } + + return this; + } + + /// + /// Register and instance of EntityConfiguration + /// You could register configuration for multiple entities. + /// + /// IEntity + /// Instance of EntityConfiguration[typeparamref name="T"] + /// + public ISchemioOptions WithEntityConfiguration(Func> entityConfiguration) where T : class, IEntity + { + if (entityConfiguration != null) + { + Services.AddTransient(typeof(IEntityConfiguration), c => entityConfiguration(c)); + } + + return this; + } + } + + public interface ISchemioOptions + { + ISchemioOptions WithEngine(Func queryEngines); + + ISchemioOptions WithEngine() where TEngine : IQueryEngine; + + ISchemioOptions WithEngines(Func queryEngines); + + ISchemioOptions WithPathMatcher(Func pathMatcher); + + ISchemioOptions WithEntityConfiguration(Func> entityConfiguration) where T : class, IEntity; + } +} \ No newline at end of file diff --git a/src/Schemio/XML/XMLDataProvider.cs b/src/Schemio.Core/XML/XMLDataProvider.cs similarity index 90% rename from src/Schemio/XML/XMLDataProvider.cs rename to src/Schemio.Core/XML/XMLDataProvider.cs index 2aab53e..f3e1872 100644 --- a/src/Schemio/XML/XMLDataProvider.cs +++ b/src/Schemio.Core/XML/XMLDataProvider.cs @@ -2,9 +2,9 @@ using System.Xml.Linq; using System.Xml.Serialization; using Microsoft.Extensions.Logging; -using Schemio.Helpers.Xml; +using Schemio.Core.Helpers.Xml; -namespace Schemio.XML +namespace Schemio.Core.XML { internal class XMLDataProvider where T : IEntity { @@ -28,7 +28,7 @@ public XMLDataProvider(IEntityContextValidator entityContextValidator, /// /// /// XDocument - public virtual XDocument GetData(IEntityContext context) + public virtual XDocument GetData(IEntityRequest context) { // Log Request context. LogRequest(context); @@ -63,8 +63,8 @@ private XDocument TransformToXmlDocument(T dataSource) return doc; } - private void LogRequest(IEntityContext context) => logger.LogInformation(context.GetType().Name); + private void LogRequest(IEntityRequest context) => logger.LogInformation(context.GetType().Name); - private void ValidateRequest(IEntityContext context) => entityContextValidator.Validate(context); + private void ValidateRequest(IEntityRequest context) => entityContextValidator.Validate(context); } } \ No newline at end of file diff --git a/src/Schemio.EntityFramework/BaseSQLChildQuery.cs b/src/Schemio.EntityFramework/BaseSQLChildQuery.cs deleted file mode 100644 index 134d310..0000000 --- a/src/Schemio.EntityFramework/BaseSQLChildQuery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Schemio.EntityFramework -{ - public abstract class BaseSQLChildQuery - : BaseChildQuery, ISQLQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Get query delegate with implementation to return query result. - /// Delegate returns a collection from db. - /// - /// Func> - public abstract IEnumerable Run(DbContext dbContext); - } -} \ No newline at end of file diff --git a/src/Schemio.EntityFramework/BaseSQLRootQuery.cs b/src/Schemio.EntityFramework/BaseSQLRootQuery.cs deleted file mode 100644 index c682126..0000000 --- a/src/Schemio.EntityFramework/BaseSQLRootQuery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Schemio.EntityFramework -{ - public abstract class BaseSQLRootQuery - : BaseRootQuery, ISQLQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Get query delegate with implementation to return query result. - /// Delegate returns a collection from db. - /// - /// Func> - public abstract IEnumerable Run(DbContext dbContext); - } -} \ No newline at end of file diff --git a/src/Schemio.EntityFramework/IDbContext.cs b/src/Schemio.EntityFramework/IDbContext.cs deleted file mode 100644 index 8bc0aca..0000000 --- a/src/Schemio.EntityFramework/IDbContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Schemio.EntityFramework -{ - public interface IDbContext - { - DbSet Set() where TEntity : class; - } -} \ No newline at end of file diff --git a/src/Schemio.EntityFramework/ISQLQuery.cs b/src/Schemio.EntityFramework/ISQLQuery.cs index a8240e5..137e8de 100644 --- a/src/Schemio.EntityFramework/ISQLQuery.cs +++ b/src/Schemio.EntityFramework/ISQLQuery.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Schemio.Core; namespace Schemio.EntityFramework { @@ -9,6 +10,6 @@ public interface ISQLQuery /// Delegate returns a collection from db. /// /// Func> - IEnumerable Run(DbContext dbContext); + Task Run(DbContext dbContext); } } \ No newline at end of file diff --git a/src/Schemio.EntityFramework/QueryEngine.cs b/src/Schemio.EntityFramework/QueryEngine.cs index 1aff4c1..acd2b2b 100644 --- a/src/Schemio.EntityFramework/QueryEngine.cs +++ b/src/Schemio.EntityFramework/QueryEngine.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Schemio.Core; namespace Schemio.EntityFramework { @@ -13,23 +14,12 @@ public QueryEngine(IDbContextFactory _dbContextFactory) public bool CanExecute(IQuery query) => query != null && query is ISQLQuery; - public IEnumerable Execute(IEnumerable queries) + public Task Execute(IQuery query) { - var output = new List(); - using (var dbcontext = _dbContextFactory.CreateDbContext()) { - foreach (var query in queries) - { - var results = ((ISQLQuery)query).Run(dbcontext); - - if (results == null) - continue; - - output.AddRange(results); - } - - return output.ToArray(); + var result = ((ISQLQuery)query).Run(dbcontext); + return result; } } } diff --git a/src/Schemio.EntityFramework/SQLQuery.cs b/src/Schemio.EntityFramework/SQLQuery.cs new file mode 100644 index 0000000..ad20b68 --- /dev/null +++ b/src/Schemio.EntityFramework/SQLQuery.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Schemio.Core; + +namespace Schemio.EntityFramework +{ + public abstract class SQLQuery + : BaseQuery, ISQLQuery + where TQueryResult : IQueryResult + { + private Func> QueryDelegate = null; + + public override bool IsContextResolved() => QueryDelegate != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + QueryDelegate = GetQuery(context, parentQueryResult); + } + + async Task ISQLQuery.Run(DbContext dbContext) + { + return await QueryDelegate(dbContext); + } + + /// + /// Get query delegate to return query result. + /// + /// + /// + /// + protected abstract Func> GetQuery(IDataContext context, IQueryResult parentQueryResult); + } +} \ No newline at end of file diff --git a/src/Schemio.EntityFramework/Schemio.EntityFramework.csproj b/src/Schemio.EntityFramework/Schemio.EntityFramework.csproj index ff8fc03..b9dcc86 100644 --- a/src/Schemio.EntityFramework/Schemio.EntityFramework.csproj +++ b/src/Schemio.EntityFramework/Schemio.EntityFramework.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable true enable @@ -19,6 +19,13 @@ LICENSE.md True True + snupkg + True + 2.0.0 + + + + True @@ -37,11 +44,11 @@ - + - + diff --git a/src/Schemio.SQL/BaseSQLChildQuery.cs b/src/Schemio.SQL/BaseSQLChildQuery.cs deleted file mode 100644 index b293575..0000000 --- a/src/Schemio.SQL/BaseSQLChildQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Schemio.SQL -{ - public abstract class BaseSQLChildQuery : BaseSQLQuery, IChildQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - public abstract void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult); - } -} \ No newline at end of file diff --git a/src/Schemio.SQL/BaseSQLQuery.cs b/src/Schemio.SQL/BaseSQLQuery.cs deleted file mode 100644 index 98e6f26..0000000 --- a/src/Schemio.SQL/BaseSQLQuery.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using Dapper; - -namespace Schemio.SQL -{ - public abstract class BaseSQLQuery : BaseQuery, ISQLQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - public abstract IEnumerable Execute(IDbConnection conn); - - public IEnumerable Run(IDbConnection conn) - { - return (IEnumerable)Execute(conn); - } - } -} \ No newline at end of file diff --git a/src/Schemio.SQL/BaseSQLRootQuery.cs b/src/Schemio.SQL/BaseSQLRootQuery.cs deleted file mode 100644 index 3573f88..0000000 --- a/src/Schemio.SQL/BaseSQLRootQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Schemio.SQL -{ - public abstract class BaseSQLRootQuery : BaseSQLQuery, IRootQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - public abstract void ResolveRootQueryParameter(IDataContext context); - } -} \ No newline at end of file diff --git a/src/Schemio.SQL/ISQLQuery.cs b/src/Schemio.SQL/ISQLQuery.cs index 3445994..7292752 100644 --- a/src/Schemio.SQL/ISQLQuery.cs +++ b/src/Schemio.SQL/ISQLQuery.cs @@ -1,9 +1,10 @@ using System.Data; +using Schemio.Core; namespace Schemio.SQL { public interface ISQLQuery : IQuery { - IEnumerable Run(IDbConnection conn); + Task Run(IDbConnection conn); } } \ No newline at end of file diff --git a/src/Schemio.SQL/QueryEngine.cs b/src/Schemio.SQL/QueryEngine.cs index ac0739a..ad5d168 100644 --- a/src/Schemio.SQL/QueryEngine.cs +++ b/src/Schemio.SQL/QueryEngine.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Schemio.Core; namespace Schemio.SQL { @@ -19,33 +20,22 @@ public QueryEngine(SQLConfiguration sqlConfiguration) public bool CanExecute(IQuery query) => query != null && query is ISQLQuery; - public IEnumerable Execute(IEnumerable queries) + public Task Execute(IQuery query) { var factory = DbProviderFactories.GetFactory(sqlConfiguration.ConnectionSettings.ProviderName) ?? throw new InvalidOperationException($"Provider: {sqlConfiguration.ConnectionSettings.ProviderName} is not supported. Please register entry in DbProviderFactories "); - var batches = queries.Chunk(sqlConfiguration.QuerySettings?.QueryBatchSize ?? 10); + using (var connection = factory.CreateConnection()) + { + if (connection == null) + throw new Exception($"Failed to create connection with Provider: {sqlConfiguration.ConnectionSettings.ProviderName}. Please check the connection settings."); - var output = new List(); + connection.ConnectionString = sqlConfiguration.ConnectionSettings.ConnectionString; - foreach (var batch in batches) - using (var connection = factory.CreateConnection()) - { - if (connection == null) - throw new Exception($"Failed to create connection with Provider: {sqlConfiguration.ConnectionSettings.ProviderName}. Please check the connection settings."); + var result = ((ISQLQuery)query).Run(connection); - connection.ConnectionString = sqlConfiguration.ConnectionSettings.ConnectionString; - - foreach (var query in batch.Cast()) - { - var results = query.Run(connection); - - if (results != null && results.Any()) - output.AddRange(results); - } - } - - return output.ToArray(); + return result; + } } } } \ No newline at end of file diff --git a/src/Schemio.SQL/SQLQuery.cs b/src/Schemio.SQL/SQLQuery.cs new file mode 100644 index 0000000..85fa883 --- /dev/null +++ b/src/Schemio.SQL/SQLQuery.cs @@ -0,0 +1,31 @@ +using System.Data; +using Schemio.Core; + +namespace Schemio.SQL +{ + public abstract class SQLQuery : BaseQuery, ISQLQuery + where TQueryResult : IQueryResult + { + async Task ISQLQuery.Run(IDbConnection conn) + { + return await QueryDelegate(conn); + } + + private Func> QueryDelegate = null; + + public override bool IsContextResolved() => QueryDelegate != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + QueryDelegate = GetQuery(context, parentQueryResult); + } + + /// + /// Get query delegate to return query result. + /// + /// + /// + /// + protected abstract Func> GetQuery(IDataContext context, IQueryResult parentQueryResult); + } +} \ No newline at end of file diff --git a/src/Schemio.SQL/Schemio.SQL.csproj b/src/Schemio.SQL/Schemio.SQL.csproj index af22bed..1946be3 100644 --- a/src/Schemio.SQL/Schemio.SQL.csproj +++ b/src/Schemio.SQL/Schemio.SQL.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable true @@ -19,6 +19,13 @@ LICENSE.md True True + snupkg + True + 2.0.0 + + + + True @@ -37,12 +44,12 @@ - - + + - + diff --git a/src/Schemio/BaseChildQuery.cs b/src/Schemio/BaseChildQuery.cs deleted file mode 100644 index 6bbb761..0000000 --- a/src/Schemio/BaseChildQuery.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Schemio -{ - /// - /// Implement this base class to create a dependent or child query. - /// - /// Parameter type of the query. - /// Result type of the query. - public abstract class BaseChildQuery : BaseQuery, IChildQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Implement to resolve query parameter using parent query's result. - /// - /// Data context passed in. - /// Parent Query's result. - public abstract void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult); - } -} \ No newline at end of file diff --git a/src/Schemio/BaseQuery.cs b/src/Schemio/BaseQuery.cs deleted file mode 100644 index 9d82dfa..0000000 --- a/src/Schemio/BaseQuery.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Schemio -{ - /// - /// Implement this base class to create a data provider query. - /// - /// - /// - public abstract class BaseQuery : IQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Parameter values for query to execute. - /// - protected TQueryParameter QueryParameter; - - /// - /// Children queries dependent on this query. - /// - public List Children { get; set; } - - /// - /// Get the result type for the query - /// - public Type ResultType - { - get { return typeof(TQueryResult); } - } - - /// - /// Determines whether the parameter for query is resolved. - /// - /// - public bool IsContextResolved() => QueryParameter != null; - } -} \ No newline at end of file diff --git a/src/Schemio/BaseRootQuery.cs b/src/Schemio/BaseRootQuery.cs deleted file mode 100644 index 2d17301..0000000 --- a/src/Schemio/BaseRootQuery.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Schemio -{ - /// - /// Implement this base class to create a root (or level 1) query. - /// - /// Parameter type of the query. - /// Result type of the query. - public abstract class BaseRootQuery : BaseQuery, IRootQuery - where TQueryParameter : IQueryParameter - where TQueryResult : IQueryResult - { - /// - /// Implement to resolve query parameter using data context. - /// - /// Data context. - public abstract void ResolveRootQueryParameter(IDataContext context); - } -} \ No newline at end of file diff --git a/src/Schemio/CollectionResult.cs b/src/Schemio/CollectionResult.cs deleted file mode 100644 index 02dba67..0000000 --- a/src/Schemio/CollectionResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio -{ - public class CollectionResult : IQueryResult - { - private List list; - - public CollectionResult(List list) - { - this.list = list; - } - - public List Items - { get { return list; } } - } -} \ No newline at end of file diff --git a/src/Schemio/IChildQuery.cs b/src/Schemio/IChildQuery.cs deleted file mode 100644 index 0df26e9..0000000 --- a/src/Schemio/IChildQuery.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio -{ - /// - /// Implement to create a child query. - /// - public interface IChildQuery : IQuery - { - /// - /// Implement to resolve query parameter using parent query's result. - /// - /// - /// - void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult); - } -} \ No newline at end of file diff --git a/src/Schemio/IEntityContextValidator.cs b/src/Schemio/IEntityContextValidator.cs deleted file mode 100644 index 9622b23..0000000 --- a/src/Schemio/IEntityContextValidator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio -{ - public interface IEntityContextValidator - { - public void Validate(IEntityContext context); - } -} \ No newline at end of file diff --git a/src/Schemio/IRootQuery.cs b/src/Schemio/IRootQuery.cs deleted file mode 100644 index 7ed12d2..0000000 --- a/src/Schemio/IRootQuery.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Schemio -{ - /// - /// Implement to create root query. - /// - public interface IRootQuery : IQuery - { - void ResolveRootQueryParameter(IDataContext context); - } -} \ No newline at end of file diff --git a/src/Schemio/ISchemaPathMatcher.cs b/src/Schemio/ISchemaPathMatcher.cs deleted file mode 100644 index 4c54ded..0000000 --- a/src/Schemio/ISchemaPathMatcher.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio -{ - public interface ISchemaPathMatcher - { - bool IsMatch(string inputPath, ISchemaPaths configuredPaths); - } -} \ No newline at end of file diff --git a/src/Schemio/ITransformExecutor.cs b/src/Schemio/ITransformExecutor.cs deleted file mode 100644 index c7f7adb..0000000 --- a/src/Schemio/ITransformExecutor.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio -{ - public interface ITransformExecutor where TEntity : IEntity - { - TEntity Execute(IDataContext context, IList results); - } -} \ No newline at end of file diff --git a/src/Schemio/ServicesExtensions.cs b/src/Schemio/ServicesExtensions.cs deleted file mode 100644 index 9f2434e..0000000 --- a/src/Schemio/ServicesExtensions.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Schemio.Impl; -using Schemio.PathMatchers; - -namespace Schemio -{ - public static class ServicesExtensions - { - public static IServiceCollection UseSchemio(this IServiceCollection services, - ISchemioOption options) - where TEntity : IEntity, new() - { - services.AddTransient, QueryBuilder>(); - services.AddTransient, TransformExecutor>(); - services.AddTransient(); - - var schemioOptions = options?.SchemioOptions; - - if (schemioOptions?.EntitySchema == null) - throw new ArgumentException("Cannot find configured Entity Schema definition. Please pass in implementation of IEntitySchema where T is IEntity."); - - services.AddTransient(c => schemioOptions.EntitySchema(c)); - - services.AddTransient(typeof(ISchemaPathMatcher), c => schemioOptions?.SchemaPathMatcher != null ? schemioOptions?.SchemaPathMatcher(c) : new XPathMatcher()); - - if (schemioOptions?.QueryEngines == null || !schemioOptions.QueryEngines.Any()) - throw new ArgumentException("Cannot find configured Query Engines. Please pass in implementation of IQueryEngine."); - - foreach (var engine in schemioOptions.QueryEngines) - { - services.AddTransient(c => engine(c)); - } - - if (schemioOptions?.Logger != null) - services.AddTransient(typeof(ILogger>), c => schemioOptions.Logger(c)); - - // Register data provider with dependencies. - services.AddTransient>(c => - new DataProvider - ( - schemioOptions?.Logger != null ? c.GetService>>() : null, - c.GetService>(), - c.GetService(), - c.GetService>() - )); - - return services; - } - } - - public class SchemioOptions where TEntity : IEntity, new() - { - public SchemioOptions() - { - QueryEngines = new List>(); - } - - /// - /// Implementation of IEntitySchema for schema definition. - /// - public Func> EntitySchema { get; set; } - - /// - /// Custom ISchemaPathMatcher Implementation for schema path matching. Default is XPathMatcher if not provided. - /// - public Func SchemaPathMatcher { get; set; } - - /// - /// Supported query engines. List of type IQueryEngine implementation. - /// - public List> QueryEngines { get; set; } - - /// - /// Supported query engines. List of type IQueryEngine implementation. - /// - public Func>> Logger { get; set; } - } - - public class SchemioOptionsBuilder : ISchemioLogger, ISchemioEngine, ISchemioOption - where TEntity : IEntity, new() - { - public SchemioOptions SchemioOptions { get; set; } - - public ISchemioEngine AddEngine(Func queryEngine) - { - SchemioOptions.QueryEngines.Add(queryEngine); - return this; - } - - public ISchemioLogger LogWith(Func>> logger) - { - SchemioOptions.Logger = logger; - return this; - } - } - - public interface ISchemioLogger : ISchemioOption - where TEntity : IEntity, new() - { - ISchemioLogger LogWith(Func>> logger); - } - - public interface ISchemioEngine : ISchemioOption - where TEntity : IEntity, new() - { - ISchemioEngine AddEngine(Func queryEngine); - - ISchemioLogger LogWith(Func>> logger); - } - - public interface ISchemioOption where TEntity : IEntity, new() - { - SchemioOptions SchemioOptions { get; set; } - } - - public static class With - { - public static ISchemioEngine Schema(Func> entitySchema, Func schemaPathMatcher = null) where TEntity : IEntity, new() - { - var builder = new SchemioOptionsBuilder(); - builder.SchemioOptions = new SchemioOptions { EntitySchema = entitySchema, SchemaPathMatcher = schemaPathMatcher }; - return builder; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/E2E.Tests/BaseTest.cs b/tests/Schemio.API.Tests/E2E.Tests/BaseTest.cs new file mode 100644 index 0000000..ebb7a34 --- /dev/null +++ b/tests/Schemio.API.Tests/E2E.Tests/BaseTest.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Schemio.API.Tests.EntitySetup; +using Schemio.Core; +using Schemio.Core.PathMatchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace Schemio.API.Tests.E2E.Tests +{ + [TestFixture] + public class BaseTest + { + protected WireMockServer server; + protected ServiceProvider serviceProvider; + protected IDataProvider dataProvider; + + [OneTimeTearDown] + public void OneTimeTearDown() + { + if (serviceProvider is IDisposable disposable) + disposable.Dispose(); + + server.Stop(); + + if (server is IDisposable sdisposable) + sdisposable.Dispose(); + } + + public void StubApi(string endpoint, object body, IDictionary headers = null) + { + // Arrange (start WireMock.Net server) + + var response = Response.Create() + .WithStatusCode(200) + .WithBodyAsJson(body); + + if (headers != null) + response.WithHeaders(headers); + + server + .Given(Request.Create().WithUrl(endpoint).UsingGet()) + .RespondWith(response); + + // Act (use a HttpClient which connects to the URL where WireMock.Net is running) + var result = new HttpClient().GetAsync(endpoint).Result; + var raw = result.Content.ReadAsStringAsync().Result; + + // Assert + //Check.That(response).IsEqualTo(EXPECTED_RESULT); + } + + [OneTimeSetUp] + public void OneTimeSetup() + { + server = WireMockServer.Start(5000); + + var services = new ServiceCollection(); + + services.AddLogging(c => c.AddConsole()); + services.AddHttpClient(); + + services.UseSchemio() + .WithEngine() + .WithPathMatcher(c => new JPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); + + // 4. Build the service provider + serviceProvider = services.BuildServiceProvider(); + + dataProvider = serviceProvider.GetService>(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/E2E.Tests/E2ETests.cs b/tests/Schemio.API.Tests/E2E.Tests/E2ETests.cs new file mode 100644 index 0000000..b44161a --- /dev/null +++ b/tests/Schemio.API.Tests/E2E.Tests/E2ETests.cs @@ -0,0 +1,168 @@ +using Schemio.Core.Helpers; +using NUnit.Framework; +using Schemio.API.Tests.EntitySetup; +using Schemio.API.Tests.EntitySetup.WebApis; +using static Schemio.API.Tests.EntitySetup.Customer; +using static Schemio.API.Tests.EntitySetup.Customer.Contacts; + +namespace Schemio.API.Tests.E2E.Tests +{ + [TestFixture] + public class E2ETests : BaseTest + { + [SetUp] + public void Setup() + { + StubApi(string.Format(Endpoints.BaseAddress + Endpoints.Customer, Endpoints.Ids.CustomerId), new { Id = 1000, Name = "John McKinsey", Code = "THG-UY6789" }, new Dictionary { { "x-meta-branch-code", "London" } }); + StubApi(string.Format(Endpoints.BaseAddress + Endpoints.Communication, Endpoints.Ids.CustomerId), new { Id = 4567, Telephone = "07675998878", Email = "John.McKinsy@gmail.com", HouseNo = "22", City = "London", Region = "London", PostalCode = "W12 6GH", Country = "United Kingdom" }); + StubApi(string.Format(Endpoints.BaseAddress + Endpoints.Orders, Endpoints.Ids.CustomerId), new[] { new { OrderId = 1234, OrderNo = "GHK-897GB", Date = "2024-01-01T00:00:00" } }); + StubApi(string.Format(Endpoints.BaseAddress + Endpoints.OrderItems, Endpoints.Ids.CustomerId, Endpoints.Ids.OrderId), new[] { new { OrderId = 1234, ItemId = 2244, Name = "Pen", Cost = 12.00m }, new { OrderId = 1234, ItemId = 6677, Name = "Book", Cost = 15.00m } }); + } + + [TearDown] + public void Teardown() + { + } + + [Test] + public void TestDataProviderToFetchWholeContractWhenNamesAreNull() + { + var customer = dataProvider.GetData(new CustomerRequest + { + CustomerId = Endpoints.Ids.CustomerId + }); + + var expected = new Customer + { + Id = 1000, + Name = "John McKinsey", + Code = "THG-UY6789", + Branch = "London", // Received via response header + Communication = new Contacts + { + ContactId = 4567, + Phone = "07675998878", + Email = "John.McKinsy@gmail.com", + PostalAddress = new Address + { + HouseNo = "22", + City = "London", + Region = "London", + PostalCode = "W12 6GH", + Country = "United Kingdom", + } + }, + Orders = + [ + new Order + { + OrderId = 1234, + OrderNo = "GHK-897GB", + Date = DateTime.Parse("2024-01-01T00:00:00"), + Items = + [ + new Order.OrderItem + { + ItemId = 2244, Name = "Pen", Cost = 12.00m + }, + new Order.OrderItem + { + ItemId = 6677, Name = "Book", Cost = 15.00m + } + ] + } + ] + }; + + AssertAreEqual(expected, customer); + } + + [Test] + public void TestDataProviderToFetchPartialCustomerOrdersContractWhenNamesAreIncluded() + { + var customer = dataProvider.GetData(new CustomerRequest + { + CustomerId = Endpoints.Ids.CustomerId, + SchemaPaths = ["customer.orders.items"] + }); + + var expected = new Customer + { + Id = 1000, + Name = "John McKinsey", + Code = "THG-UY6789", + Branch = "London", + Orders = + [ + new Order + { + OrderId = 1234, + OrderNo = "GHK-897GB", + Date = DateTime.Parse("2024-01-01T00:00:00"), + Items = + [ + new Order.OrderItem + { + ItemId = 2244, Name = "Pen", Cost = 12.00m + }, + new Order.OrderItem + { + ItemId = 6677, Name = "Book", Cost = 15.00m + } + ] + } + ] + }; + + AssertAreEqual(expected, customer); + } + + [Test] + public void TestDataProviderToFetchPartialCustomerCommunicationContractWhenNamesAreIncluded() + { + var customer = dataProvider.GetData(new CustomerRequest + { + CustomerId = Endpoints.Ids.CustomerId, + SchemaPaths = ["customer.communication"] + }); + + var expected = new Customer + { + Id = 1000, + Name = "John McKinsey", + Code = "THG-UY6789", + Branch = "London", + Communication = new Contacts + { + ContactId = 4567, + Phone = "07675998878", + Email = "John.McKinsy@gmail.com", + PostalAddress = new Address + { + HouseNo = "22", + City = "London", + Region = "London", + PostalCode = "W12 6GH", + Country = "United Kingdom", + } + } + }; + + AssertAreEqual(expected, customer); + } + + private void AssertAreEqual(Customer expected, Customer actual) + { + var actualCustomer = actual.ToJson(); + var expectedCustomer = expected.ToJson(); + + Console.WriteLine("expected:"); + Console.WriteLine(expectedCustomer); + + Console.WriteLine("actual:"); + Console.WriteLine(actualCustomer); + + Assert.That(actualCustomer, Is.EqualTo(expectedCustomer)); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/Customer.cs b/tests/Schemio.API.Tests/EntitySetup/Customer.cs new file mode 100644 index 0000000..b9edab2 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/Customer.cs @@ -0,0 +1,46 @@ +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup +{ + public class Customer : IEntity + { + public int Id { get; set; } + public string Code { get; set; } + public string Name { get; set; } + public string Branch { get; set; } + public Contacts Communication { get; set; } + public Order[] Orders { get; set; } + + public class Contacts + { + public int ContactId { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public Address PostalAddress { get; set; } + + public class Address + { + public string HouseNo { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + } + } + + public class Order + { + public int OrderId { get; set; } + public string OrderNo { get; set; } + public DateTime Date { get; set; } + public OrderItem[] Items { get; set; } + + public class OrderItem + { + public int ItemId { get; set; } + public string Name { get; set; } + public decimal Cost { get; set; } + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/CustomerConfiguration.cs b/tests/Schemio.API.Tests/EntitySetup/CustomerConfiguration.cs new file mode 100644 index 0000000..da5f523 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/CustomerConfiguration.cs @@ -0,0 +1,25 @@ +using Schemio.API.Tests.EntitySetup.ResultTransformers; +using Schemio.API.Tests.EntitySetup.WebApis; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup +{ + internal class CustomerConfiguration : EntityConfiguration + { + /// + /// Constructs the api aggregate with web apis and result transformers to map data to aggregated contract. + /// + /// Mappings + public override IEnumerable> GetSchema() + { + return CreateSchema.For() + .Map(For.Paths("customer"), + customer => customer.Dependents + .Map(For.Paths("customer.communication")) + .Map(For.Paths("customer.orders"), + customerOrders => customerOrders.Dependents + .Map(For.Paths("customer.orders.items"))) + ).End(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/CustomerRequest.cs b/tests/Schemio.API.Tests/EntitySetup/CustomerRequest.cs new file mode 100644 index 0000000..3568b57 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/CustomerRequest.cs @@ -0,0 +1,10 @@ +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup +{ + internal class CustomerRequest : IEntityRequest + { + public int CustomerId { get; set; } + public string[] SchemaPaths { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs b/tests/Schemio.API.Tests/EntitySetup/QueryResults/CommunicationResult.cs similarity index 85% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs rename to tests/Schemio.API.Tests/EntitySetup/QueryResults/CommunicationResult.cs index 9080774..729c021 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs +++ b/tests/Schemio.API.Tests/EntitySetup/QueryResults/CommunicationResult.cs @@ -1,4 +1,6 @@ -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.QueryResults { [CacheResult] public class CommunicationResult : IQueryResult diff --git a/tests/Schemio.API.Tests/EntitySetup/QueryResults/CustomerResult.cs b/tests/Schemio.API.Tests/EntitySetup/QueryResults/CustomerResult.cs new file mode 100644 index 0000000..0a55954 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/QueryResults/CustomerResult.cs @@ -0,0 +1,9 @@ +namespace Schemio.API.Tests.EntitySetup.QueryResults +{ + public class CustomerResult : WebHeaderResult + { + public int Id { get; set; } + public string Code { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderItemResult.cs b/tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderItemResult.cs new file mode 100644 index 0000000..260a388 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderItemResult.cs @@ -0,0 +1,10 @@ +namespace Schemio.API.Tests.EntitySetup.QueryResults +{ + public class OrderItemResult + { + public int OrderId { get; set; } + public int ItemId { get; set; } + public string Name { get; set; } + public decimal Cost { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderResult.cs b/tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderResult.cs similarity index 71% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderResult.cs rename to tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderResult.cs index d04050e..6a5a26d 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderResult.cs +++ b/tests/Schemio.API.Tests/EntitySetup/QueryResults/OrderResult.cs @@ -1,4 +1,6 @@ -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.QueryResults { public class OrderResult : IQueryResult { diff --git a/tests/Schemio.API.Tests/EntitySetup/Transforms/CommunicationTransform.cs b/tests/Schemio.API.Tests/EntitySetup/Transforms/CommunicationTransform.cs new file mode 100644 index 0000000..d7fc36d --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/Transforms/CommunicationTransform.cs @@ -0,0 +1,29 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.ResultTransformers +{ + public class CommunicationTransform : BaseTransformer + { + public override void Transform(CommunicationResult apiResult, Customer contract) + { + var customer = contract ?? new Customer(); + customer.Communication = new Customer.Contacts + { + ContactId = apiResult.Id, + Email = apiResult.Email, + Phone = apiResult.Telephone + }; + + if (apiResult.HouseNo != null) + customer.Communication.PostalAddress = new Customer.Contacts.Address + { + HouseNo = apiResult.HouseNo, + City = apiResult.City, + Country = apiResult.Country, + PostalCode = apiResult.PostalCode, + Region = apiResult.Region + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/Transforms/CustomerTransform.cs b/tests/Schemio.API.Tests/EntitySetup/Transforms/CustomerTransform.cs new file mode 100644 index 0000000..3f53a6f --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/Transforms/CustomerTransform.cs @@ -0,0 +1,20 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.ResultTransformers +{ + public class CustomerTransform : BaseTransformer + { + public override void Transform(CustomerResult result, Customer contract) + { + var customer = contract ?? new Customer(); + customer.Id = result.Id; + customer.Name = result.Name; + customer.Code = result.Code; + + if (result is WebHeaderResult webResult) + if (webResult.Headers.TryGetValue("x-meta-branch-code", out var branch)) + customer.Branch = branch; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/Transforms/OrderItemsTransform.cs b/tests/Schemio.API.Tests/EntitySetup/Transforms/OrderItemsTransform.cs new file mode 100644 index 0000000..8c5912a --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/Transforms/OrderItemsTransform.cs @@ -0,0 +1,31 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; +using Schemio.Core.Helpers; +using static Schemio.API.Tests.EntitySetup.Customer.Order; + +namespace Schemio.API.Tests.EntitySetup.ResultTransformers +{ + public class OrderItemsTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer customer) + { + if (collectionResult == null || !collectionResult.Any() || customer.Orders == null) + return; + + foreach (var result in collectionResult) + { + var order = customer.Orders.FirstOrDefault(o => o.OrderId == result.OrderId); + if (order == null) + continue; + + order.Items = ArrayUtil.EnsureAndResizeArray(order.Items, out var index); + order.Items[index] = new OrderItem + { + ItemId = result.ItemId, + Name = result.Name, + Cost = result.Cost + }; + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/Transforms/OrdersTransform.cs b/tests/Schemio.API.Tests/EntitySetup/Transforms/OrdersTransform.cs new file mode 100644 index 0000000..8f71f72 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/Transforms/OrdersTransform.cs @@ -0,0 +1,27 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; +using static Schemio.API.Tests.EntitySetup.Customer; + +namespace Schemio.API.Tests.EntitySetup.ResultTransformers +{ + public class OrdersTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer contract) + { + if (collectionResult == null || !collectionResult.Any()) + return; + + var customer = contract ?? new Customer(); + + customer.Orders = new Order[collectionResult.Count]; + + for (var index = 0; index < collectionResult.Count; index++) + customer.Orders[index] = new Order + { + Date = collectionResult[index].Date, + OrderId = collectionResult[index].OrderId, + OrderNo = collectionResult[index].OrderNo + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/WebApis/CommunicationWebQuery.cs b/tests/Schemio.API.Tests/EntitySetup/WebApis/CommunicationWebQuery.cs new file mode 100644 index 0000000..6207527 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/WebApis/CommunicationWebQuery.cs @@ -0,0 +1,18 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.WebApis +{ + internal class CommunicationWebQuery : WebQuery + { + public CommunicationWebQuery() : base(Endpoints.BaseAddress) + { + } + + protected override Func GetQuery(IDataContext context, IQueryResult parentApiResult) + { + var customer = (CustomerResult)parentApiResult; + return () => new Uri(string.Format(Endpoints.BaseAddress + Endpoints.Communication, customer.Id), UriKind.Absolute); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/WebApis/CustomerWebQuery.cs b/tests/Schemio.API.Tests/EntitySetup/WebApis/CustomerWebQuery.cs new file mode 100644 index 0000000..1f5d26f --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/WebApis/CustomerWebQuery.cs @@ -0,0 +1,42 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.WebApis +{ + public class CustomerWebQuery : WebQuery + { + public CustomerWebQuery() : base(Endpoints.BaseAddress) + { + } + + protected override Func GetQuery(IDataContext context, IQueryResult parentApiResult) + { + // Executes as root or level 1 api. + var customerContext = (CustomerRequest)context.Request; + + return () => new Uri(string.Format(Endpoints.BaseAddress + Endpoints.Customer, customerContext.CustomerId), UriKind.Absolute); + } + + /// + /// Override to pass outgoing request headers. + /// + /// + protected override IDictionary GetRequestHeaders() + { + return new Dictionary + { + { "x-meta-branch-code", "London" } + }; + } + + /// + /// Override to subscribe for given Response headers to be added to Web query Result. + /// For receiving response headers, You need to implement the TQueryResult type from WebResult class instead of IQueryResult. + /// + /// + protected override IEnumerable GetResponseHeaders() + { + return new[] { "x-meta-branch-code" }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/WebApis/Endpoints.cs b/tests/Schemio.API.Tests/EntitySetup/WebApis/Endpoints.cs new file mode 100644 index 0000000..e59bf16 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/WebApis/Endpoints.cs @@ -0,0 +1,26 @@ +namespace Schemio.API.Tests.EntitySetup.WebApis +{ + public static class Endpoints + { + public const string Customer = "v2/clients/{0}"; + public const string Communication = "v2/clients/{0}/communication"; + public const string Orders = "v2/clients/{0}/orders"; + public const string OrderItems = "v2/clients/{0}/orders/items?$filter=orderId in {1}"; + + public const string BaseAddress = "http://localhost:5000/"; + + public static class Ids + { + public const int CustomerId = 1000; + public const int OrderId = 1234; + } + + public static class Response + { + public const string Customer = @"{""id"": 1000, ""code"": ""ABC-2244"",""name"": ""John McKinsy""}"; + public const string Communication = @"{ 'contactId': 4567, 'phone': '07675998878', 'email': 'John.McKinsy@gmail.com', 'postalAddress': { 'addressId': 3456, 'houseNo': '22', 'city': 'London', 'region': 'London', 'postalCode': 'W12 6GH', 'country': 'United Kingdom' } }"; + public const string Orders = @"{ 'orderId': 1234, 'orderNo': 'GHK-897GB', 'date': '2024-01-01T00:00:00' }"; + public const string OrderItems = @"[{ 'itemId': 2244, 'name': 'Pen', 'cost': 12.0 }, { 'itemId': 6677, 'name': 'Book', 'cost': 15.0 }]"; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/WebApis/OrderItemsWebQuery.cs b/tests/Schemio.API.Tests/EntitySetup/WebApis/OrderItemsWebQuery.cs new file mode 100644 index 0000000..9d68217 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/WebApis/OrderItemsWebQuery.cs @@ -0,0 +1,22 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; +using Schemio.Core.Helpers; + +namespace Schemio.API.Tests.EntitySetup.WebApis +{ + internal class OrderItemsWebQuery : WebQuery> + { + public OrderItemsWebQuery() : base(Endpoints.BaseAddress) + { + } + + protected override Func GetQuery(IDataContext context, IQueryResult parentApiResult) + { + // Execute as nested api to order parent api taking OrderResult to resolve api parameter. + var orders = (CollectionResult)parentApiResult; + var customerContext = (CustomerRequest)context.Request; + + return () => new Uri(string.Format(Endpoints.BaseAddress + Endpoints.OrderItems, customerContext.CustomerId, orders.Select(o => o.OrderId).ToCSV()), UriKind.Absolute); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/EntitySetup/WebApis/OrdersWebQuery.cs b/tests/Schemio.API.Tests/EntitySetup/WebApis/OrdersWebQuery.cs new file mode 100644 index 0000000..daa43b8 --- /dev/null +++ b/tests/Schemio.API.Tests/EntitySetup/WebApis/OrdersWebQuery.cs @@ -0,0 +1,20 @@ +using Schemio.API.Tests.EntitySetup.QueryResults; +using Schemio.Core; + +namespace Schemio.API.Tests.EntitySetup.WebApis +{ + internal class OrdersWebQuery : WebQuery> + { + public OrdersWebQuery() : base(Endpoints.BaseAddress) + { + } + + protected override Func GetQuery(IDataContext context, IQueryResult parentApiResult) + { + // Execute as child to customer api. + var customer = (CustomerResult)parentApiResult; + + return () => new Uri(string.Format(Endpoints.BaseAddress + Endpoints.Orders, customer.Id), UriKind.Absolute); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.API.Tests/Schemio.API.Tests.csproj b/tests/Schemio.API.Tests/Schemio.API.Tests.csproj new file mode 100644 index 0000000..efbeb65 --- /dev/null +++ b/tests/Schemio.API.Tests/Schemio.API.Tests.csproj @@ -0,0 +1,34 @@ + + + + net9.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/Schemio.Tests/DataProvider.Tests/DataProviderTests.cs b/tests/Schemio.Core.Tests/DataProvider.Tests/DataProviderTests.cs similarity index 68% rename from tests/Schemio.Tests/DataProvider.Tests/DataProviderTests.cs rename to tests/Schemio.Core.Tests/DataProvider.Tests/DataProviderTests.cs index b0830a1..e8ce3c8 100644 --- a/tests/Schemio.Tests/DataProvider.Tests/DataProviderTests.cs +++ b/tests/Schemio.Core.Tests/DataProvider.Tests/DataProviderTests.cs @@ -1,10 +1,10 @@ using Microsoft.Extensions.Logging; using Moq; -using Schemio.Impl; -using Schemio.Tests.EntitySetup; -using Schemio.Tests.EntitySetup.Entities; +using Schemio.Core.Impl; +using Schemio.Core.Tests.EntitySetup; +using Schemio.Core.Tests.EntitySetup.Entities; -namespace Schemio.Tests.DataProvider.Tests +namespace Schemio.Core.Tests.DataProvider.Tests { [TestFixture] internal class DataProviderTests @@ -13,7 +13,7 @@ internal class DataProviderTests private Mock>> _logger; private Mock> _queryBuilder; private Mock _queryExecutor; - private Mock> _transformExecutor; + private Mock> _entityBuilder; [SetUp] public void Setup() @@ -21,9 +21,9 @@ public void Setup() _logger = new Mock>>(); _queryBuilder = new Mock>(); _queryExecutor = new Mock(); - _transformExecutor = new Mock>(); + _entityBuilder = new Mock>(); - _provider = new DataProvider(_logger.Object, _queryBuilder.Object, _queryExecutor.Object, _transformExecutor.Object); + _provider = new DataProvider(_logger.Object, _queryBuilder.Object, _queryExecutor.Object, _entityBuilder.Object); } [Test] @@ -35,7 +35,7 @@ public void TestDataProvider() _queryBuilder.Verify(x => x.Build(It.IsAny()), Times.Once); _queryExecutor.Verify(x => x.Execute(It.IsAny(), It.IsAny()), Times.Once); - _transformExecutor.Verify(x => x.Execute(It.IsAny(), It.IsAny>()), Times.Once); + _entityBuilder.Verify(x => x.Build(It.IsAny(), It.IsAny>()), Times.Once); } } } \ No newline at end of file diff --git a/tests/Schemio.Tests/DataProvider.Tests/TransformExecutorTests.cs b/tests/Schemio.Core.Tests/DataProvider.Tests/EntityBuilderTests.cs similarity index 59% rename from tests/Schemio.Tests/DataProvider.Tests/TransformExecutorTests.cs rename to tests/Schemio.Core.Tests/DataProvider.Tests/EntityBuilderTests.cs index 507397d..14ae66b 100644 --- a/tests/Schemio.Tests/DataProvider.Tests/TransformExecutorTests.cs +++ b/tests/Schemio.Core.Tests/DataProvider.Tests/EntityBuilderTests.cs @@ -1,15 +1,14 @@ -using Schemio.Tests.EntitySetup; -using Schemio.Impl; -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; +using Schemio.Core.Impl; +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Entities; -namespace Schemio.Tests.DataProvider.Tests +namespace Schemio.Core.Tests.DataProvider.Tests { [TestFixture] - internal class TransformExecutorTests + internal class EntityBuilderTests { - private TransformExecutor _transformExecutor; - private IEntitySchema _entitySchema; + private EntityBuilder _entityBuilder; + private IEntityConfiguration _entitySchema; private static List<(Type result, int InvocationCount)> TransformerInvocations; @@ -17,7 +16,7 @@ internal class TransformExecutorTests public void Setup() { _entitySchema = new MockCustomerSchema(); - _transformExecutor = new TransformExecutor(_entitySchema); + _entityBuilder = new EntityBuilder(_entitySchema); TransformerInvocations = new List<(Type result, int InvocationCount)>(); } @@ -26,31 +25,31 @@ public void TestTransformExecutorForCorrectExecutionOfConfiguredTransforms() { var queryList = new List { - new CustomerResult{Id = 123, CustomerCode= "ABC", CustomerName="Ninja Labs"}, - new CommunicationResult{Id = 123, Email = "ninja@labs.com", Telephone = "0212345689"}, - new OrderCollectionResult(), - new OrderItemCollectionResult() + new CustomerRecord{Id = 123, CustomerCode= "ABC", CustomerName="Ninja Labs"}, + new CommunicationRecord{Id = 123, Email = "ninja@labs.com", Telephone = "0212345689"}, + new CollectionResult(), + new CollectionResult() }; - var entity = _transformExecutor.Execute(new DataContext(new EntityContext()), queryList); + var entity = _entityBuilder.Build(new DataContext(new EntityContext()), queryList); - var customerTransforms = TransformerInvocations.Where(x => x.result == typeof(CustomerResult)); + var customerTransforms = TransformerInvocations.Where(x => x.result == typeof(CustomerRecord)); Assert.That(customerTransforms.Count() == 1); Assert.That(customerTransforms.ElementAt(0).InvocationCount == 1); - var communicationTransforms = TransformerInvocations.Where(x => x.result == typeof(CommunicationResult)); + var communicationTransforms = TransformerInvocations.Where(x => x.result == typeof(CommunicationRecord)); Assert.That(communicationTransforms.Count() == 1); Assert.That(communicationTransforms.ElementAt(0).InvocationCount == 1); - var orderCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(OrderCollectionResult)); + var orderCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(CollectionResult)); Assert.That(orderCollectionTransforms.Count() == 1); Assert.That(orderCollectionTransforms.ElementAt(0).InvocationCount == 1); - var orderItemsCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(OrderItemCollectionResult)); + var orderItemsCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(CollectionResult)); Assert.That(orderItemsCollectionTransforms.Count() == 1); Assert.That(orderItemsCollectionTransforms.ElementAt(0).InvocationCount == 1); - Assert.IsNotNull(entity); + Assert.That(entity, Is.Not.Null); } public class MockTransform : BaseTransformer @@ -63,23 +62,23 @@ public override void Transform(TQueryResult queryResult, TEntity entity) } } - internal class MockCustomerSchema : BaseEntitySchema + internal class MockCustomerSchema : EntityConfiguration { public override IEnumerable> GetSchema() { return CreateSchema.For() - .Map>(For.Paths("customer/id", "customer/customercode", "customer/customername"), + .Map>(For.Paths("customer/id", "customer/customercode", "customer/customername"), customer => customer.Dependents - .Map>(For.Paths("customer/communication")) - .Map>(For.Paths("customer/orders"), + .Map>(For.Paths("customer/communication")) + .Map, Customer>>(For.Paths("customer/orders"), customerOrders => customerOrders.Dependents - .Map>(For.Paths("customer/orders/order/items"))) - ).Create(); + .Map, Customer>>(For.Paths("customer/orders/order/items"))) + ).End(); } } } - internal class EntityContext : IEntityContext + internal class EntityContext : IEntityRequest { public int CustomerId { get; set; } public string[] SchemaPaths { get; set; } diff --git a/tests/Schemio.Tests/DataProvider.Tests/QueryBuilderTests.cs b/tests/Schemio.Core.Tests/DataProvider.Tests/QueryBuilderTests.cs similarity index 79% rename from tests/Schemio.Tests/DataProvider.Tests/QueryBuilderTests.cs rename to tests/Schemio.Core.Tests/DataProvider.Tests/QueryBuilderTests.cs index c5db662..5f23466 100644 --- a/tests/Schemio.Tests/DataProvider.Tests/QueryBuilderTests.cs +++ b/tests/Schemio.Core.Tests/DataProvider.Tests/QueryBuilderTests.cs @@ -1,24 +1,24 @@ -using Schemio.Impl; -using Schemio.PathMatchers; -using Schemio.Tests.EntitySetup; -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.EntitySchemas; -using Schemio.Tests.EntitySetup.Queries; - -namespace Schemio.Tests.DataProvider.Tests +using Schemio.Core.Impl; +using Schemio.Core.PathMatchers; +using Schemio.Core.Tests.EntitySetup; +using Schemio.Core.Tests.EntitySetup.Entities; +using Schemio.Core.Tests.EntitySetup.Configuration; +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; + +namespace Schemio.Core.Tests.DataProvider.Tests { [TestFixture] internal class QueryBuilderTests { private QueryBuilder _queryBuilder; - private IEntitySchema _entitySchema; + private IEntityConfiguration _entitySchema; private ISchemaPathMatcher _schemaPathMatcher; [SetUp] public void Setup() { - _entitySchema = new CustomerSchema(); + _entitySchema = new CustomerConfiguration(); /*----------------------------------------- * * @@ -44,7 +44,7 @@ public void TestQueryBuilderForCorrectParentQueryList() var result = _queryBuilder.Build(context); - Assert.IsNotNull(result); + Assert.That(result, Is.Not.Null); // returns parent query with filtered out child communication query. @@ -63,7 +63,7 @@ public void TestQueryBuilderForCorrectParentQueryListWithOneChildren() var result = _queryBuilder.Build(context); - Assert.IsNotNull(result); + Assert.That(result, Is.Not.Null); // returns parent query with filtered out child communication query. @@ -75,7 +75,7 @@ public void TestQueryBuilderForCorrectParentQueryListWithOneChildren() Assert.That(parentQuery.GetType() == typeof(CustomerQuery)); var childQuery = parentQuery.Children.First(); - Assert.That(childQuery.GetType() == typeof(CustomerCommunicationQuery)); + Assert.That(childQuery.GetType() == typeof(CommunicationQuery)); } [Test] @@ -85,7 +85,7 @@ public void TestQueryBuilderForCorrectParentQueryListWithTwoChildren() var result = _queryBuilder.Build(context); - Assert.IsNotNull(result); + Assert.That(result, Is.Not.Null); // returns parent query with filtered out children - communication & orders query. @@ -96,11 +96,11 @@ public void TestQueryBuilderForCorrectParentQueryListWithTwoChildren() var parentQuery = result.Queries.First(); Assert.That(parentQuery.GetType() == typeof(CustomerQuery)); - var communicationChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CustomerCommunicationQuery)); - var ordersChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CustomerOrdersQuery)); + var communicationChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CommunicationQuery)); + var ordersChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(OrdersQuery)); - Assert.IsNotNull(communicationChildQuery); - Assert.IsNotNull(ordersChildQuery); + Assert.That(communicationChildQuery, Is.Not.Null); + Assert.That(ordersChildQuery, Is.Not.Null); // nested child query for order item not included as order items are excluded from paths Assert.That(ordersChildQuery.Children.Count, Is.EqualTo(0)); @@ -113,7 +113,7 @@ public void TestQueryBuilderForCorrectParentQueryListWithTwoChildrenAndOneChildF var result = _queryBuilder.Build(context); - Assert.IsNotNull(result); + Assert.That(result, Is.Not.Null); // returns parent query with filtered out children - communication & orders query. @@ -124,17 +124,17 @@ public void TestQueryBuilderForCorrectParentQueryListWithTwoChildrenAndOneChildF var parentQuery = result.Queries.First(); Assert.That(parentQuery.GetType() == typeof(CustomerQuery)); - var communicationChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CustomerCommunicationQuery)); - var ordersChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CustomerOrdersQuery)); + var communicationChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CommunicationQuery)); + var ordersChildQuery = parentQuery.Children.FirstOrDefault(x => x.GetType() == typeof(OrdersQuery)); - Assert.IsNotNull(communicationChildQuery); - Assert.IsNotNull(ordersChildQuery); + Assert.That(communicationChildQuery, Is.Not.Null); + Assert.That(ordersChildQuery, Is.Not.Null); // nested child query for order item in order query children as order items are included in paths Assert.That(ordersChildQuery.Children.Count, Is.EqualTo(1)); - var orderItemsChildQuery = ordersChildQuery.Children.FirstOrDefault(x => x.GetType() == typeof(CustomerOrderItemsQuery)); - Assert.IsNotNull(orderItemsChildQuery); + var orderItemsChildQuery = ordersChildQuery.Children.FirstOrDefault(x => x.GetType() == typeof(OrderItemsQuery)); + Assert.That(orderItemsChildQuery, Is.Not.Null); } } } \ No newline at end of file diff --git a/tests/Schemio.Tests/DataProvider.Tests/QueryExecutorTests.cs b/tests/Schemio.Core.Tests/DataProvider.Tests/QueryExecutorTests.cs similarity index 67% rename from tests/Schemio.Tests/DataProvider.Tests/QueryExecutorTests.cs rename to tests/Schemio.Core.Tests/DataProvider.Tests/QueryExecutorTests.cs index ede3cd3..6d40035 100644 --- a/tests/Schemio.Tests/DataProvider.Tests/QueryExecutorTests.cs +++ b/tests/Schemio.Core.Tests/DataProvider.Tests/QueryExecutorTests.cs @@ -1,12 +1,12 @@ using Moq; -using Schemio.Impl; -using Schemio.PathMatchers; -using Schemio.Tests.EntitySetup; -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.EntitySchemas; -using Schemio.Tests.EntitySetup.Queries; - -namespace Schemio.Tests.DataProvider.Tests +using Schemio.Core.Impl; +using Schemio.Core.PathMatchers; +using Schemio.Core.Tests.EntitySetup; +using Schemio.Core.Tests.EntitySetup.Entities; +using Schemio.Core.Tests.EntitySetup.Configuration; +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; + +namespace Schemio.Core.Tests.DataProvider.Tests { [TestFixture] internal class QueryExecutorTests @@ -28,7 +28,7 @@ public void TestQueryExecutorToReturnWhenNoQueries() { _queryExecutor.Execute(new DataContext(new EntityContext()), new QueryList()); - _queryEngine.Verify(x => x.Execute(It.IsAny>()), Times.Never()); + _queryEngine.Verify(x => x.Execute(It.IsAny()), Times.Never()); } [Test] @@ -36,18 +36,18 @@ public void TestQueryExecutorToCallEngineWhenQueriesExistForExecution() { _queryExecutor.Execute(new DataContext(new EntityContext()), new QueryList(new[] { new CustomerQuery() }) { }); - _queryEngine.Verify(x => x.Execute(It.IsAny>()), Times.Once()); + _queryEngine.Verify(x => x.Execute(It.IsAny()), Times.Once()); } [Test] // TODO - All sequence assertions public void TestQueryExecutorToExecuteConfiguredQueriesInCorrectOrder() { - var querList = new QueryBuilder(new CustomerSchema(), new XPathMatcher()) + var querList = new QueryBuilder(new CustomerConfiguration(), new XPathMatcher()) .Build(new DataContext(new CustomerContext())); _queryExecutor.Execute(new DataContext(new EntityContext()), querList); - _queryEngine.Verify(x => x.Execute(It.IsAny>()), Times.Once()); + _queryEngine.Verify(x => x.Execute(It.IsAny()), Times.Once()); } } } \ No newline at end of file diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/CustomerConfiguration.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/CustomerConfiguration.cs new file mode 100644 index 0000000..be8d432 --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/CustomerConfiguration.cs @@ -0,0 +1,21 @@ +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Configuration.Transforms; +using Schemio.Core.Tests.EntitySetup.Entities; + +namespace Schemio.Core.Tests.EntitySetup.Configuration +{ + internal class CustomerConfiguration : EntityConfiguration + { + public override IEnumerable> GetSchema() + { + return CreateSchema.For() + .Map(For.Paths("customer/id", "customer/customercode", "customer/customername"), + customer => customer.Dependents + .Map(For.Paths("customer/communication")) + .Map(For.Paths("customer/orders"), + customerOrders => customerOrders.Dependents + .Map(For.Paths("customer/orders/order/items"))) + ).End(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationQuery.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationQuery.cs new file mode 100644 index 0000000..3136b90 --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationQuery.cs @@ -0,0 +1,19 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + internal class CommunicationQuery : BaseQuery + { + private object QueryParameter; + + public override bool IsContextResolved() => QueryParameter != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerRecord)parentQueryResult; + QueryParameter = new + { + CustomerId = customer.Id + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CommunicationResult.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationRecord.cs similarity index 75% rename from tests/Schemio.Tests/EntitySetup/Queries/CommunicationResult.cs rename to tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationRecord.cs index 9b01aed..fe1274d 100644 --- a/tests/Schemio.Tests/EntitySetup/Queries/CommunicationResult.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CommunicationRecord.cs @@ -1,6 +1,6 @@ -namespace Schemio.Tests.EntitySetup.Queries +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries { - public class CommunicationResult : IQueryResult + public class CommunicationRecord : IQueryResult { public int Id { get; set; } public string Telephone { get; set; } diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerQuery.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerQuery.cs new file mode 100644 index 0000000..003c58c --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerQuery.cs @@ -0,0 +1,19 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + public class CustomerQuery : BaseQuery + { + private object QueryParameter; + + public override bool IsContextResolved() => QueryParameter != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Executes as root or level 1 query. + var customer = (CustomerContext)context.Request; + QueryParameter = new + { + customer.CustomerId + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerResult.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerRecord.cs similarity index 57% rename from tests/Schemio.Tests/EntitySetup/Queries/CustomerResult.cs rename to tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerRecord.cs index 3a59062..b56ec89 100644 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerResult.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/CustomerRecord.cs @@ -1,6 +1,6 @@ -namespace Schemio.Tests.EntitySetup.Queries +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries { - public class CustomerResult : IQueryResult + public class CustomerRecord : IQueryResult { public int Id { get; set; } public string CustomerCode { get; set; } diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemRecord.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemRecord.cs new file mode 100644 index 0000000..c7621c3 --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemRecord.cs @@ -0,0 +1,8 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + public class OrderItemRecord + { + public int OrderId { get; set; } + public (int ItemId, string Name, decimal Cost)[] Items { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemsQuery.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemsQuery.cs new file mode 100644 index 0000000..3e9d021 --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderItemsQuery.cs @@ -0,0 +1,19 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + internal class OrderItemsQuery : BaseQuery> + { + private object QueryParameter; + + public override bool IsContextResolved() => QueryParameter != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to order query. + var ordersResult = (CollectionResult)parentQueryResult; + QueryParameter = new + { + OrderIds = new List(ordersResult.Select(x => x.OrderId)) + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderRecord.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderRecord.cs new file mode 100644 index 0000000..21811ed --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrderRecord.cs @@ -0,0 +1,9 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + public class OrderRecord + { + public int OrderId { get; set; } + public string OrderNo { get; set; } + public DateTime Date { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrdersQuery.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrdersQuery.cs new file mode 100644 index 0000000..9acd1bc --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Queries/OrdersQuery.cs @@ -0,0 +1,19 @@ +namespace Schemio.Core.Tests.EntitySetup.Configuration.Queries +{ + internal class OrdersQuery : BaseQuery> + { + private object QueryParameter; + + public override bool IsContextResolved() => QueryParameter != null; + + public override void ResolveQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Does not execute as child to any query. + var customer = (CustomerRecord)parentQueryResult; + QueryParameter = new + { + CustomerId = customer.Id + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CommunicationTransform.cs similarity index 69% rename from tests/Schemio.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs rename to tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CommunicationTransform.cs index 8b11ff7..c608aec 100644 --- a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CommunicationTransform.cs @@ -1,11 +1,11 @@ -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Entities; -namespace Schemio.Tests.EntitySetup.Transforms +namespace Schemio.Core.Tests.EntitySetup.Configuration.Transforms { - public class CustomerCommunicationTransform : BaseTransformer + public class CommunicationTransform : BaseTransformer { - public override void Transform(CommunicationResult queryResult, Customer entity) + public override void Transform(CommunicationRecord queryResult, Customer entity) { var customer = entity ?? new Customer(); customer.Communication = new Communication diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CustomerTransform.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CustomerTransform.cs new file mode 100644 index 0000000..83d982e --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/CustomerTransform.cs @@ -0,0 +1,16 @@ +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Entities; + +namespace Schemio.Core.Tests.EntitySetup.Configuration.Transforms +{ + public class CustomerTransform : BaseTransformer + { + public override void Transform(CustomerRecord queryResult, Customer entity) + { + var customer = entity ?? new Customer(); + customer.Id = queryResult.Id; + customer.Name = queryResult.CustomerName; + customer.Code = queryResult.CustomerCode; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrderItemsTransform.cs similarity index 52% rename from tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs rename to tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrderItemsTransform.cs index d784624..3fa722c 100644 --- a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrderItemsTransform.cs @@ -1,16 +1,16 @@ -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Entities; -namespace Schemio.Tests.EntitySetup.Transforms +namespace Schemio.Core.Tests.EntitySetup.Configuration.Transforms { - public class CustomerOrderItemsTransform : BaseTransformer, Customer> + public class OrderItemsTransform : BaseTransformer, Customer> { - public override void Transform(CollectionResult queryResult, Customer entity) + public override void Transform(CollectionResult queryResult, Customer entity) { - if (queryResult?.Items == null || entity?.Orders == null) + if (queryResult == null || entity?.Orders == null) return; - foreach (var item in queryResult.Items.Where(x => x.Items != null)) + foreach (var item in queryResult.Where(x => x.Items != null)) foreach (var order in entity.Orders) if (order.OrderId == item.OrderId) order.Items = item.Items.Select(x => new OrderItem diff --git a/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrdersTransform.cs b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrdersTransform.cs new file mode 100644 index 0000000..dc0c536 --- /dev/null +++ b/tests/Schemio.Core.Tests/EntitySetup/Configuration/Transforms/OrdersTransform.cs @@ -0,0 +1,22 @@ +using Schemio.Core.Tests.EntitySetup.Configuration.Queries; +using Schemio.Core.Tests.EntitySetup.Entities; + +namespace Schemio.Core.Tests.EntitySetup.Configuration.Transforms +{ + public class OrdersTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult queryResult, Customer entity) + { + if (queryResult == null) + return; + + var customer = entity ?? new Customer(); + customer.Orders = queryResult.Select(x => new Order + { + Date = x.Date, + OrderId = x.OrderId, + OrderNo = x.OrderNo + }).ToArray(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/CustomerContext.cs b/tests/Schemio.Core.Tests/EntitySetup/CustomerContext.cs similarity index 53% rename from tests/Schemio.Tests/EntitySetup/CustomerContext.cs rename to tests/Schemio.Core.Tests/EntitySetup/CustomerContext.cs index 65fdaba..17dcab3 100644 --- a/tests/Schemio.Tests/EntitySetup/CustomerContext.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/CustomerContext.cs @@ -1,6 +1,6 @@ -namespace Schemio.Tests.EntitySetup +namespace Schemio.Core.Tests.EntitySetup { - internal class CustomerContext : IEntityContext + internal class CustomerContext : IEntityRequest { public int CustomerId { get; set; } public string[] SchemaPaths { get; set; } diff --git a/tests/Schemio.Tests/EntitySetup/Entities/Address.cs b/tests/Schemio.Core.Tests/EntitySetup/Entities/Address.cs similarity index 85% rename from tests/Schemio.Tests/EntitySetup/Entities/Address.cs rename to tests/Schemio.Core.Tests/EntitySetup/Entities/Address.cs index 76d5b62..db8a8c9 100644 --- a/tests/Schemio.Tests/EntitySetup/Entities/Address.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Entities/Address.cs @@ -1,4 +1,4 @@ -namespace Schemio.Tests.EntitySetup.Entities +namespace Schemio.Core.Tests.EntitySetup.Entities { public class Address { diff --git a/tests/Schemio.Tests/EntitySetup/Entities/Communication.cs b/tests/Schemio.Core.Tests/EntitySetup/Entities/Communication.cs similarity index 81% rename from tests/Schemio.Tests/EntitySetup/Entities/Communication.cs rename to tests/Schemio.Core.Tests/EntitySetup/Entities/Communication.cs index 16dd320..68a39d9 100644 --- a/tests/Schemio.Tests/EntitySetup/Entities/Communication.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Entities/Communication.cs @@ -1,4 +1,4 @@ -namespace Schemio.Tests.EntitySetup.Entities +namespace Schemio.Core.Tests.EntitySetup.Entities { public class Communication { diff --git a/tests/Schemio.Tests/EntitySetup/Entities/Customer.cs b/tests/Schemio.Core.Tests/EntitySetup/Entities/Customer.cs similarity index 84% rename from tests/Schemio.Tests/EntitySetup/Entities/Customer.cs rename to tests/Schemio.Core.Tests/EntitySetup/Entities/Customer.cs index 00c37b9..66b751f 100644 --- a/tests/Schemio.Tests/EntitySetup/Entities/Customer.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Entities/Customer.cs @@ -1,4 +1,4 @@ -namespace Schemio.Tests.EntitySetup.Entities +namespace Schemio.Core.Tests.EntitySetup.Entities { public class Customer : IEntity { diff --git a/tests/Schemio.Tests/EntitySetup/Entities/EntityDiagram.cd b/tests/Schemio.Core.Tests/EntitySetup/Entities/EntityDiagram.cd similarity index 100% rename from tests/Schemio.Tests/EntitySetup/Entities/EntityDiagram.cd rename to tests/Schemio.Core.Tests/EntitySetup/Entities/EntityDiagram.cd diff --git a/tests/Schemio.Tests/EntitySetup/Entities/Order.cs b/tests/Schemio.Core.Tests/EntitySetup/Entities/Order.cs similarity index 80% rename from tests/Schemio.Tests/EntitySetup/Entities/Order.cs rename to tests/Schemio.Core.Tests/EntitySetup/Entities/Order.cs index 7daffaf..9f3a70e 100644 --- a/tests/Schemio.Tests/EntitySetup/Entities/Order.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Entities/Order.cs @@ -1,4 +1,4 @@ -namespace Schemio.Tests.EntitySetup.Entities +namespace Schemio.Core.Tests.EntitySetup.Entities { public class Order { diff --git a/tests/Schemio.Tests/EntitySetup/Entities/OrderItem.cs b/tests/Schemio.Core.Tests/EntitySetup/Entities/OrderItem.cs similarity index 76% rename from tests/Schemio.Tests/EntitySetup/Entities/OrderItem.cs rename to tests/Schemio.Core.Tests/EntitySetup/Entities/OrderItem.cs index aaad668..99616a1 100644 --- a/tests/Schemio.Tests/EntitySetup/Entities/OrderItem.cs +++ b/tests/Schemio.Core.Tests/EntitySetup/Entities/OrderItem.cs @@ -1,4 +1,4 @@ -namespace Schemio.Tests.EntitySetup.Entities +namespace Schemio.Core.Tests.EntitySetup.Entities { public class OrderItem { diff --git a/tests/Schemio.Core.Tests/Schemio.Core.Tests.csproj b/tests/Schemio.Core.Tests/Schemio.Core.Tests.csproj new file mode 100644 index 0000000..efd5bc7 --- /dev/null +++ b/tests/Schemio.Core.Tests/Schemio.Core.Tests.csproj @@ -0,0 +1,33 @@ + + + + net9.0 + enable + enable + + false + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/tests/Schemio.Tests/Usings.cs b/tests/Schemio.Core.Tests/Usings.cs similarity index 100% rename from tests/Schemio.Tests/Usings.cs rename to tests/Schemio.Core.Tests/Usings.cs diff --git a/tests/Schemio.EntityFramework.Tests/BaseTest.cs b/tests/Schemio.EntityFramework.Tests/BaseTest.cs index 5f852bc..c44f222 100644 --- a/tests/Schemio.EntityFramework.Tests/BaseTest.cs +++ b/tests/Schemio.EntityFramework.Tests/BaseTest.cs @@ -1,6 +1,9 @@ +using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Schemio.Core; +using Schemio.Core.Helpers; +using Schemio.Core.PathMatchers; using Schemio.EntityFramework.Tests.EntitySetup.Entities; using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas; @@ -10,6 +13,20 @@ public class BaseTest { protected ServiceProvider _serviceProvider; + protected void AssertAreEqual(Customer expected, Customer actual) + { + var actualCustomer = actual.ToJson(); + var expectedCustomer = expected.ToJson(); + + Console.WriteLine("expected:"); + Console.WriteLine(expectedCustomer); + + Console.WriteLine("actual:"); + Console.WriteLine(actualCustomer); + + Assert.That(actualCustomer, Is.EqualTo(expectedCustomer)); + } + [OneTimeSetUp] public void Setup() { @@ -22,9 +39,10 @@ public void Setup() services.AddLogging(); - services.UseSchemio(With.Schema(c => new CustomerSchema()) - .AddEngine(c => new QueryEngine(c.GetService>())) - .LogWith(c => new Logger>(c.GetService()))); + services.UseSchemio() + .WithEngine(c => new QueryEngine(c.GetService>())) + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); // 4. Build the service provider _serviceProvider = services.BuildServiceProvider(); @@ -33,7 +51,8 @@ public void Setup() [OneTimeTearDown] public void TearDown() { - _serviceProvider = null; + if (_serviceProvider is IDisposable disposable) + disposable.Dispose(); } } } \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/Customer.db b/tests/Schemio.EntityFramework.Tests/Customer.db index 59c0139..82716c1 100644 Binary files a/tests/Schemio.EntityFramework.Tests/Customer.db and b/tests/Schemio.EntityFramework.Tests/Customer.db differ diff --git a/tests/Schemio.EntityFramework.Tests/CustomerDbContext.cs b/tests/Schemio.EntityFramework.Tests/CustomerDbContext.cs index ac5456b..225b9c7 100644 --- a/tests/Schemio.EntityFramework.Tests/CustomerDbContext.cs +++ b/tests/Schemio.EntityFramework.Tests/CustomerDbContext.cs @@ -1,7 +1,7 @@ using System.Globalization; using Microsoft.EntityFrameworkCore; +using Schemio.Core.Helpers; using Schemio.EntityFramework.Tests.Domain; -using Schemio.Helpers; namespace Schemio.EntityFramework.Tests { @@ -55,7 +55,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.HasKey(b => b.OrderId); eb.Property(b => b.OrderNo); eb.Property(b => b.Date).HasColumnName("OrderDate") - .HasConversion(v => v.ToShortDateString(), s => s.IsNotNullOrEmpty() ? DateTime.ParseExact(s, "dd/MM/yyyy", null) : DateTime.MinValue); + .HasConversion(v => v.ToShortDateString(), s => s.IsNotNullOrEmpty() ? DateTime.ParseExact(s, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture) : DateTime.MinValue); eb.HasOne(b => b.Customer); eb.HasMany(b => b.Items); }); diff --git a/tests/Schemio.EntityFramework.Tests/E2E.Tests.cs b/tests/Schemio.EntityFramework.Tests/E2E.Tests.cs index a814253..da03464 100644 --- a/tests/Schemio.EntityFramework.Tests/E2E.Tests.cs +++ b/tests/Schemio.EntityFramework.Tests/E2E.Tests.cs @@ -1,9 +1,11 @@ using Microsoft.Extensions.DependencyInjection; +using Schemio.Core; using Schemio.EntityFramework.Tests.EntitySetup; using Schemio.EntityFramework.Tests.EntitySetup.Entities; namespace Schemio.EntityFramework.Tests { + [TestFixture] public class E2ETests : BaseTest { private IDataProvider _provider; @@ -17,26 +19,120 @@ public void Setup() [Test] public void TestDataProviderToFetchWholeEntityWhenPathsAreNull() { - var customer = _provider.GetData(new CustomerContext + var customer = _provider.GetData(new CustomerRequest { CustomerId = 1 }); - Assert.IsNotNull(customer); - Assert.That("{\"Id\":1,\"Code\":\"AB123\",\"Name\":\"Jack Sparrow\",\"Communication\":{\"ContactId\":1,\"Phone\":\"0123456789\",\"Email\":\"jack.sparrow@schemio.com\",\"Address\":{\"AddressId\":1,\"HouseNo\":\"77\",\"City\":\"Wansted\",\"Region\":\"Belfast\",\"PostalCode\":\"BL34Y56\",\"Country\":\"United Kingdom\"}},\"Orders\":[{\"OrderId\":1,\"OrderNo\":\"ZX123VH\",\"Date\":\"2021-10-22T00:00:00\",\"Items\":[{\"ItemId\":1,\"Name\":\"12 inch Cake\",\"Cost\":30},{\"ItemId\":2,\"Name\":\"20 Cake Candles\",\"Cost\":5}]}]}", Is.EqualTo(customer.ToJson())); + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Communication = new Communication + { + ContactId = 1, + Phone = "0123456789", + Email = "jack.sparrow@gmail.com", + Address = new Address + { + AddressId = 1, + HouseNo = "77", + City = "Wansted", + Region = "Belfast", + PostalCode = "BL34Y56", + Country = "United Kingdom", + } + }, + Orders = [ new Order { + OrderId = 1, + OrderNo = "ZX123VH", + Date = DateTime.Parse("2021-10-22T12:13:04"), + Items = + [ + new OrderItem + { + ItemId = 1, Name = "12 inch Cake", Cost = 30m + }, + new OrderItem + { + ItemId = 2, Name = "20 Cake Candles", Cost = 5m + } + ] + }] + }; + + AssertAreEqual(expected, customer); + } + + [Test] + public void TestDataProviderToFetchEntityWhenPathsContainsCommunication() + { + var customer = _provider.GetData(new CustomerRequest + { + CustomerId = 1, + SchemaPaths = new[] { "Customer/Communication" } + }); + + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Communication = new Communication + { + ContactId = 1, + Phone = "0123456789", + Email = "jack.sparrow@gmail.com", + Address = new Address + { + AddressId = 1, + HouseNo = "77", + City = "Wansted", + Region = "Belfast", + PostalCode = "BL34Y56", + Country = "United Kingdom", + } + } + }; + + AssertAreEqual(expected, customer); } [Test] - public void TestDataProviderToFetchEntityWhenPathsNotNull() + public void TestDataProviderToFetchEntityWhenPathsContainsOrderItems() { - var customer = _provider.GetData(new CustomerContext + var customer = _provider.GetData(new CustomerRequest { CustomerId = 1, SchemaPaths = new[] { "Customer/orders/order/items/item" } }); - Assert.IsNotNull(customer); - Assert.AreEqual(customer.ToJson(), "{\"Id\":1,\"Code\":\"AB123\",\"Name\":\"Jack Sparrow\",\"Communication\":null,\"Orders\":[{\"OrderId\":1,\"OrderNo\":\"ZX123VH\",\"Date\":\"2021-10-22T00:00:00\",\"Items\":[{\"ItemId\":1,\"Name\":\"12 inch Cake\",\"Cost\":30},{\"ItemId\":2,\"Name\":\"20 Cake Candles\",\"Cost\":5}]}]}"); + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Orders = [ new Order + { + OrderId = 1, + OrderNo = "ZX123VH", + Date = DateTime.Parse("2021-10-22T12:13:04"), + Items = + [ + new OrderItem + { + ItemId = 1, Name = "12 inch Cake", Cost = 30m + }, + new OrderItem + { + ItemId = 2, Name = "20 Cake Candles", Cost = 5m + } + ] + }] + }; + + AssertAreEqual(expected, customer); } } } \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerContext.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerRequest.cs similarity index 68% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerContext.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerRequest.cs index 00b33f5..e33b242 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerContext.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/CustomerRequest.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup { - internal class CustomerContext : IEntityContext + internal class CustomerRequest : IEntityRequest { public int CustomerId { get; set; } public string[] SchemaPaths { get; set; } diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/Entities/Customer.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/Entities/Customer.cs index 8f3c8d7..4cce91d 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/Entities/Customer.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/Entities/Customer.cs @@ -1,3 +1,5 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup.Entities { public class Customer : IEntity diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerConfiguration.cs similarity index 58% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerConfiguration.cs index 5e030f2..d3769bc 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/CustomerConfiguration.cs @@ -1,21 +1,22 @@ +using Schemio.Core; using Schemio.EntityFramework.Tests.EntitySetup.Entities; using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms; namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas { - internal class CustomerSchema : BaseEntitySchema + internal class CustomerConfiguration : EntityConfiguration { public override IEnumerable> GetSchema() { return CreateSchema.For() .Map(For.Paths("customer"), customer => customer.Dependents - .Map(For.Paths("customer/communication")) - .Map(For.Paths("customer/orders"), + .Map(For.Paths("customer/communication")) + .Map(For.Paths("customer/orders"), customerOrders => customerOrders.Dependents - .Map(For.Paths("customer/orders/order/items"))) - ).Create(); + .Map(For.Paths("customer/orders/order/items"))) + ).End(); } } } \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationQuery.cs new file mode 100644 index 0000000..914b098 --- /dev/null +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationQuery.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Schemio.Core; +using Schemio.EntityFramework.Tests.Domain; + +namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class CommunicationQuery : SQLQuery + { + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + var customer = (CustomerRecord)parentQueryResult; + + return async dbContext => await dbContext.Set() + .Where(p => p.Customer.Id == customer.Id) + .Select(c => new CommunicationRecord + { + Id = c.CommunicationId, + AddressId = c.Address.AddressId, + Telephone = c.Phone, + Email = c.Email, + HouseNo = c.Address.HouseNo, + City = c.Address.City, + Region = c.Address.Region, + PostalCode = c.Address.PostalCode, + Country = c.Address.Country + }) + .FirstOrDefaultAsync(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationRecord.cs similarity index 87% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationRecord.cs index 815f7f4..29a8b01 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationResult.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CommunicationRecord.cs @@ -1,7 +1,9 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries { [CacheResult] - public class CommunicationResult : IQueryResult + public class CommunicationRecord : IQueryResult { public int Id { get; set; } public string Telephone { get; set; } diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs deleted file mode 100644 index b7fe417..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Schemio.EntityFramework.Tests.Domain; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerCommunicationQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to customer query. - var customer = (CustomerResult)parentQueryResult; - QueryParameter = new CustomerParameter - { - CustomerId = customer.Id - }; - } - - public override IEnumerable Run(DbContext dbContext) - { - return dbContext.Set() - .Where(p => p.Customer.Id == QueryParameter.CustomerId) - .Select(c => new CommunicationResult - { - Id = c.CommunicationId, - AddressId = c.Address.AddressId, - Telephone = c.Phone, - Email = c.Email, - HouseNo = c.Address.HouseNo, - City = c.Address.City, - Region = c.Address.Region, - PostalCode = c.Address.PostalCode, - Country = c.Address.Country - }); - ; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs deleted file mode 100644 index ed5799d..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Schemio.EntityFramework.Tests.Domain; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerOrderItemsQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to order query. - var ordersResult = (CustomerOrderResult)parentQueryResult; - - QueryParameter ??= new OrderItemParameter(); - QueryParameter.OrderIds.Add(ordersResult.OrderId); - } - - public override IEnumerable Run(DbContext dbContext) - { - return dbContext.Set() - .Where(p => QueryParameter.OrderIds.Contains(p.Order.OrderId)) - .Select(c => new OrderItemResult - { - ItemId = c.ItemId, - Name = c.Name, - Cost = c.Cost, - OrderId = c.Order.OrderId - }); - ; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs deleted file mode 100644 index 23383c3..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Schemio.EntityFramework.Tests.Domain; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerOrdersQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to customer query. - var customer = (CustomerResult)parentQueryResult; - QueryParameter = new CustomerParameter - { - CustomerId = customer.Id - }; - } - - public override IEnumerable Run(DbContext dbContext) - { - return dbContext.Set() - .Where(p => p.Customer.Id == QueryParameter.CustomerId) - .Select(c => new CustomerOrderResult - { - CustomerId = c.CustomerId, - OrderId = c.OrderId, - Date = c.Date, - OrderNo = c.OrderNo - }); - ; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs deleted file mode 100644 index a36544f..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries -{ - public class CustomerParameter : IQueryParameter - { - public int CustomerId { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs index 2540252..4e8daa8 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs @@ -1,30 +1,30 @@ using Microsoft.EntityFrameworkCore; +using Schemio.Core; using Schemio.EntityFramework.Tests.Domain; namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries { - public class CustomerQuery : BaseSQLRootQuery + public class CustomerQuery : SQLQuery { - public override void ResolveRootQueryParameter(IDataContext context) + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) { - // Executes as root or level 1 query. - var customer = (CustomerContext)context.Entity; - QueryParameter = new CustomerParameter - { - CustomerId = customer.CustomerId - }; - } + // Executes as root or level 1 query. parentQueryResult will be null. + var customer = (CustomerRequest)context.Request; - public override IEnumerable Run(DbContext dbContext) - { - return dbContext.Set() - .Where(c => c.Id == QueryParameter.CustomerId) - .Select(c => new CustomerResult + return async dbContext => + { + var result = await dbContext.Set() + .Where(c => c.Id == customer.CustomerId) + .Select(c => new CustomerRecord { Id = c.Id, Name = c.Name, Code = c.Code - }); + }) + .FirstOrDefaultAsync(); + + return result; + }; } } } \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerRecord.cs similarity index 75% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerRecord.cs index 34df022..ae7f175 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerRecord.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries { - public class CustomerResult : IQueryResult + public class CustomerRecord : IQueryResult { public int Id { get; set; } public string Code { get; set; } diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs deleted file mode 100644 index 76f4cbd..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Schemio.Helpers; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class OrderItemParameter : IQueryParameter - { - public OrderItemParameter() - { - OrderIds = new List(); - } - - public string ToCsv() - { - return OrderIds.ToCSV(); - } - - public List OrderIds { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemRecord.cs similarity index 78% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemRecord.cs index f4c2bbe..bb1c216 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemRecord.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries { - public class OrderItemResult : IQueryResult + public class OrderItemRecord : IQueryResult { public int OrderId { get; set; } public int ItemId { get; set; } diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemsQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemsQuery.cs new file mode 100644 index 0000000..6c306ec --- /dev/null +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderItemsQuery.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Schemio.Core; +using Schemio.EntityFramework.Tests.Domain; + +namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class OrderItemsQuery : SQLQuery> + { + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to order query. + var ordersResults = (CollectionResult)parentQueryResult; + + return async dbContext => + { + var items = await dbContext.Set() + .Where(p => ordersResults.Select(o => o.OrderId).Contains(p.Order.OrderId)) + .Select(c => new OrderItemRecord + { + ItemId = c.ItemId, + Name = c.Name, + Cost = c.Cost, + OrderId = c.Order.OrderId + }) + .ToListAsync(); + + return new CollectionResult(items); + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderResult.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderRecord.cs similarity index 80% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderResult.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderRecord.cs index ae22cdb..2892312 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderResult.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrderRecord.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries { - public class CustomerOrderResult : IQueryResult + public class OrderRecord : IQueryResult { public int CustomerId { get; set; } public int OrderId { get; set; } diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrdersQuery.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrdersQuery.cs new file mode 100644 index 0000000..a16eae5 --- /dev/null +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Queries/OrdersQuery.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Schemio.Core; +using Schemio.EntityFramework.Tests.Domain; + +namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class OrdersQuery : SQLQuery> + { + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerRecord)parentQueryResult; + + return async dbContext => + { + var items = await dbContext.Set() + .Where(p => p.Customer.Id == customer.Id) + .Select(c => new OrderRecord + { + CustomerId = c.CustomerId, + OrderId = c.OrderId, + Date = c.Date, + OrderNo = c.OrderNo + }) + .ToListAsync(); + + return new CollectionResult(items); + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CommunicationTransform.cs similarity index 85% rename from tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs rename to tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CommunicationTransform.cs index 5eae75d..bd52373 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CommunicationTransform.cs @@ -1,11 +1,12 @@ +using Schemio.Core; using Schemio.EntityFramework.Tests.EntitySetup.Entities; using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms { - public class CustomerCommunicationTransform : BaseTransformer + public class CommunicationTransform : BaseTransformer { - public override void Transform(CommunicationResult queryResult, Customer entity) + public override void Transform(CommunicationRecord queryResult, Customer entity) { var customer = entity ?? new Customer(); customer.Communication = new Communication diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs deleted file mode 100644 index 091f021..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Schemio.EntityFramework.Tests.EntitySetup.Entities; -using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; -using Schemio.Helpers; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms -{ - public class CustomerOrderItemsTransform : BaseTransformer - { - public override void Transform(OrderItemResult queryResult, Customer entity) - { - if (queryResult == null || entity?.Orders == null) - return; - - foreach (var order in entity.Orders) - if (order.OrderId == queryResult.OrderId) - { - order.Items = ArrayUtil.EnsureAndResizeArray(order.Items, out var index); - order.Items[index] = new OrderItem - { - ItemId = queryResult.ItemId, - Name = queryResult.Name, - Cost = queryResult.Cost - }; - } - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs deleted file mode 100644 index d46456d..0000000 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Schemio.EntityFramework.Tests.EntitySetup.Entities; -using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; -using Schemio.Helpers; - -namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms -{ - public class CustomerOrdersTransform : BaseTransformer - { - public override void Transform(CustomerOrderResult queryResult, Customer entity) - { - if (queryResult == null) - return; - - var customer = entity ?? new Customer(); - - if (customer.Id != queryResult.CustomerId) - return; - - customer.Orders = ArrayUtil.EnsureAndResizeArray(customer.Orders, out var index); - - customer.Orders[index] = new Order - { - Date = queryResult.Date, - OrderId = queryResult.OrderId, - OrderNo = queryResult.OrderNo - }; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs index e970e19..ae467a0 100644 --- a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs @@ -1,11 +1,12 @@ +using Schemio.Core; using Schemio.EntityFramework.Tests.EntitySetup.Entities; using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms { - public class CustomerTransform : BaseTransformer + public class CustomerTransform : BaseTransformer { - public override void Transform(CustomerResult queryResult, Customer entity) + public override void Transform(CustomerRecord queryResult, Customer entity) { var customer = entity ?? new Customer(); customer.Id = queryResult.Id; diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrderItemsTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrderItemsTransform.cs new file mode 100644 index 0000000..d6b9efa --- /dev/null +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrderItemsTransform.cs @@ -0,0 +1,31 @@ +using Schemio.Core; +using Schemio.Core.Helpers; +using Schemio.EntityFramework.Tests.EntitySetup.Entities; +using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; + +namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms +{ + public class OrderItemsTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer customer) + { + if (collectionResult == null || !collectionResult.Any() || customer.Orders == null) + return; + + foreach (var result in collectionResult) + { + var order = customer.Orders.FirstOrDefault(o => o.OrderId == result.OrderId); + if (order == null) + continue; + + order.Items = ArrayUtil.EnsureAndResizeArray(order.Items, out var index); + order.Items[index] = new OrderItem + { + ItemId = result.ItemId, + Name = result.Name, + Cost = result.Cost + }; + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrdersTransform.cs b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrdersTransform.cs new file mode 100644 index 0000000..0a29b7e --- /dev/null +++ b/tests/Schemio.EntityFramework.Tests/EntitySetup/EntitySchemas/Transforms/OrdersTransform.cs @@ -0,0 +1,29 @@ +using Schemio.Core; +using Schemio.EntityFramework.Tests.EntitySetup.Entities; +using Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Queries; + +namespace Schemio.EntityFramework.Tests.EntitySetup.EntitySchemas.Transforms +{ + public class OrdersTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer entity) + { + if (collectionResult == null || !collectionResult.Any()) + return; + + var customer = entity ?? new Customer(); + + customer.Orders = new Order[collectionResult.Count]; + + for (var index = 0; index < collectionResult.Count; index++) + { + customer.Orders[index] = new Order + { + Date = collectionResult[index].Date, + OrderId = collectionResult[index].OrderId, + OrderNo = collectionResult[index].OrderNo + }; + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/JsonExtensions.cs b/tests/Schemio.EntityFramework.Tests/JsonExtensions.cs deleted file mode 100644 index c255245..0000000 --- a/tests/Schemio.EntityFramework.Tests/JsonExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json; - -namespace Schemio.EntityFramework.Tests -{ - public static class JsonExtensions - { - public static string ToJson(this T obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - return JsonSerializer.Serialize(obj); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.EntityFramework.Tests/Schemio.EntityFramework.Tests.csproj b/tests/Schemio.EntityFramework.Tests/Schemio.EntityFramework.Tests.csproj index 36e3bce..7aa3bf6 100644 --- a/tests/Schemio.EntityFramework.Tests/Schemio.EntityFramework.Tests.csproj +++ b/tests/Schemio.EntityFramework.Tests/Schemio.EntityFramework.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,14 +10,20 @@ - - - - - - - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Schemio.SQL.Tests/BaseTest.cs b/tests/Schemio.SQL.Tests/BaseTest.cs index 885502a..009bfba 100644 --- a/tests/Schemio.SQL.Tests/BaseTest.cs +++ b/tests/Schemio.SQL.Tests/BaseTest.cs @@ -1,7 +1,10 @@ +using System; using System.Data.Common; using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Schemio.Core; +using Schemio.Core.Helpers; +using Schemio.Core.PathMatchers; using Schemio.SQL; using Schemio.SQL.Tests.EntitySetup.Entities; using Schemio.SQL.Tests.EntitySetup.EntitySchemas; @@ -13,6 +16,20 @@ public class BaseTest protected ServiceProvider _serviceProvider; private const string DbProviderName = "System.Data.SQLite"; + protected void AssertAreEqual(Customer expected, Customer actual) + { + var actualCustomer = actual.ToJson(); + var expectedCustomer = expected.ToJson(); + + Console.WriteLine("expected:"); + Console.WriteLine(expectedCustomer); + + Console.WriteLine("actual:"); + Console.WriteLine(actualCustomer); + + Assert.That(actualCustomer, Is.EqualTo(expectedCustomer)); + } + [OneTimeSetUp] public void Setup() { @@ -26,9 +43,10 @@ public void Setup() services.AddLogging(); - services.UseSchemio(With.Schema(c => new CustomerSchema()) - .AddEngine(c => new QueryEngine(configuration)) - .LogWith(c => new Logger>(c.GetService()))); + services.UseSchemio() + .WithEngine(c => new QueryEngine(configuration)) + .WithPathMatcher(c => new XPathMatcher()) + .WithEntityConfiguration(c => new CustomerConfiguration()); // 4. Build the service provider _serviceProvider = services.BuildServiceProvider(); @@ -37,7 +55,8 @@ public void Setup() [OneTimeTearDown] public void TearDown() { - _serviceProvider = null; + if (_serviceProvider is IDisposable disposable) + disposable.Dispose(); } } } \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/Customer.db b/tests/Schemio.SQL.Tests/Customer.db index 59c0139..da2682c 100644 Binary files a/tests/Schemio.SQL.Tests/Customer.db and b/tests/Schemio.SQL.Tests/Customer.db differ diff --git a/tests/Schemio.SQL.Tests/E2E.Tests.cs b/tests/Schemio.SQL.Tests/E2E.Tests.cs index a6b9ba9..2fd1f5f 100644 --- a/tests/Schemio.SQL.Tests/E2E.Tests.cs +++ b/tests/Schemio.SQL.Tests/E2E.Tests.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; +using Schemio.Core; +using Schemio.Core.Impl; using Schemio.EntityFramework.Tests; -using Schemio.Impl; using Schemio.SQL.Tests.EntitySetup; using Schemio.SQL.Tests.EntitySetup.Entities; @@ -19,40 +20,131 @@ public void Setup() [Test] public void TestDataProviderToFetchWholeEntityWhenPathsAreNull() { - var customer = _provider.GetData(new CustomerContext + var customer = _provider.GetData(new CustomerRequest { CustomerId = 1 }); - Assert.IsNotNull(customer); - Assert.That(customer.ToJson(), Is.EqualTo("{\"Id\":1,\"Code\":\"AB123\",\"Name\":\"Jack Sparrow\",\"Communication\":{\"ContactId\":1,\"Phone\":\"0123456789\",\"Email\":\"jack.sparrow@schemio.com\",\"Address\":{\"AddressId\":0,\"HouseNo\":\"77\",\"City\":\"Wansted\",\"Region\":\"Belfast\",\"PostalCode\":\"BL34Y56\",\"Country\":\"United Kingdom\"}},\"Orders\":[{\"OrderId\":1,\"OrderNo\":\"ZX123VH\",\"Date\":\"0001-01-01T00:00:00\",\"Items\":[{\"ItemId\":1,\"Name\":\"12 inch Cake\",\"Cost\":30},{\"ItemId\":2,\"Name\":\"20 Cake Candles\",\"Cost\":5}]}]}")); + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Communication = new Communication + { + ContactId = 1, + Phone = "0123456789", + Email = "jack.sparrow@gmail.com", + Address = new Address + { + AddressId = 1, + HouseNo = "77", + City = "Wansted", + Region = "Belfast", + PostalCode = "BL34Y56", + Country = "United Kingdom", + } + }, + Orders = [ new Order { + OrderId = 1, + OrderNo = "ZX123VH", + Date = DateTime.Parse("2021-10-22T12:13:04"), + Items = + [ + new OrderItem + { + ItemId = 1, Name = "12 inch Cake", Cost = 30m + }, + new OrderItem + { + ItemId = 2, Name = "20 Cake Candles", Cost = 5m + } + ] + }] + }; + + AssertAreEqual(expected, customer); } [Test] - public void TestDataProviderToFetchEntityWhenPathsNotNull() + public void TestDataProviderToFetchEntityWhenPathsContainsOrderItems() { - var customer = _provider.GetData(new CustomerContext + var customer = _provider.GetData(new CustomerRequest { CustomerId = 1, SchemaPaths = new[] { "Customer/orders/order/items/item" } }); - Assert.IsNotNull(customer); - Assert.That(customer.ToJson(), Is.EqualTo("{\"Id\":1,\"Code\":\"AB123\",\"Name\":\"Jack Sparrow\",\"Communication\":null,\"Orders\":[{\"OrderId\":1,\"OrderNo\":\"ZX123VH\",\"Date\":\"0001-01-01T00:00:00\",\"Items\":[{\"ItemId\":1,\"Name\":\"12 inch Cake\",\"Cost\":30},{\"ItemId\":2,\"Name\":\"20 Cake Candles\",\"Cost\":5}]}]}")); + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Orders = [ new Order + { + OrderId = 1, + OrderNo = "ZX123VH", + Date = DateTime.Parse("2021-10-22T12:13:04"), + Items = + [ + new OrderItem + { + ItemId = 1, Name = "12 inch Cake", Cost = 30m + }, + new OrderItem + { + ItemId = 2, Name = "20 Cake Candles", Cost = 5m + } + ] + }] + }; + + AssertAreEqual(expected, customer); + } + + [Test] + public void TestDataProviderToFetchEntityWhenPathsContainsCommunication() + { + var customer = _provider.GetData(new CustomerRequest + { + CustomerId = 1, + SchemaPaths = new[] { "Customer/Communication" } + }); + + var expected = new Customer + { + Id = 1, + Name = "Jack Sparrow", + Code = "AB123", + Communication = new Communication + { + ContactId = 1, + Phone = "0123456789", + Email = "jack.sparrow@gmail.com", + Address = new Address + { + AddressId = 1, + HouseNo = "77", + City = "Wansted", + Region = "Belfast", + PostalCode = "BL34Y56", + Country = "United Kingdom", + } + } + }; + + AssertAreEqual(expected, customer); } [Test] public void TestDataProviderToCacheResultForResultsWithAttributeApplied() { - var context = new DataContext(new CustomerContext + var context = new DataContext(new CustomerRequest { CustomerId = 1 }); - var customer = ((DataProvider)_provider).GetData(context); - - Assert.IsNotNull(customer); - Assert.That(customer.ToJson(), Is.EqualTo("{\"Id\":1,\"Code\":\"AB123\",\"Name\":\"Jack Sparrow\",\"Communication\":{\"ContactId\":1,\"Phone\":\"0123456789\",\"Email\":\"jack.sparrow@schemio.com\",\"Address\":{\"AddressId\":0,\"HouseNo\":\"77\",\"City\":\"Wansted\",\"Region\":\"Belfast\",\"PostalCode\":\"BL34Y56\",\"Country\":\"United Kingdom\"}},\"Orders\":[{\"OrderId\":1,\"OrderNo\":\"ZX123VH\",\"Date\":\"0001-01-01T00:00:00\",\"Items\":[{\"ItemId\":1,\"Name\":\"12 inch Cake\",\"Cost\":30},{\"ItemId\":2,\"Name\":\"20 Cake Candles\",\"Cost\":5}]}]}")); + ((DataProvider)_provider).GetData(context); Assert.That(context.Cache, Is.Not.Null); Assert.That(context.Cache.Count, Is.EqualTo(1)); diff --git a/tests/Schemio.SQL.Tests/EntitySetup/CustomerContext.cs b/tests/Schemio.SQL.Tests/EntitySetup/CustomerRequest.cs similarity index 67% rename from tests/Schemio.SQL.Tests/EntitySetup/CustomerContext.cs rename to tests/Schemio.SQL.Tests/EntitySetup/CustomerRequest.cs index c9975bb..6234c15 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/CustomerContext.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/CustomerRequest.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.SQL.Tests.EntitySetup { - internal class CustomerContext : IEntityContext + internal class CustomerRequest : IEntityRequest { public int CustomerId { get; set; } public string[] SchemaPaths { get; set; } diff --git a/tests/Schemio.SQL.Tests/EntitySetup/Entities/Customer.cs b/tests/Schemio.SQL.Tests/EntitySetup/Entities/Customer.cs index c46d25b..772c668 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/Entities/Customer.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/Entities/Customer.cs @@ -1,3 +1,5 @@ +using Schemio.Core; + namespace Schemio.SQL.Tests.EntitySetup.Entities { public class Customer : IEntity diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/CustomerConfiguration.cs similarity index 56% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs rename to tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/CustomerConfiguration.cs index 8c55751..37797f7 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/CustomerConfiguration.cs @@ -1,21 +1,22 @@ +using Schemio.Core; using Schemio.SQL.Tests.EntitySetup.Entities; using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms; namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas { - internal class CustomerSchema : BaseEntitySchema + internal class CustomerConfiguration : EntityConfiguration { public override IEnumerable> GetSchema() { return CreateSchema.For() .Map(For.Paths("customer"), customer => customer.Dependents - .Map(For.Paths("customer/communication")) - .Map(For.Paths("customer/orders"), + .Map(For.Paths("customer/communication")) + .Map(For.Paths("customer/orders"), customerOrders => customerOrders.Dependents - .Map(For.Paths("customer/orders/order/items"))) - ).Create(); + .Map(For.Paths("customer/orders/order/items"))) + ).End(); } } } \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationQuery.cs new file mode 100644 index 0000000..0a75dd9 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationQuery.cs @@ -0,0 +1,31 @@ +using System.Data; +using Dapper; +using Schemio.Core; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class CommunicationQuery : SQLQuery + { + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerRecord)parentQueryResult; + + return connection => connection.QueryFirstOrDefaultAsync(new CommandDefinition + ( + "select c.CommunicationId as ContactId, " + + "c.Phone as Telephone, " + + "c.Email, " + + "a.AddressId, " + + "a.HouseNo, " + + "a.City, " + + "a.Region, " + + "a.PostCode as PostalCode, " + + "a.Country " + + "from TCommunication c " + + "left join TAddress a on a.CommunicationId = c.CommunicationId " + + $"where customerId={customer.Id}" + )); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationRecord.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationRecord.cs new file mode 100644 index 0000000..a71784b --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CommunicationRecord.cs @@ -0,0 +1,18 @@ +using Schemio.Core; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + [CacheResult] + public class CommunicationRecord : IQueryResult + { + public int ContactId { get; set; } + public string Telephone { get; set; } + public string Email { get; set; } + public int AddressId { get; set; } + public string HouseNo { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerQuery.cs new file mode 100644 index 0000000..fb5c2ca --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerQuery.cs @@ -0,0 +1,23 @@ +using System.Data; +using Dapper; +using Schemio.Core; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + public class CustomerQuery : SQLQuery + { + protected override Func> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Executes as root or level 1 query. + var customer = (CustomerRequest)context.Request; + + return connection => connection.QueryFirstOrDefaultAsync(new CommandDefinition + ( + "select CustomerId as Id, " + + "Customer_Name as Name," + + "Customer_Code as Code " + + $"from TCustomer where customerId={customer.CustomerId}" + )); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerRecord.cs similarity index 74% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs rename to tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerRecord.cs index 34e9064..67038cf 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerResult.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/CustomerRecord.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries { - public class CustomerResult : IQueryResult + public class CustomerRecord : IQueryResult { public int Id { get; set; } public string Code { get; set; } diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemRecord.cs similarity index 77% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs rename to tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemRecord.cs index e4605c6..f9667ee 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemResult.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemRecord.cs @@ -1,6 +1,8 @@ +using Schemio.Core; + namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries { - public class OrderItemResult : IQueryResult + public class OrderItemRecord : IQueryResult { public int OrderId { get; set; } public int ItemId { get; set; } diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemsQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemsQuery.cs new file mode 100644 index 0000000..4256ae7 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderItemsQuery.cs @@ -0,0 +1,30 @@ +using System.Data; +using Dapper; +using Schemio.Core; +using Schemio.Core.Helpers; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class OrderItemsQuery : SQLQuery> + { + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child query to order query taking OrderResult to resolve query parameter. + var ordersResult = (CollectionResult)parentQueryResult; + + return async connection => + { + var items = await connection.QueryAsync(new CommandDefinition + ( + "select OrderId, " + + "OrderItemId as ItemId, " + + "Name, " + + "Cost " + + $"from TOrderItem where OrderId in ({ordersResult.Select(o => o.OrderId).ToCSV()})" + )); + + return new CollectionResult(items); + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderRecord.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderRecord.cs new file mode 100644 index 0000000..336df23 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrderRecord.cs @@ -0,0 +1,11 @@ +using Schemio.Core; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + public class OrderRecord : IQueryResult + { + public int OrderId { get; set; } + public string OrderNo { get; set; } + public string OrderDate { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrdersQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrdersQuery.cs new file mode 100644 index 0000000..e49a354 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Queries/OrdersQuery.cs @@ -0,0 +1,29 @@ +using System.Data; +using Dapper; +using Schemio.Core; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries +{ + internal class OrdersQuery : SQLQuery> + { + protected override Func>> GetQuery(IDataContext context, IQueryResult parentQueryResult) + { + // Execute as child to customer query. + var customer = (CustomerRecord)parentQueryResult; + + return async connection => + { + var items = await connection.QueryAsync(new CommandDefinition + ( + "select OrderId, " + + "OrderNo, " + + "OrderDate " + + "from TOrder " + + $"where customerId={customer.Id}" + )); + + return new CollectionResult(items); + }; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CommunicationTransform.cs similarity index 75% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs rename to tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CommunicationTransform.cs index 94c5beb..caadad1 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerCommunicationTransform.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CommunicationTransform.cs @@ -1,16 +1,17 @@ +using Schemio.Core; using Schemio.SQL.Tests.EntitySetup.Entities; using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms { - public class CustomerCommunicationTransform : BaseTransformer + public class CommunicationTransform : BaseTransformer { - public override void Transform(CommunicationResult queryResult, Customer entity) + public override void Transform(CommunicationRecord queryResult, Customer entity) { var customer = entity ?? new Customer(); customer.Communication = new Communication { - ContactId = queryResult.Id, + ContactId = queryResult.ContactId, Email = queryResult.Email, Phone = queryResult.Telephone }; @@ -18,6 +19,7 @@ public override void Transform(CommunicationResult queryResult, Customer entity) if (queryResult.HouseNo != null) customer.Communication.Address = new Address { + AddressId = queryResult.AddressId, HouseNo = queryResult.HouseNo, City = queryResult.City, Country = queryResult.Country, diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CustomerTransform.cs similarity index 71% rename from tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs rename to tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CustomerTransform.cs index 10758db..8669ffa 100644 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerTransform.cs +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/CustomerTransform.cs @@ -1,11 +1,12 @@ +using Schemio.Core; using Schemio.SQL.Tests.EntitySetup.Entities; using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms { - public class CustomerTransform : BaseTransformer + public class CustomerTransform : BaseTransformer { - public override void Transform(CustomerResult queryResult, Customer entity) + public override void Transform(CustomerRecord queryResult, Customer entity) { var customer = entity ?? new Customer(); customer.Id = queryResult.Id; diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrderItemsTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrderItemsTransform.cs new file mode 100644 index 0000000..0dc1f85 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrderItemsTransform.cs @@ -0,0 +1,31 @@ +using Schemio.Core; +using Schemio.Core.Helpers; +using Schemio.SQL.Tests.EntitySetup.Entities; +using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms +{ + public class OrderItemsTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer customer) + { + if (collectionResult == null || !collectionResult.Any() || customer.Orders == null) + return; + + foreach (var result in collectionResult) + { + var order = customer.Orders.FirstOrDefault(o => o.OrderId == result.OrderId); + if (order == null) + continue; + + order.Items = ArrayUtil.EnsureAndResizeArray(order.Items, out var index); + order.Items[index] = new OrderItem + { + ItemId = result.ItemId, + Name = result.Name, + Cost = result.Cost + }; + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrdersTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrdersTransform.cs new file mode 100644 index 0000000..47306f3 --- /dev/null +++ b/tests/Schemio.SQL.Tests/EntitySetup/EntityConfiguration/Transforms/OrdersTransform.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using Schemio.Core; +using Schemio.SQL.Tests.EntitySetup.Entities; +using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; + +namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms +{ + public class OrdersTransform : BaseTransformer, Customer> + { + public override void Transform(CollectionResult collectionResult, Customer contract) + { + if (collectionResult == null || !collectionResult.Any()) + return; + + var customer = contract ?? new Customer(); + + customer.Orders = new Order[collectionResult.Count]; + + for (var index = 0; index < collectionResult.Count; index++) + { + customer.Orders[index] = new Order + { + Date = DateTime.ParseExact(collectionResult[index].OrderDate, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture), + OrderId = collectionResult[index].OrderId, + OrderNo = collectionResult[index].OrderNo + }; + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs deleted file mode 100644 index f9b9263..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerCommunicationQuery.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Data; -using Dapper; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerCommunicationQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to customer query. - var customer = (CustomerResult)parentQueryResult; - QueryParameter = new CustomerParameter - { - CustomerId = customer.Id - }; - } - - public override IEnumerable Execute(IDbConnection conn) - { - return conn.Query(new CommandDefinition - ( - "select c.CommunicationId as Id, " + - "c.Phone as Telephone, " + - "c.Email, " + - "a.AddressId, " + - "a.HouseNo, " + - "a.City, " + - "a.Region, " + - "a.PostCode as PostalCode, " + - "a.Country " + - "from TCommunication c " + - "left join TAddress a on a.CommunicationId = c.CommunicationId " + - $"where customerId={QueryParameter.CustomerId}" - )); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs deleted file mode 100644 index 8542c78..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrderItemsQuery.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Data; -using Dapper; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerOrderItemsQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child query to order query taking OrderResult to resolve query parameter. - var ordersResult = (OrderResult)parentQueryResult; - - QueryParameter ??= new OrderItemParameter(); - QueryParameter.OrderIds.Add(ordersResult.OrderId); - } - - public override IEnumerable Execute(IDbConnection conn) - { - return conn.Query(new CommandDefinition - ( - "select OrderId, " + - "OrderItemId as ItemId, " + - "Name, " + - "Cost " + - $"from TOrderItem where OrderId in ({QueryParameter.ToCsv()})" - )); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs deleted file mode 100644 index b76f4de..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerOrdersQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Data; -using Dapper; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class CustomerOrdersQuery : BaseSQLChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to customer query. - var customer = (CustomerResult)parentQueryResult; - QueryParameter = new CustomerParameter - { - CustomerId = customer.Id - }; - } - - public override IEnumerable Execute(IDbConnection conn) - { - return conn.Query(new CommandDefinition - ( - "select OrderId, " + - "OrderNo, " + - "Date(OrderDate) as Date " + - "from TOrder " + - $"where customerId={QueryParameter.CustomerId}" - )); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs deleted file mode 100644 index 94c68e8..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerParameter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - public class CustomerParameter : IQueryParameter - { - public int CustomerId { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs deleted file mode 100644 index 3b7d18a..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/CustomerQuery.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Data; -using Dapper; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - public class CustomerQuery : BaseSQLRootQuery - { - public override void ResolveRootQueryParameter(IDataContext context) - { - // Executes as root or level 1 query. - var customer = (CustomerContext)context.Entity; - QueryParameter = new CustomerParameter - { - CustomerId = (int)customer.CustomerId - }; - } - - public override IEnumerable Execute(IDbConnection conn) - { - return conn.Query(new CommandDefinition - ( - "select CustomerId as Id, " + - "Customer_Name as Name," + - "Customer_Code as Code " + - $"from TCustomer where customerId={QueryParameter.CustomerId}" - )); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs deleted file mode 100644 index 5dc9249..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Queries/OrderItemParameter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Schemio.Helpers; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries -{ - internal class OrderItemParameter : IQueryParameter - { - public OrderItemParameter() - { - OrderIds = new List(); - } - - public string ToCsv() - { - return OrderIds.ToCSV(); - } - - public List OrderIds { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs deleted file mode 100644 index 99098fe..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrderItemsTransform.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Schemio.Helpers; -using Schemio.SQL.Tests.EntitySetup.Entities; -using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms -{ - public class CustomerOrderItemsTransform : BaseTransformer - { - public override void Transform(OrderItemResult queryResult, Customer entity) - { - if (queryResult == null || entity?.Orders == null) - return; - - foreach (var order in entity.Orders) - if (order.OrderId == queryResult.OrderId) - { - order.Items = ArrayUtil.EnsureAndResizeArray(order.Items, out var index); - order.Items[index] = new OrderItem - { - ItemId = queryResult.ItemId, - Name = queryResult.Name, - Cost = queryResult.Cost - }; - } - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs b/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs deleted file mode 100644 index c88c789..0000000 --- a/tests/Schemio.SQL.Tests/EntitySetup/EntitySchemas/Transforms/CustomerOrdersTransform.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Schemio.Helpers; -using Schemio.SQL.Tests.EntitySetup.Entities; -using Schemio.SQL.Tests.EntitySetup.EntitySchemas.Queries; - -namespace Schemio.SQL.Tests.EntitySetup.EntitySchemas.Transforms -{ - public class CustomerOrdersTransform : BaseTransformer - { - public override void Transform(OrderResult queryResult, Customer entity) - { - if (queryResult == null) - return; - - var customer = entity ?? new Customer(); - - customer.Orders = ArrayUtil.EnsureAndResizeArray(customer.Orders, out var index); - - customer.Orders[index] = new Order - { - Date = queryResult.Date, - OrderId = queryResult.OrderId, - OrderNo = queryResult.OrderNo - }; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/JsonExtensions.cs b/tests/Schemio.SQL.Tests/JsonExtensions.cs deleted file mode 100644 index 656f17e..0000000 --- a/tests/Schemio.SQL.Tests/JsonExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json; - -namespace Schemio.SQL.Tests -{ - public static class JsonExtensions - { - public static string ToJson(this T obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - return JsonSerializer.Serialize(obj); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.SQL.Tests/Schemio.SQL.Tests.csproj b/tests/Schemio.SQL.Tests/Schemio.SQL.Tests.csproj index da8f385..82a43e1 100644 --- a/tests/Schemio.SQL.Tests/Schemio.SQL.Tests.csproj +++ b/tests/Schemio.SQL.Tests/Schemio.SQL.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,15 +10,21 @@ - - - - - - - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Schemio.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs b/tests/Schemio.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs deleted file mode 100644 index 8e856d2..0000000 --- a/tests/Schemio.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; -using Schemio.Tests.EntitySetup.Transforms; - -namespace Schemio.Tests.EntitySetup.EntitySchemas -{ - internal class CustomerSchema : BaseEntitySchema - { - public override IEnumerable> GetSchema() - { - return CreateSchema.For() - .Map(For.Paths("customer/id", "customer/customercode", "customer/customername"), - customer => customer.Dependents - .Map(For.Paths("customer/communication")) - .Map(For.Paths("customer/orders"), - customerOrders => customerOrders.Dependents - .Map(For.Paths("customer/orders/order/items"))) - ).Create(); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs b/tests/Schemio.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs deleted file mode 100644 index 8eafe6c..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - internal class CustomerCommunicationQuery : BaseChildQuery - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to customer query. - var customer = (CustomerResult)parentQueryResult; - QueryParameter = new CustomerParameter - { - CustomerId = customer.Id - }; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs b/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs deleted file mode 100644 index d1c3f8c..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - internal class CustomerOrderItemsQuery : BaseChildQuery> - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Execute as child to order query. - var ordersResult = (OrderCollectionResult)parentQueryResult; - QueryParameter = new OrderItemParameter - { - OrderIds = new List(ordersResult.Orders.Select(x => x.OrderId)) - }; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs b/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs deleted file mode 100644 index 9f40565..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - internal class CustomerOrdersQuery : BaseChildQuery> - { - public override void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult) - { - // Does not execute as child to any query. - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerParameter.cs b/tests/Schemio.Tests/EntitySetup/Queries/CustomerParameter.cs deleted file mode 100644 index 364e1e9..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerParameter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - public class CustomerParameter : IQueryParameter - { - public int CustomerId { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/CustomerQuery.cs b/tests/Schemio.Tests/EntitySetup/Queries/CustomerQuery.cs deleted file mode 100644 index dd726ed..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/CustomerQuery.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - public class CustomerQuery : BaseRootQuery - { - public override void ResolveRootQueryParameter(IDataContext context) - { - // Executes as root or level 1 query. - var customer = (CustomerContext)context.Entity; - QueryParameter = new CustomerParameter - { - CustomerId = customer.CustomerId - }; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/OrderCollectionResult.cs b/tests/Schemio.Tests/EntitySetup/Queries/OrderCollectionResult.cs deleted file mode 100644 index 11a5c45..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/OrderCollectionResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - public class OrderCollectionResult : IQueryResult - { - public int CustomerId { get; set; } - public OrderValue[] Orders { get; set; } - } - - public class OrderValue - { - public int OrderId { get; set; } - public string OrderNo { get; set; } - public DateTime Date { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs b/tests/Schemio.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs deleted file mode 100644 index 74f2c51..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - public class OrderItemCollectionResult : IQueryResult - { - public List OrderItems { get; set; } - } - - public class OrderItemValue - { - public int OrderId { get; set; } - public (int ItemId, string Name, decimal Cost)[] Items { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Queries/OrderItemParameter.cs b/tests/Schemio.Tests/EntitySetup/Queries/OrderItemParameter.cs deleted file mode 100644 index 31bf45a..0000000 --- a/tests/Schemio.Tests/EntitySetup/Queries/OrderItemParameter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Schemio.Tests.EntitySetup.Queries -{ - internal class OrderItemParameter : IQueryParameter - { - public List OrderIds { get; set; } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs b/tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs deleted file mode 100644 index 0fa7f47..0000000 --- a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; - -namespace Schemio.Tests.EntitySetup.Transforms -{ - public class CustomerOrdersTransform : BaseTransformer, Customer> - { - public override void Transform(CollectionResult queryResult, Customer entity) - { - if (queryResult?.Items == null) - return; - - var customer = entity ?? new Customer(); - customer.Orders = queryResult.Items.Select(x => new Order - { - Date = x.Date, - OrderId = x.OrderId, - OrderNo = x.OrderNo - }).ToArray(); - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerTransform.cs b/tests/Schemio.Tests/EntitySetup/Transforms/CustomerTransform.cs deleted file mode 100644 index 4b29529..0000000 --- a/tests/Schemio.Tests/EntitySetup/Transforms/CustomerTransform.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Schemio.Tests.EntitySetup.Entities; -using Schemio.Tests.EntitySetup.Queries; - -namespace Schemio.Tests.EntitySetup.Transforms -{ - public class CustomerTransform : BaseTransformer - { - public override void Transform(CustomerResult queryResult, Customer entity) - { - var customer = entity ?? new Customer(); - customer.Id = queryResult.Id; - customer.Name = queryResult.CustomerName; - customer.Code = queryResult.CustomerCode; - } - } -} \ No newline at end of file diff --git a/tests/Schemio.Tests/Schemio.Tests.csproj b/tests/Schemio.Tests/Schemio.Tests.csproj deleted file mode 100644 index dcc5ad6..0000000 --- a/tests/Schemio.Tests/Schemio.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - net8.0 - enable - enable - - false - - - - - - - - - - - - - - - - - diff --git a/tests/Schemio.Tests/UnitTest1.cs b/tests/Schemio.Tests/UnitTest1.cs deleted file mode 100644 index 8ed042a..0000000 --- a/tests/Schemio.Tests/UnitTest1.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Schemio.Tests -{ - public class Tests - { - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } - } -} \ No newline at end of file