diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2329ba4..f597a18 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -30,7 +30,7 @@ jobs: beta_Version: ${{ steps.gitversion.outputs.nuGetVersion }} branchName: ${{ steps.gitversion.outputs.branchName }} env: - working-directory: /home/runner/work/Schemio.Data/Schemio.Data + working-directory: /home/runner/work/Schemio.Object/Schemio.Object steps: - name: Step-01 Install GitVersion @@ -87,7 +87,7 @@ jobs: env: github-token: '${{ secrets.GITHUB_TOKEN }}' nuget-token: '${{ secrets.NUGET_API_KEY }}' - working-directory: /home/runner/work/Schemio.Data/Schemio.Data + working-directory: /home/runner/work/Schemio.Object/Schemio.Object steps: - name: Step-01 Retrieve Build Artifacts uses: actions/download-artifact@v3 @@ -103,7 +103,7 @@ jobs: - name: Step-03 Release to Nuget Org if: ${{ startsWith(github.head_ref, 'release/')}} - run: dotnet nuget push ${{env.working-directory}}/src/Schemio.Data/bin/Release/*.nupkg --skip-duplicate --api-key ${{ env.nuget-token }} --source https://api.nuget.org/v3/index.json + run: dotnet nuget push ${{env.working-directory}}/src/Schemio.Object/bin/Release/*.nupkg --skip-duplicate --api-key ${{ env.nuget-token }} --source https://api.nuget.org/v3/index.json Release: name: Release to Nuget @@ -119,15 +119,15 @@ jobs: # dotnet-version: '6.0.x' # Publish - - name: publish Schemio.Data package + - name: publish Schemio.Object package id: publish_nuget uses: Rebel028/publish-nuget@v2.8.0 with: # Filepath of the project to be packaged, relative to root of repository - PROJECT_FILE_PATH: Schemio.Data/Schemio.Data.csproj + PROJECT_FILE_PATH: Schemio.Object/Schemio.Object.csproj # NuGet package id, used for version detection & defaults to project name - PACKAGE_NAME: Schemio.Data + PACKAGE_NAME: Schemio.Object # Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH # VERSION_FILE_PATH: Directory.Build.props diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..3f9ebba --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,15 @@ +next-version: 1.0.1 +tag-prefix: '[vV]' +mode: ContinuousDeployment +branches: + master: + regex: ^master$ + release: + regex: ^release$ + develop: + regex: ^develop$|^dev$ + tag: beta + pull-request: + tag: beta +ignore: + sha: [] \ No newline at end of file diff --git a/README.md b/README.md index a4ca7ba..d08085a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,185 @@ -# Schemio.Data -[![NuGet version](https://badge.fury.io/nu/Schemio.Data.svg)](https://badge.fury.io/nu/Schemio.Data) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/NinjaRocks/Schemio.Data/blob/master/License.md) [![CI](https://github.com/NinjaRocks/Data2Xml/actions/workflows/CI.yml/badge.svg)](https://github.com/NinjaRocks/Data2Xml/actions/workflows/CI.yml) [![GitHub Release](https://img.shields.io/github/v/release/ninjarocks/Data2Xml?logo=github&sort=semver)](https://github.com/ninjarocks/Data2Xml/releases/latest) -[![CodeQL](https://github.com/NinjaRocks/Schemio.Data/actions/workflows/CodeQL.yml/badge.svg)](https://github.com/NinjaRocks/Schemio.Data/actions/workflows/CodeQL.yml) [![.Net Stardard](https://img.shields.io/badge/.Net%20Standard-2.1-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) +# Schemio.Object +[![NuGet version](https://badge.fury.io/nu/Schemio.Object.svg)](https://badge.fury.io/nu/Schemio.Object) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/NinjaRocks/Schemio.Object/blob/master/License.md) [![CI](https://github.com/NinjaRocks/Data2Xml/actions/workflows/CI.yml/badge.svg)](https://github.com/NinjaRocks/Data2Xml/actions/workflows/CI.yml) [![GitHub Release](https://img.shields.io/github/v/release/ninjarocks/Data2Xml?logo=github&sort=semver)](https://github.com/ninjarocks/Data2Xml/releases/latest) +[![CodeQL](https://github.com/NinjaRocks/Schemio.Object/actions/workflows/CodeQL.yml/badge.svg)](https://github.com/NinjaRocks/Schemio.Object/actions/workflows/CodeQL.yml) [![.Net Stardard](https://img.shields.io/badge/.Net%20Standard-2.1-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) -- -### .Net Standard 2.1 Library to map data to Entity using Schema Paths (Like XPath or JsonPath). +## What is Schemio? +`Schemio` is a .Net utility that can be used to fetch entities by specifying sections of object graph for hydrating data. +> Supports XPath & JsonPath for object schema paths. + +## When to use Schemio? +Schemio is a perfect utility when you need to fetch a large entity from data source. Ideally, you may not require all of the entity data but only sections of the object graph for different fetches. +> Example use case is document generation which may require only templated sections of client data to be fetched for different document templates in context. + +## How to use Schemio? +You could use Schemio out of the box or extend the utility in order to suit your custom needs. +> To use schemio you need to +> - Setup the entity to be fetched using DataProvider. +> - Construct the DataProvider with required dependencies. + +### Entity Setup +* Define the `entity` to be fetched using `DataProvider` - which is basically a class with nested typed properties. +* Define the `entity schema` with `query` and `transformer` pairs mappings to entity's object graph. The relevant query and transformer pairs will execute in the order of their nesting when their mapped `schema paths` are included in the `request` parameter of the DataProvider. +* `Query` is an implementation to fetch `data` for the entity object graph from the underlying data storage supported by the chosen `QueryEngine`. QueryEngine is an implementation of `IQueryEngine` to execute queries against supported data source. +* `Transformer` is an implementation to transform the data fetched by the associated query to mapped section of the object graph. +#### 1. Entity +> Step 1 - To mark the class as Entity using schemio, implement the class from `IEntity` interface. Bear in mind this is the root entity to be fetched. + +Below is an example `Customer` entity we want to fetch using schemio. + +``` + 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; } + } +``` +Example Customer class with XSD Schema +``` +To Do +``` +There are three levels of nesting in the object graph for customer class above. +- Level 1 with paths: `Customer/CustomerId`, `Customer/CustomerCode`, `Customer/CustomerName` +- Level 2 with paths: `Customer/Communication` and `Customer/Orders` +- Level 3 with paths: `Customer/Orders/Order/Items` + +#### 2. Entity Schema Definition +> Step 2 - Define entity schema configuration which is basically a hierarchy of query/transformer pairs mapping to the schema paths pointing to the object graph of the entity in context. +In query/transformer setup, the output of the query serves as the input to the transformer to map data to linked object graph of the entity in context. +You could nest query/transformer pairs in a parent/child setup. In which case the output of the parent query would become the input to the child query to resolve query paramter. + +To define Entity schema, implement `IEntitySchema` interface where T is entity in context. The `query/transformer` mappings can be `nested` to `5` levels down. + +You could map multiple schema paths to a given query/transformer pair. Currently, XPath and JSONPath schema paths are supported. + +If you need to support custom schema language for mapping to object graph, then use the custom paths in entity schema definition however you may need to provide custom implementation of `ISchemaPathMatcher` interface. +``` +public interface ISchemaPathMatcher + { + bool IsMatch(string inputPath, ISchemaPaths configuredPaths); + } +``` + +Example Entity Schema Definition +> 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 : IEntitySchema + { + private IEnumerable> mappings; + + public IEnumerable> Mappings => mappings; + + public CustomerSchema() + { + // Create an object mapping graph of query and transformer pairs using xpaths. + + mappings = 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"))) + ).Complete(); + } + } +``` +image + + +#### 2.1 Query Class +The purpose of a query class is to execute to fetch data from data source when mapped schema path(s) are included in the request parameter of data provider. +- To define a query you need to implement from `BaseQuery` where `TQueryParameter` is the query parameter and `TQueryResult` is the query result. +- `TQueryParameter` is basically the class that holds the `inputs` required by the query for execution. +- `TQueryResult` is the result that will be obtained from executing the query. +- You can run the query in `Parent` or `Child` (nested) mode. The parent/child relationship is achieved by configuring the query accordingly in entity schema definition. See `CustomerSchema` above. +- The query parameter needs to be resolved before executing the query with QueryEngine. + - In `parent` mode, the query parameter is resolved using the `request/context` parameter passed to data provider class. + - In `child` mode, the query parameter is resolved using the `query result` of the `parent query` as stiched in the entity schema configuration. You could have a maximum of `5` levels of children query nestings. + +See example `CustomerQuery` implemented to run in parent mode below. +``` +public class CustomerQuery : BaseQuery + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Executes as Parent or Level 1 query. + // The query parameter is resolved using context parameter of data provider class. + + var customer = (CustomerContext)context; + QueryParameter = new CustomerParameter + { + CustomerId = customer.CustomerId + }; + } + + public override void ResolveParameterInChildMode(IDataContext context, IQueryResult parentQueryResult) + { + // Does not execute as child to any query. Hence has no implementation provided. + } + } +``` +See example `CustomerCommunicationQuery` implemented to run as child or nested query to customer query below. Please see `CustomerSchema` definition above for parent/child configuration setup. +``` + internal class CustomerCommunicationQuery : BaseQuery + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Does not execute as Parent or Level 1 query. Hence has no implementation provided. + } + + public override void ResolveParameterInChildMode(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 + }; + } + } +``` +Please Note: The above query implementation is basic and could vary with different implementations of the QueryEngine. +> Please see Query engine provider specific implementation of queries below. + + +#### 2.2 Tranformer Class +The purpose of the transformer class is to transform the data fetched by the linked query class to mapped object graph of the entity. + +To define a transformer class, you need to implement `BaseTransformer` +- where T is Entity implementing `IEntity`. eg. Customer. +- where TD is Query Result from associated Query implementing `IQueryResult` in EntitySchema definition. This is the query result obtained from the query, the transformer will consume to map to the relevant object graph of the Entity. + +> Please Note: Every `Query` type in the `EntitySchema` definition should have a complementing `Transformer` type. + +Below is the snippet from `CustomerSchema` definition. +> .Map(For.Paths("customer/id", "customer/customercode", "customer/customername")) + +The transformer should `map` `data` to only the `schema` mapped `sections` of the `object graph`. + + +In below example, `CustomerTransformer` (transformer) is implemented to transform `Customer` (entity) with `CustomerResult` (query result) obtained from `CustomerQuery` (query) execution. + +The transformer maps data to only `XPath` mapped sections of Customer object grapgh - `customer/id`, `customer/customercode`, `customer/customername` + +``` +public 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; + } + } +``` + +### DataProvider Setup +> coming soon diff --git a/Schemio.Object.sln b/Schemio.Object.sln index 313db86..501bc60 100644 --- a/Schemio.Object.sln +++ b/Schemio.Object.sln @@ -16,10 +16,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{AF .gitignore = .gitignore .github\workflows\CI.yml = .github\workflows\CI.yml .github\workflows\CodeQL.yml = .github\workflows\CodeQL.yml - README.md = README.md + GitVersion.yml = GitVersion.yml LICENSE.md = LICENSE.md + README.md = README.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.Object.SQL", "src\Schemio.Object.SQL\Schemio.Object.SQL.csproj", "{1A0CB973-23C9-4A17-905E-59510CD18932}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schemio.Object.SQL.Tests", "tests\Schemio.Object.SQL.Tests\Schemio.Object.SQL.Tests.csproj", "{CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schemio.Object.EF", "src\Schemio.Object.EF\Schemio.Object.EF.csproj", "{6B92CC17-B7DB-446F-BF2F-A93696D48B5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +41,18 @@ Global {FDB00281-8B65-4A17-9F1F-B97865544BEF}.Debug|Any CPU.Build.0 = Debug|Any CPU {FDB00281-8B65-4A17-9F1F-B97865544BEF}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDB00281-8B65-4A17-9F1F-B97865544BEF}.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 + {CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA}.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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -41,6 +60,9 @@ Global GlobalSection(NestedProjects) = preSolution {6F017146-B95A-4081-9CC0-B0245F78D72B} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} {FDB00281-8B65-4A17-9F1F-B97865544BEF} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} + {1A0CB973-23C9-4A17-905E-59510CD18932} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} + {CBFE3D90-7FB2-4CB5-8B74-C8664F0173CA} = {07BAE427-96CF-4F9B-80A9-48CFB0A89CF3} + {6B92CC17-B7DB-446F-BF2F-A93696D48B5D} = {F41DA3D8-A0E9-4A05-8A35-78313C0F5804} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C0FF62D6-1374-4939-A546-432862338528} diff --git a/src/Schemio.Object.EF/BaseSQLQuery.cs b/src/Schemio.Object.EF/BaseSQLQuery.cs new file mode 100644 index 0000000..5ced6f0 --- /dev/null +++ b/src/Schemio.Object.EF/BaseSQLQuery.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace Schemio.Object.EF +{ + public abstract class BaseSQLQuery + : BaseQuery, 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 Func> GetQuery(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object.EF/EFQueryEngine.cs b/src/Schemio.Object.EF/EFQueryEngine.cs new file mode 100644 index 0000000..566bbe0 --- /dev/null +++ b/src/Schemio.Object.EF/EFQueryEngine.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; + +namespace Schemio.Object.EF +{ + public class EFQueryEngine : IQueryEngine where T : DbContext + { + private readonly IDbContextFactory _dbContextFactory; + + public EFQueryEngine(IDbContextFactory _dbContextFactory) + => this._dbContextFactory = _dbContextFactory; + + public IQueryResult[] Run(IQueryList queryList, IDataContext context) + { + if (queryList?.Queries == null) + return Array.Empty(); + + var queries = queryList.Queries.Cast(); + + if (!queries.Any()) + return Array.Empty(); + + var output = new List(); + + using (var dbcontext = _dbContextFactory.CreateDbContext()) + { + foreach (var query in queries) + { + var queryDelegate = query.GetQuery(); + if (queryDelegate == null) + continue; + + var results = queryDelegate(dbcontext); + if (results == null) + continue; + + output.AddRange(results); + } + return output.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/src/Schemio.Object.EF/IDbContext.cs b/src/Schemio.Object.EF/IDbContext.cs new file mode 100644 index 0000000..a56c90c --- /dev/null +++ b/src/Schemio.Object.EF/IDbContext.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Schemio.Object.EF +{ + public interface IDbContext + { + //DbSet Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>() + //where TEntity : class; + DbSet Set() where TEntity : class; + } + + internal class SchemioContext : DbContext, IDataContext + { + public string[] Paths { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public decimal CurrentVersion => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object.EF/ISQLQuery.cs b/src/Schemio.Object.EF/ISQLQuery.cs new file mode 100644 index 0000000..70890fb --- /dev/null +++ b/src/Schemio.Object.EF/ISQLQuery.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace Schemio.Object.EF +{ + public interface ISQLQuery + { + /// + /// Get query delegate with implementation to return query result. + /// Delegate returns a collection from db. + /// + /// Func> + Func> GetQuery(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object.EF/Schemio.Object.EF.csproj b/src/Schemio.Object.EF/Schemio.Object.EF.csproj new file mode 100644 index 0000000..7b884ac --- /dev/null +++ b/src/Schemio.Object.EF/Schemio.Object.EF.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Schemio.Object.SQL/BaseSQLQuery.cs b/src/Schemio.Object.SQL/BaseSQLQuery.cs new file mode 100644 index 0000000..5d486b9 --- /dev/null +++ b/src/Schemio.Object.SQL/BaseSQLQuery.cs @@ -0,0 +1,9 @@ +namespace Schemio.Object.SQL +{ + public abstract class BaseSQLQuery : BaseQuery, ISQLQuery + where TQueryParameter : IQueryParameter + where TQueryResult : IQueryResult + { + public abstract string GetQuery(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object.SQL/DapperQueryEngine.cs b/src/Schemio.Object.SQL/DapperQueryEngine.cs new file mode 100644 index 0000000..1610dd9 --- /dev/null +++ b/src/Schemio.Object.SQL/DapperQueryEngine.cs @@ -0,0 +1,74 @@ +using System.Data.Common; +using System.Data; +using Dapper; +using System.Text; +using System.Linq; + +namespace Schemio.Object.SQL +{ + public class DapperQueryEngine : IQueryEngine + { + private readonly SqlConfiguration sqlConfiguration; + + public DapperQueryEngine(SqlConfiguration sqlConfiguration) + { + this.sqlConfiguration = sqlConfiguration; + } + + public IQueryResult[] Run(IQueryList queryList, IDataContext context) + { + if (queryList?.Queries == null) + return Array.Empty(); + + var queries = queryList.Queries.Cast(); + + if (!queries.Any()) + return Array.Empty(); + + var results = new List(); + + var batches = queries.Chunk(sqlConfiguration.QuerySettings.QueryBatchSize); + foreach (var batch in batches) + { + var result = RunQueryBatch(batch); + if (result != null) + results.AddRange(result); + } + + return results.ToArray(); + } + + public IEnumerable RunQueryBatch(ISQLQuery[] queryList) + { + var factory = DbProviderFactories.GetFactory(sqlConfiguration.ConnectionSettings.ProviderName); + + if (factory == null) + throw new InvalidOperationException($"Provider: {sqlConfiguration.ConnectionSettings.ProviderName} is not supported. Please register entry in DbProviderFactories "); + + var output = new List(); + + 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."); + + connection.ConnectionString = sqlConfiguration.ConnectionSettings.ConnectionString; + + var sqlBuilder = new StringBuilder(); + + foreach (var query in queryList) + sqlBuilder.Append(query.GetQuery() + ";"); + + var queryResults = connection.QueryMultiple(sql: sqlBuilder.ToString(), commandTimeout: sqlConfiguration.QuerySettings.TimeoutInSecs); + + foreach (var query in queryList) + { + var results = queryResults.Read(query.ResultType)?.Cast(); + if (results != null) + output.AddRange(results); + } + return output; + } + } + } +} \ No newline at end of file diff --git a/src/Schemio.Object.SQL/ISQLQuery.cs b/src/Schemio.Object.SQL/ISQLQuery.cs new file mode 100644 index 0000000..be8798a --- /dev/null +++ b/src/Schemio.Object.SQL/ISQLQuery.cs @@ -0,0 +1,9 @@ +namespace Schemio.Object.SQL +{ + public interface ISQLQuery + { + Type ResultType { get; } + + string GetQuery(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object.SQL/Schemio.Object.SQL.csproj b/src/Schemio.Object.SQL/Schemio.Object.SQL.csproj new file mode 100644 index 0000000..72b3e02 --- /dev/null +++ b/src/Schemio.Object.SQL/Schemio.Object.SQL.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + diff --git a/src/Schemio.Object.SQL/SqlConfiguration.cs b/src/Schemio.Object.SQL/SqlConfiguration.cs new file mode 100644 index 0000000..657bbdc --- /dev/null +++ b/src/Schemio.Object.SQL/SqlConfiguration.cs @@ -0,0 +1,28 @@ +using System.Data.Common; + +namespace Schemio.Object.SQL +{ + public class SqlConfiguration + { + public SqlConfiguration() + { + ConnectionSettings = new ConnectionSettings(); + QuerySettings = new QuerySettings(); + } + + public ConnectionSettings ConnectionSettings { get; set; } + public QuerySettings QuerySettings { get; set; } + } + + public class ConnectionSettings + { + public DbConnection ProviderName { get; set; } + public string ConnectionString { get; set; } + } + + public class QuerySettings + { + public int TimeoutInSecs { get; set; } = 30; + public int QueryBatchSize { get; set; } = 10; + } +} \ No newline at end of file diff --git a/src/Schemio.Object/BaseEntity.cs b/src/Schemio.Object/BaseEntity.cs new file mode 100644 index 0000000..202c2d2 --- /dev/null +++ b/src/Schemio.Object/BaseEntity.cs @@ -0,0 +1,7 @@ +namespace Schemio.Object +{ + //public abstract class BaseEntity : IEntity + //{ + // public decimal Version { get; set; } = 1; + //} +} \ No newline at end of file diff --git a/src/Schemio.Object/BaseEntitySchema.cs b/src/Schemio.Object/BaseEntitySchema.cs new file mode 100644 index 0000000..429eda8 --- /dev/null +++ b/src/Schemio.Object/BaseEntitySchema.cs @@ -0,0 +1,19 @@ +namespace Schemio.Object +{ + /// + /// Implement to configure schema path mappings for an Entity. + /// + /// Entity type + public abstract class BaseEntitySchema : IEntitySchema where TEntity : IEntity + { + public IEnumerable> Mappings { get; } + public decimal Version { get; } + + public BaseEntitySchema() + { + Mappings = ConfigureSchema(); + } + + public abstract Mapping[] ConfigureSchema(); + } +} \ No newline at end of file diff --git a/src/Schemio.Object/BaseQuery.cs b/src/Schemio.Object/BaseQuery.cs new file mode 100644 index 0000000..4bd2f74 --- /dev/null +++ b/src/Schemio.Object/BaseQuery.cs @@ -0,0 +1,49 @@ +namespace Schemio.Object +{ + /// + /// 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; + + /// + /// Implement this method to resolve query parameter to execute query in child mode to a parent query. + /// + /// DataContext object passed to dataprovider. + /// Query result of parent query. + public abstract void ResolveParameterInChildMode(IDataContext context, IQueryResult parentQueryResult); + + /// + /// Implement this method to resolve query parameter to execute query in parent mode. + /// + /// DataContext object passed to dataprovider. + public abstract void ResolveParameterInParentMode(IDataContext context); + } +} \ No newline at end of file diff --git a/src/Schemio.Object/BaseTransformer.cs b/src/Schemio.Object/BaseTransformer.cs new file mode 100644 index 0000000..ca0b753 --- /dev/null +++ b/src/Schemio.Object/BaseTransformer.cs @@ -0,0 +1,19 @@ +namespace Schemio.Object +{ + public abstract class BaseTransformer : ITransformer + where T : IEntity + where TD : IQueryResult + { + public IDataContext Context { get; private set; } + public Type SupportedQueryResult => typeof(TD); + + public void ResolveContext(IDataContext context) => Context = context; + + public IEntity Run(IQueryResult queryResult, IEntity entity) + { + return Transform((TD)queryResult, (T)entity); + } + + public abstract T Transform(TD queryResult, T entity); + } +} \ No newline at end of file diff --git a/src/Schemio.Object/ChildrenQueries.cs b/src/Schemio.Object/ChildrenQueries.cs index b1d8dcf..d934593 100644 --- a/src/Schemio.Object/ChildrenQueries.cs +++ b/src/Schemio.Object/ChildrenQueries.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { public class ChildrenQueries { diff --git a/src/Schemio.Object/CollectionResult.cs b/src/Schemio.Object/CollectionResult.cs new file mode 100644 index 0000000..d924b6d --- /dev/null +++ b/src/Schemio.Object/CollectionResult.cs @@ -0,0 +1,15 @@ +namespace Schemio.Object +{ + 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.Object/CreateSchema.cs b/src/Schemio.Object/CreateSchema.cs index c7233a7..7ec8f97 100644 --- a/src/Schemio.Object/CreateSchema.cs +++ b/src/Schemio.Object/CreateSchema.cs @@ -1,18 +1,17 @@ -using System; -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { #region Helpers public class CreateSchema { - public static IMappings For() where T : IEntity => new Mappings { Order = 1 }; + public static IMappings For() where T : IEntity + => new Mappings { Order = 1 }; } public class For { - public static ISchemaPaths Paths(params string[] paths) => new SchemaPaths { Paths = paths }; + public static ISchemaPaths Paths(params string[] paths) + => new SchemaPaths { Paths = paths }; } public class SchemaPaths : ISchemaPaths @@ -40,7 +39,7 @@ public class Mappings : /// public IMapOrComplete Map(ISchemaPaths paths) where TQ : IQuery, new() - where TR : ITransformer, new() => + where TR : ITransformer, new() => Map(paths, null); /// @@ -53,7 +52,7 @@ public IMapOrComplete Map(ISchemaPaths paths) /// public IMapOrComplete Map(ISchemaPaths paths, Func, IMap> dependents) where TQ : IQuery, new() - where TR : ITransformer, new() + where TR : ITransformer, new() { var mapping = new Mapping { @@ -64,13 +63,11 @@ public IMapOrComplete Map(ISchemaPaths paths, Func)dependents(mapping)).GetMappings) { dep.DependentOn ??= mapping.Query; Add(dep); } - } Add(mapping); @@ -90,7 +87,7 @@ public class Mapping : public int Order { get; set; } public ISchemaPaths SchemaPaths { get; set; } public IQuery Query { get; set; } - public ITransformer Transformer { get; set; } + public ITransformer Transformer { get; set; } public IQuery DependentOn { get; set; } public IMappings Dependents => new Mappings { Order = Order + 1 }; @@ -109,11 +106,11 @@ public interface IMap { IMapOrComplete Map(ISchemaPaths paths) where TQ : IQuery, new() - where TR : ITransformer, new(); + where TR : ITransformer, new(); IMapOrComplete Map(ISchemaPaths paths, Func, IMap> dependents) where TQ : IQuery, new() - where TR : ITransformer, new(); + where TR : ITransformer, new(); } public interface IMappings : IMap @@ -140,5 +137,4 @@ public interface IWithDependents } #endregion Fluent Interfaces - } \ No newline at end of file diff --git a/src/Schemio.Object/DataProvider.cs b/src/Schemio.Object/DataProvider.cs index c62d7f7..f4daea4 100644 --- a/src/Schemio.Object/DataProvider.cs +++ b/src/Schemio.Object/DataProvider.cs @@ -1,14 +1,25 @@ using Microsoft.Extensions.Logging; +using Schemio.Object.Impl; -namespace Schemio.Data.Core.Impl +namespace Schemio.Object { - public class DataProvider : IDataProvider where T : IEntity + public class DataProvider : IDataProvider where T : IEntity, new() { private readonly ILogger> logger; private readonly IQueryExecutor queryExecutor; private readonly IQueryBuilder queryBuilder; private readonly ITransformExecutor transformExecutor; + public DataProvider( + ILogger> logger, + IEntitySchema entitySchema, + ISchemaPathMatcher schemaPathMatcher, + IQueryEngine[] queryEngines) + : this(logger, new QueryBuilder(entitySchema, schemaPathMatcher), + new QueryExecutor(queryEngines), new TransformExecutor(entitySchema)) + { + } + public DataProvider( ILogger> logger, IQueryBuilder queryBuilder, diff --git a/src/Schemio.Object/Helpers/ExtentionMethods.cs b/src/Schemio.Object/Helpers/ExtentionMethods.cs index 8d7bb7d..0c099fa 100644 --- a/src/Schemio.Object/Helpers/ExtentionMethods.cs +++ b/src/Schemio.Object/Helpers/ExtentionMethods.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core.Helpers +namespace Schemio.Object.Helpers { public static class ExtentionMethods { diff --git a/src/Schemio.Object/Helpers/Xml/XDocumentExts.cs b/src/Schemio.Object/Helpers/Xml/XDocumentExts.cs index 948ea50..b9fd172 100644 --- a/src/Schemio.Object/Helpers/Xml/XDocumentExts.cs +++ b/src/Schemio.Object/Helpers/Xml/XDocumentExts.cs @@ -1,7 +1,6 @@ -using System.Linq; using System.Xml.Linq; -namespace Schemio.Data.Core.Helpers.Xml +namespace Schemio.Object.Helpers.Xml { public static class XDocumentExts { diff --git a/src/Schemio.Object/Helpers/Xml/XmlHelper.cs b/src/Schemio.Object/Helpers/Xml/XmlHelper.cs index d79b7d0..c0b65c1 100644 --- a/src/Schemio.Object/Helpers/Xml/XmlHelper.cs +++ b/src/Schemio.Object/Helpers/Xml/XmlHelper.cs @@ -2,7 +2,7 @@ using System.Xml; using System.Xml.Serialization; -namespace Schemio.Data.Core.Helpers.Xml +namespace Schemio.Object.Helpers.Xml { public static class XmlHelper { diff --git a/src/Schemio.Object/Helpers/Xml/XmlSanitizer.cs b/src/Schemio.Object/Helpers/Xml/XmlSanitizer.cs index 72b9ffa..bd964ee 100644 --- a/src/Schemio.Object/Helpers/Xml/XmlSanitizer.cs +++ b/src/Schemio.Object/Helpers/Xml/XmlSanitizer.cs @@ -2,7 +2,7 @@ using System.Text.RegularExpressions; using System.Xml; -namespace Schemio.Data.Core.Helpers.Xml +namespace Schemio.Object.Helpers.Xml { public static class XmlSanitizer { diff --git a/src/Schemio.Object/IDataContext.cs b/src/Schemio.Object/IDataContext.cs index 2527a7b..2b38183 100644 --- a/src/Schemio.Object/IDataContext.cs +++ b/src/Schemio.Object/IDataContext.cs @@ -1,8 +1,8 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IDataContext { public string[] Paths { get; set; } - string CurrentVersion { get; } + decimal CurrentVersion { get; } } } \ No newline at end of file diff --git a/src/Schemio.Object/IDataContextValidator.cs b/src/Schemio.Object/IDataContextValidator.cs index a510eac..ca36725 100644 --- a/src/Schemio.Object/IDataContextValidator.cs +++ b/src/Schemio.Object/IDataContextValidator.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IDataContextValidator { diff --git a/src/Schemio.Object/IDataProvider.cs b/src/Schemio.Object/IDataProvider.cs index 9560c8f..48c21a1 100644 --- a/src/Schemio.Object/IDataProvider.cs +++ b/src/Schemio.Object/IDataProvider.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IDataProvider where T : IEntity { diff --git a/src/Schemio.Object/IEntity.cs b/src/Schemio.Object/IEntity.cs index 8e7310b..f4dc6fb 100644 --- a/src/Schemio.Object/IEntity.cs +++ b/src/Schemio.Object/IEntity.cs @@ -1,10 +1,10 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { /// - /// Implement Entity required to be hydrated (with data using query/transformer). + /// Implement Entity required to be hydrated (using query/transformer). /// public interface IEntity { - decimal Version { get; set; } + //decimal Version { get; set; } } } \ No newline at end of file diff --git a/src/Schemio.Object/IEntitySchema.cs b/src/Schemio.Object/IEntitySchema.cs index 885b45b..d37e0f3 100644 --- a/src/Schemio.Object/IEntitySchema.cs +++ b/src/Schemio.Object/IEntitySchema.cs @@ -1,14 +1,12 @@ -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { /// - /// Implement to configure entity schema path mappings (using Query/Trasformer pairs). + /// Implement to configure schema path mappings for an Entity. /// - /// - public interface IEntitySchema where T : IEntity + /// Entity type + public interface IEntitySchema where TEntity : IEntity { - IEnumerable> Mappings { get; } - decimal Version { get; } + public IEnumerable> Mappings { get; } + public decimal Version { get; } } } \ No newline at end of file diff --git a/src/Schemio.Object/IPolymorphicQueryResult.cs b/src/Schemio.Object/IPolymorphicQueryResult.cs index b4955ef..da18ffa 100644 --- a/src/Schemio.Object/IPolymorphicQueryResult.cs +++ b/src/Schemio.Object/IPolymorphicQueryResult.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IPolymorphicQueryResult : IQueryResult { diff --git a/src/Schemio.Object/IQuery.cs b/src/Schemio.Object/IQuery.cs index e8f8c8c..caeb3bd 100644 --- a/src/Schemio.Object/IQuery.cs +++ b/src/Schemio.Object/IQuery.cs @@ -1,21 +1,44 @@ -using System; -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { /// /// Implement IQuery to fetch data using API or database. /// public interface IQuery { - List ChildQueries { get; set; } + List Children { get; set; } - Type GetResultType { get; } + Type ResultType { get; } - void ResolveContextAsPrimary(IDataContext context); + void ResolveParameterInParentMode(IDataContext context); - void ResolveContextAsChild(IDataContext context, IQueryResult parentQueryResult); + void ResolveParameterInChildMode(IDataContext context, IQueryResult parentQueryResult); bool IsContextResolved(); } + + //public interface IParentQuery : IQuery + //{ + // //List Children { get; set; } + + // //Type GetResultType { get; } + + // void ResolveRootQueryParameter(IDataContext context); + + // //void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult); + + // //bool IsContextResolved(); + //} + + //public interface IChildQuery : IQuery + //{ + // //List Children { get; set; } + + // //Type GetResultType { get; } + + // //void ResolveRootQueryParameter(IDataContext context); + + // void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult); + + // //bool IsContextResolved(); + //} } \ No newline at end of file diff --git a/src/Schemio.Object/IQueryBuilder.cs b/src/Schemio.Object/IQueryBuilder.cs index e252fcb..af754c0 100644 --- a/src/Schemio.Object/IQueryBuilder.cs +++ b/src/Schemio.Object/IQueryBuilder.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IQueryBuilder { diff --git a/src/Schemio.Object/IQueryEngine.cs b/src/Schemio.Object/IQueryEngine.cs index 65884f4..9aa554f 100644 --- a/src/Schemio.Object/IQueryEngine.cs +++ b/src/Schemio.Object/IQueryEngine.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IQueryEngine { diff --git a/src/Schemio.Object/IQueryExecutor.cs b/src/Schemio.Object/IQueryExecutor.cs index d655120..7e3bf93 100644 --- a/src/Schemio.Object/IQueryExecutor.cs +++ b/src/Schemio.Object/IQueryExecutor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IQueryExecutor { diff --git a/src/Schemio.Object/IQueryList.cs b/src/Schemio.Object/IQueryList.cs index eb5db9a..bdca13f 100644 --- a/src/Schemio.Object/IQueryList.cs +++ b/src/Schemio.Object/IQueryList.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IQueryList - { + { int QueryDependencyDepth { get; set; } IEnumerable Queries { get; } diff --git a/src/Schemio.Object/IQueryParameter.cs b/src/Schemio.Object/IQueryParameter.cs new file mode 100644 index 0000000..b8bc423 --- /dev/null +++ b/src/Schemio.Object/IQueryParameter.cs @@ -0,0 +1,6 @@ +namespace Schemio.Object +{ + public interface IQueryParameter + { + } +} \ No newline at end of file diff --git a/src/Schemio.Object/IQueryResult.cs b/src/Schemio.Object/IQueryResult.cs index a728f21..122c3c2 100644 --- a/src/Schemio.Object/IQueryResult.cs +++ b/src/Schemio.Object/IQueryResult.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface IQueryResult { diff --git a/src/Schemio.Object/ISchemaPathMatcher.cs b/src/Schemio.Object/ISchemaPathMatcher.cs index 40fb83d..909d981 100644 --- a/src/Schemio.Object/ISchemaPathMatcher.cs +++ b/src/Schemio.Object/ISchemaPathMatcher.cs @@ -1,4 +1,4 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { public interface ISchemaPathMatcher { diff --git a/src/Schemio.Object/ITransformExecutor.cs b/src/Schemio.Object/ITransformExecutor.cs index 58d2c02..e28a627 100644 --- a/src/Schemio.Object/ITransformExecutor.cs +++ b/src/Schemio.Object/ITransformExecutor.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { - public interface ITransformExecutor where T : IEntity + public interface ITransformExecutor where TEntity : IEntity { - T Execute(IDataContext context, IList dtos); + TEntity Execute(IDataContext context, IList results); } } \ No newline at end of file diff --git a/src/Schemio.Object/ITransformer.cs b/src/Schemio.Object/ITransformer.cs index f2b1b42..61266b3 100644 --- a/src/Schemio.Object/ITransformer.cs +++ b/src/Schemio.Object/ITransformer.cs @@ -1,16 +1,16 @@ -namespace Schemio.Data.Core +namespace Schemio.Object { /// - /// Implement transformer to map data to entity using query result. + /// Implement transformer to map data to entity using supported query result. /// - /// - /// - public interface ITransformer - where TR : IQueryResult - where T : IEntity + public interface ITransformer { - void ResovleContext(IDataContext context); + IDataContext Context { get; } - T TransformToDataEntity(TR queryResult, T entity); + Type SupportedQueryResult { get; } + + void ResolveContext(IDataContext context); + + IEntity Run(IQueryResult queryResult, IEntity entity); } } \ No newline at end of file diff --git a/src/Schemio.Object/Impl/EventAggregator.cs b/src/Schemio.Object/Impl/EventAggregator.cs index c74fbc3..1f8cc86 100644 --- a/src/Schemio.Object/Impl/EventAggregator.cs +++ b/src/Schemio.Object/Impl/EventAggregator.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Schemio.Data.Core.Impl +namespace Schemio.Object.Impl { public class EventAggregator { diff --git a/src/Schemio.Object/Impl/EventSubscriber.cs b/src/Schemio.Object/Impl/EventSubscriber.cs index 4f61bc3..758e70d 100644 --- a/src/Schemio.Object/Impl/EventSubscriber.cs +++ b/src/Schemio.Object/Impl/EventSubscriber.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Schemio.Data.Core.Impl +namespace Schemio.Object.Impl { public class EventSubscriber : ISubscriber { @@ -32,9 +29,7 @@ public void OnEventHandler(IDataContext context, ExecutorResultArgs args) continue; foreach (var query in unresolved.Queries) - { - query.ResolveContextAsChild(context, queryResult); - } + query.ResolveParameterInChildMode(context, queryResult); } } @@ -54,9 +49,7 @@ public void OnEventHandler(IDataContext context, ExecutorResultArgs args) continue; foreach (var query in unresolved.Queries) - { - query.ResolveContextAsChild(context, queryResult); - } + query.ResolveParameterInChildMode(context, queryResult); } } } diff --git a/src/Schemio.Object/Impl/QueryBuilder.cs b/src/Schemio.Object/Impl/QueryBuilder.cs index 0ca15d9..a88c8a5 100644 --- a/src/Schemio.Object/Impl/QueryBuilder.cs +++ b/src/Schemio.Object/Impl/QueryBuilder.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Schemio.Data.Core.Impl +namespace Schemio.Object.Impl { public class QueryBuilder : IQueryBuilder where T : IEntity { @@ -24,7 +21,7 @@ public IQueryList Build(IDataContext context) var queries = GetMappedQueries(entitySchema.Mappings.ToList(), context); foreach (var query in queries.Queries) - query.ResolveContextAsPrimary(context); + query.ResolveParameterInParentMode(context); return new QueryList(queries.Queries); } @@ -40,11 +37,11 @@ private QueryList GetMappedQueries(IReadOnlyCollection> foreach (var map in maps) { var dependentQueries = - mappings.Where(x => x.Order == (index + 1) && x.DependentOn != null && x.DependentOn.GetType() == map.Query.GetType()).ToList(); + mappings.Where(x => x.Order == index + 1 && x.DependentOn != null && x.DependentOn.GetType() == map.Query.GetType()).ToList(); - map.Query.ChildQueries ??= new List(); + map.Query.Children ??= new List(); - map.Query.ChildQueries.AddRange(FilterByPaths(context, dependentQueries)); + map.Query.Children.AddRange(FilterByPaths(context, dependentQueries)); } } diff --git a/src/Schemio.Object/Impl/QueryExecutor.cs b/src/Schemio.Object/Impl/QueryExecutor.cs index d9e1af6..25f9b49 100644 --- a/src/Schemio.Object/Impl/QueryExecutor.cs +++ b/src/Schemio.Object/Impl/QueryExecutor.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Schemio.Data.Core.Impl +namespace Schemio.Object.Impl { public class QueryExecutor : IQueryExecutor { diff --git a/src/Schemio.Object/Impl/TransformExecutor.cs b/src/Schemio.Object/Impl/TransformExecutor.cs index c834853..19c17a2 100644 --- a/src/Schemio.Object/Impl/TransformExecutor.cs +++ b/src/Schemio.Object/Impl/TransformExecutor.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Schemio.Data.Core.Impl +namespace Schemio.Object.Impl { public class TransformExecutor : ITransformExecutor where T : IEntity, new() { - private readonly IEntitySchema entitySchemaMapping; + private readonly IEntitySchema entitySchema; - public TransformExecutor(IEntitySchema entitySchemaMapping) + public TransformExecutor(IEntitySchema entitySchema) { - this.entitySchemaMapping = entitySchemaMapping; + this.entitySchema = entitySchema; } /// @@ -20,15 +17,15 @@ public TransformExecutor(IEntitySchema entitySchemaMapping) /// public T Execute(IDataContext context, IList queryResults) { - var entity = new T { Version = entitySchemaMapping.Version }; + var entity = new T { /*Version = entitySchemaMapping.Version*/ }; if (queryResults == null || !queryResults.Any()) return entity; - var mappings = entitySchemaMapping.Mappings.ToList(); + var mappings = entitySchema.Mappings.ToList(); // resolve context of each transformer so it is available inside for transformation if required. - mappings.ForEach(mapping => mapping.Transformer.ResovleContext(context)); + mappings.ForEach(mapping => mapping.Transformer.ResolveContext(context)); var queryDependencyDepth = mappings.Max(x => x.Order); @@ -42,7 +39,8 @@ public T Execute(IDataContext context, IList queryResults) .ToList(); foreach (var queryResult in queryResults) - transformers.ForEach(transformer => transformer.TransformToDataEntity(queryResult, entity)); + transformers.Where(transformer => transformer.SupportedQueryResult == queryResult.GetType()).ToList() + .ForEach(supportedtransformer => supportedtransformer.Run(queryResult, entity)); } return entity; diff --git a/src/Schemio.Object/PathMatchers/JPathMatcher.cs b/src/Schemio.Object/PathMatchers/JPathMatcher.cs new file mode 100644 index 0000000..a4c7f9e --- /dev/null +++ b/src/Schemio.Object/PathMatchers/JPathMatcher.cs @@ -0,0 +1,12 @@ +using Schemio.Object.Helpers; + +namespace Schemio.Object.Pathmatchers +{ + public class JPathMatcher : ISchemaPathMatcher + { + public bool IsMatch(string inputXPath, ISchemaPaths configuredXPaths) => + // Does the template xpath contain any of the mapping xpaths? + inputXPath.IsNotNullOrEmpty() + && configuredXPaths.Paths.Any(x => inputXPath.ToLower().Contains(x.ToLower())); + } +} \ No newline at end of file diff --git a/src/Schemio.Object/JSON/JPathMatcher.cs b/src/Schemio.Object/PathMatchers/XPathMatcher.cs similarity index 82% rename from src/Schemio.Object/JSON/JPathMatcher.cs rename to src/Schemio.Object/PathMatchers/XPathMatcher.cs index 0f18bc3..351625f 100644 --- a/src/Schemio.Object/JSON/JPathMatcher.cs +++ b/src/Schemio.Object/PathMatchers/XPathMatcher.cs @@ -1,10 +1,9 @@ -using Schemio.Data.Core.Helpers; -using System.Linq; +using Schemio.Object.Helpers; using System.Text.RegularExpressions; -namespace Schemio.Data.Core.JSON +namespace Schemio.Object.PathMatchers { - public class JPathMatcher : ISchemaPathMatcher + public class XPathMatcher : ISchemaPathMatcher { private static readonly Regex ancestorRegex = new Regex(@"=ancestor::(?'path'.*?)(/@|\[.*\]/@)", RegexOptions.Compiled); diff --git a/src/Schemio.Object/QueryComparer.cs b/src/Schemio.Object/QueryComparer.cs index bb2277a..d9aeb69 100644 --- a/src/Schemio.Object/QueryComparer.cs +++ b/src/Schemio.Object/QueryComparer.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Schemio.Data.Core +namespace Schemio.Object { public class QueryComparer : IEqualityComparer { diff --git a/src/Schemio.Object/QueryList.cs b/src/Schemio.Object/QueryList.cs index 088fb73..e7e5b5e 100644 --- a/src/Schemio.Object/QueryList.cs +++ b/src/Schemio.Object/QueryList.cs @@ -1,18 +1,16 @@ -using System.Collections.Generic; -using System.Linq; - - -namespace Schemio.Data.Core +namespace Schemio.Object { public class QueryList : IQueryList { private readonly List queryList; + public QueryList() { queryList = new List(); } - public IEnumerable Queries { get { return queryList; } } + public IEnumerable Queries + { get { return queryList; } } public QueryList(IEnumerable collection) { @@ -32,7 +30,7 @@ public IQueryList GetByType() where T : class public List GetChildrenQueries() { var childrenQueries = queryList - .Select(x => new ChildrenQueries { ParentQueryResultType = x.GetResultType, Queries = x.ChildQueries }) + .Select(x => new ChildrenQueries { ParentQueryResultType = x.ResultType, Queries = x.Children }) .Where(x => x.Queries.Any()) .ToList(); diff --git a/src/Schemio.Object/Schemio.Object.csproj b/src/Schemio.Object/Schemio.Object.csproj index 8e7183f..46c8ba1 100644 --- a/src/Schemio.Object/Schemio.Object.csproj +++ b/src/Schemio.Object/Schemio.Object.csproj @@ -1,7 +1,11 @@ - + - netstandard2.1 + net6.0 + enable + enable + + false diff --git a/src/Schemio.Object/XML/XMLDataProvider.cs b/src/Schemio.Object/XML/XMLDataProvider.cs index 7c02cd2..7080649 100644 --- a/src/Schemio.Object/XML/XMLDataProvider.cs +++ b/src/Schemio.Object/XML/XMLDataProvider.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Logging; -using Schemio.Data.Core.Helpers.Xml; +using Microsoft.Extensions.Logging; +using Schemio.Object.Helpers.Xml; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; -namespace Schemio.Data.Core.XML +namespace Schemio.Object.XML { internal class XMLDataProvider where T : IEntity { diff --git a/src/Schemio.Object/XML/XPathMatcher.cs b/src/Schemio.Object/XML/XPathMatcher.cs deleted file mode 100644 index b4942a1..0000000 --- a/src/Schemio.Object/XML/XPathMatcher.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Schemio.Data.Core.Helpers; -using System.Linq; - -namespace Schemio.Data.Core.XML -{ - public class XPathMatcher : ISchemaPathMatcher - { - public bool IsMatch(string inputXPath, ISchemaPaths configuredXPaths) => - // Does the template xpath contain any of the mapping xpaths? - inputXPath.IsNotNullOrEmpty() - && configuredXPaths.Paths.Any(x => inputXPath.ToLower().Contains(x.ToLower())); - } -} \ No newline at end of file diff --git a/tests/Schemio.Object.SQL.Tests/GlobalUsings.cs b/tests/Schemio.Object.SQL.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/tests/Schemio.Object.SQL.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/tests/Schemio.Object.SQL.Tests/Schemio.Object.SQL.Tests.csproj b/tests/Schemio.Object.SQL.Tests/Schemio.Object.SQL.Tests.csproj new file mode 100644 index 0000000..f3d8962 --- /dev/null +++ b/tests/Schemio.Object.SQL.Tests/Schemio.Object.SQL.Tests.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/tests/Schemio.Object.SQL.Tests/UnitTest1.cs b/tests/Schemio.Object.SQL.Tests/UnitTest1.cs new file mode 100644 index 0000000..d3f5fe0 --- /dev/null +++ b/tests/Schemio.Object.SQL.Tests/UnitTest1.cs @@ -0,0 +1,20 @@ +using System.Data.Common; +using Microsoft.Data.Sqlite; + +namespace Schemio.Object.SQL.Tests +{ + public class Tests + { + [SetUp] + public void Setup() + { + DbProviderFactories.RegisterFactory("System.Data.SQLite", SqliteFactory.Instance); + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/DataProvider.Tests/DataProviderTests.cs b/tests/Schemio.Object.Tests/DataProvider.Tests/DataProviderTests.cs new file mode 100644 index 0000000..2165b92 --- /dev/null +++ b/tests/Schemio.Object.Tests/DataProvider.Tests/DataProviderTests.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using Schemio.Object.Tests.EntitySetup; +using Schemio.Object.Tests.EntitySetup.Entities; + +namespace Schemio.Object.Tests.DataProvider +{ + [TestFixture] + internal class DataProviderTests + { + private DataProvider _provider; + private Mock>> _logger; + private Mock> _queryBuilder; + private Mock _queryExecutor; + private Mock> _transformExecutor; + + [SetUp] + public void Setup() + { + _logger = new Mock>>(); + _queryBuilder = new Mock>(); + _queryExecutor = new Mock(); + _transformExecutor = new Mock>(); + + _provider = new DataProvider(_logger.Object, _queryBuilder.Object, _queryExecutor.Object, _transformExecutor.Object); + } + + [Test] + public void TestDataProvider() + { + var context = new CustomerContext { CustomerId = 1 }; + + _provider.GetData(context); + + _queryBuilder.Verify(x => x.Build(context), Times.Once); + _queryExecutor.Verify(x => x.Execute(context, It.IsAny()), Times.Once); + _transformExecutor.Verify(x => x.Execute(context, It.IsAny>()), Times.Once); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/DataProvider.Tests/QueryBuilderTests.cs b/tests/Schemio.Object.Tests/DataProvider.Tests/QueryBuilderTests.cs new file mode 100644 index 0000000..1b71198 --- /dev/null +++ b/tests/Schemio.Object.Tests/DataProvider.Tests/QueryBuilderTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup; +using Schemio.Object.Impl; +using Schemio.Object.Tests.EntitySetup.EntitySchemas; +using Schemio.Object.PathMatchers; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.DataProvider +{ + [TestFixture] + internal class QueryBuilderTests + { + private QueryBuilder _queryBuilder; + + private IEntitySchema _entitySchema; + private ISchemaPathMatcher _schemaPathMatcher; + + [SetUp] + public void Setup() + { + _entitySchema = new CustomerSchema(); + + /*----------------------------------------- * + * + * 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"))) + ) + * + * --------------------------------------- */ + + _schemaPathMatcher = new XPathMatcher(); + _queryBuilder = new QueryBuilder(_entitySchema, _schemaPathMatcher); + } + + [Test] + public void TestQueryBuilderForCorrectParentQueryList() + { + var context = new CustomerContext { CustomerId = 1, Paths = new[] { "customer/customercode" } }; + + var result = _queryBuilder.Build(context); + + Assert.IsNotNull(result); + + // returns parent query with filtered out child communication query. + + Assert.That(result.QueryDependencyDepth == 0); + Assert.That(result.Queries.Count, Is.EqualTo(1)); + Assert.That(result.Queries.ElementAt(0).Children.Count, Is.EqualTo(0)); + + var parentQuery = result.Queries.First(); + Assert.That(parentQuery.GetType() == typeof(CustomerQuery)); + } + + [Test] + public void TestQueryBuilderForCorrectParentQueryListWithOneChildren() + { + var context = new CustomerContext { CustomerId = 1, Paths = new[] { "customer/customercode", "customer/communication" } }; + + var result = _queryBuilder.Build(context); + + Assert.IsNotNull(result); + + // returns parent query with filtered out child communication query. + + Assert.That(result.QueryDependencyDepth == 0); + Assert.That(result.Queries.Count, Is.EqualTo(1)); + Assert.That(result.Queries.ElementAt(0).Children.Count, Is.EqualTo(1)); + + var parentQuery = result.Queries.First(); + Assert.That(parentQuery.GetType() == typeof(CustomerQuery)); + + var childQuery = parentQuery.Children.First(); + Assert.That(childQuery.GetType() == typeof(CustomerCommunicationQuery)); + } + + [Test] + public void TestQueryBuilderForCorrectParentQueryListWithTwoChildren() + { + var context = new CustomerContext { CustomerId = 1, Paths = new[] { "customer/customercode", "customer/communication", "customer/orders" } }; + + var result = _queryBuilder.Build(context); + + Assert.IsNotNull(result); + + // returns parent query with filtered out children - communication & orders query. + + Assert.That(result.QueryDependencyDepth == 0); + Assert.That(result.Queries.Count, Is.EqualTo(1)); + Assert.That(result.Queries.ElementAt(0).Children.Count, Is.EqualTo(2)); + + 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)); + + Assert.IsNotNull(communicationChildQuery); + Assert.IsNotNull(ordersChildQuery); + + // nested child query for order item not included as order items are excluded from paths + Assert.That(ordersChildQuery.Children.Count, Is.EqualTo(0)); + } + + [Test] + public void TestQueryBuilderForCorrectParentQueryListWithTwoChildrenAndOneChildFurtherNestedChildQuery() + { + var context = new CustomerContext { CustomerId = 1, Paths = new[] { "customer/customercode", "customer/communication", "customer/orders", "customer/orders/order/items" } }; + + var result = _queryBuilder.Build(context); + + Assert.IsNotNull(result); + + // returns parent query with filtered out children - communication & orders query. + + Assert.That(result.QueryDependencyDepth == 0); + Assert.That(result.Queries.Count, Is.EqualTo(1)); + Assert.That(result.Queries.ElementAt(0).Children.Count, Is.EqualTo(2)); + + 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)); + + Assert.IsNotNull(communicationChildQuery); + Assert.IsNotNull(ordersChildQuery); + + // 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); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/DataProvider.Tests/QueryExecutorTests.cs b/tests/Schemio.Object.Tests/DataProvider.Tests/QueryExecutorTests.cs new file mode 100644 index 0000000..87a8163 --- /dev/null +++ b/tests/Schemio.Object.Tests/DataProvider.Tests/QueryExecutorTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Moq; +using Schemio.Object.Impl; +using Schemio.Object.Tests.EntitySetup; +using Schemio.Object.Tests.EntitySetup.Entities; +using static Schemio.Object.Tests.DataProvider.TransformExecutorTests; + +namespace Schemio.Object.Tests.DataProvider.Tests +{ + [TestFixture] + internal class QueryExecutorTests + { + private QueryExecutor _queryExecutor; + private Mock _queryEngine; + + [SetUp] + public void Setup() + { + _queryEngine = new Mock(); + _queryExecutor = new QueryExecutor(new[] { _queryEngine.Object }); + } + + [Test] + public void TestQueryExecutorForCorrectExecutionOfConfiguredQueries() + { + _queryExecutor.Execute(new CustomerContext(), null); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/DataProvider.Tests/TransformExecutorTests.cs b/tests/Schemio.Object.Tests/DataProvider.Tests/TransformExecutorTests.cs new file mode 100644 index 0000000..2f08ac8 --- /dev/null +++ b/tests/Schemio.Object.Tests/DataProvider.Tests/TransformExecutorTests.cs @@ -0,0 +1,92 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Impl; +using Schemio.Object.Tests.EntitySetup; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.DataProvider +{ + [TestFixture] + internal class TransformExecutorTests + { + private TransformExecutor _transformExecutor; + private IEntitySchema _entitySchema; + + private static List<(Type result, int InvocationCount)> TransformerInvocations; + + [SetUp] + public void Setup() + { + _entitySchema = new MockCustomerSchema(); + _transformExecutor = new TransformExecutor(_entitySchema); + TransformerInvocations = new List<(Type result, int InvocationCount)>(); + } + + [Test] + 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() + }; + + var entity = _transformExecutor.Execute(new CustomerContext { CustomerId = 1 }, queryList); + + var customerTransforms = TransformerInvocations.Where(x => x.result == typeof(CustomerResult)); + Assert.That(customerTransforms.Count() == 1); + Assert.That(customerTransforms.ElementAt(0).InvocationCount == 1); + + var communicationTransforms = TransformerInvocations.Where(x => x.result == typeof(CommunicationResult)); + Assert.That(communicationTransforms.Count() == 1); + Assert.That(communicationTransforms.ElementAt(0).InvocationCount == 1); + + var orderCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(OrderCollectionResult)); + Assert.That(orderCollectionTransforms.Count() == 1); + Assert.That(orderCollectionTransforms.ElementAt(0).InvocationCount == 1); + + var orderItemsCollectionTransforms = TransformerInvocations.Where(x => x.result == typeof(OrderItemCollectionResult)); + Assert.That(orderItemsCollectionTransforms.Count() == 1); + Assert.That(orderItemsCollectionTransforms.ElementAt(0).InvocationCount == 1); + + Assert.IsNotNull(entity); + } + + public class MockTransform : BaseTransformer + where TEntity : IEntity + where TQueryResult : IQueryResult + { + public override TEntity Transform(TQueryResult queryResult, TEntity entity) + { + TransformerInvocations.Add((queryResult.GetType(), 1)); + return entity; + } + } + + internal class MockCustomerSchema : IEntitySchema + { + private IEnumerable> mappings; + + private decimal version; + + public IEnumerable> Mappings => mappings; + public decimal Version => version; + + public MockCustomerSchema() + { + version = 1; + + // Create an object mapping graph of query and transformer pairs using xpaths. + mappings = 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"))) + ).Complete(); + } + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/CustomerContext.cs b/tests/Schemio.Object.Tests/EntitySetup/CustomerContext.cs new file mode 100644 index 0000000..886927e --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/CustomerContext.cs @@ -0,0 +1,9 @@ +namespace Schemio.Object.Tests.EntitySetup +{ + internal class CustomerContext : IDataContext + { + public string[] Paths { get; set; } + public decimal CurrentVersion => 1; + public int CustomerId { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Entities/Address.cs b/tests/Schemio.Object.Tests/EntitySetup/Entities/Address.cs new file mode 100644 index 0000000..362e645 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/Address.cs @@ -0,0 +1,12 @@ +namespace Schemio.Object.Tests.EntitySetup.Entities +{ + public class Address + { + 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.Object.Tests/EntitySetup/Entities/Communication.cs b/tests/Schemio.Object.Tests/EntitySetup/Entities/Communication.cs new file mode 100644 index 0000000..466e1a8 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/Communication.cs @@ -0,0 +1,10 @@ +namespace Schemio.Object.Tests.EntitySetup.Entities +{ + public class Communication + { + public int ContactId { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public Address Address { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Entities/Customer.cs b/tests/Schemio.Object.Tests/EntitySetup/Entities/Customer.cs new file mode 100644 index 0000000..1817594 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/Customer.cs @@ -0,0 +1,11 @@ +namespace Schemio.Object.Tests.EntitySetup.Entities +{ + 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; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Entities/EntityDiagram.cd b/tests/Schemio.Object.Tests/EntitySetup/Entities/EntityDiagram.cd new file mode 100644 index 0000000..3013839 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/EntityDiagram.cd @@ -0,0 +1,50 @@ + + + + + + AAEAACAAAAAAAAAAAAAAAAAAQAAAABAAAAAAAAIAAAA= + EntitySetup\Entities\Customer.cs + + + + + + + + + + + QAAAAAAAgAAAAAAIIAAAAAAAABIAAAAAAAAAAAAAAAA= + EntitySetup\Entities\Address.cs + + + + + + AAAAAAAAACAAAAAAAAAAAAAAAAAAAAECAAAAIAAAAAA= + EntitySetup\Entities\Communication.cs + + + + + + + + + EAAAAAAAAAAACAAAAAAAAAAAAAAAAAABAAAAAAAAAAI= + EntitySetup\Entities\Order.cs + + + + + + + + + AAAAAAAAAAAAAAAAgAAAABQAAAAAAAAAAAAAAAAAAAA= + EntitySetup\Entities\OrderItem.cs + + + + \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Entities/Order.cs b/tests/Schemio.Object.Tests/EntitySetup/Entities/Order.cs new file mode 100644 index 0000000..cdc90d2 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/Order.cs @@ -0,0 +1,10 @@ +namespace Schemio.Object.Tests.EntitySetup.Entities +{ + public class Order + { + public int OrderId { get; set; } + public string OrderNo { get; set; } + public DateTime Date { get; set; } + public OrderItem[] Items { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Entities/OrderItem.cs b/tests/Schemio.Object.Tests/EntitySetup/Entities/OrderItem.cs new file mode 100644 index 0000000..5fa1771 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Entities/OrderItem.cs @@ -0,0 +1,9 @@ +namespace Schemio.Object.Tests.EntitySetup.Entities +{ + 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.Object.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs b/tests/Schemio.Object.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs new file mode 100644 index 0000000..b21e673 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/EntitySchemas/CustomerSchema.cs @@ -0,0 +1,31 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup.Queries; +using Schemio.Object.Tests.EntitySetup.Transforms; + +namespace Schemio.Object.Tests.EntitySetup.EntitySchemas +{ + internal class CustomerSchema : IEntitySchema + { + private IEnumerable> mappings; + + private decimal version; + + public IEnumerable> Mappings => mappings; + public decimal Version => version; + + public CustomerSchema() + { + version = 1; + + // Create an object mapping graph of query and transformer pairs using xpaths. + mappings = 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"))) + ).Complete(); + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Queries/CommunicationResult.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CommunicationResult.cs new file mode 100644 index 0000000..29d720b --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CommunicationResult.cs @@ -0,0 +1,14 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + public class CommunicationResult : IQueryResult + { + public int Id { get; set; } + public string Telephone { get; set; } + public string Email { 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.Object.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs new file mode 100644 index 0000000..3c19b86 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerCommunicationQuery.cs @@ -0,0 +1,20 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + internal class CustomerCommunicationQuery : BaseQuery + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Does not execute as root or level 1 queries. + } + + public override void ResolveParameterInChildMode(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.Object.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs new file mode 100644 index 0000000..eba45be --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerOrderItemsQuery.cs @@ -0,0 +1,20 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + internal class CustomerOrderItemsQuery : BaseQuery> + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Does not execute as root or level 1 queries. + } + + public override void ResolveParameterInChildMode(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.Object.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs new file mode 100644 index 0000000..e2d6ebf --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerOrdersQuery.cs @@ -0,0 +1,20 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + internal class CustomerOrdersQuery : BaseQuery> + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Executes as root or level 1 query. + var customer = (CustomerContext)context; + QueryParameter = new CustomerParameter + { + CustomerId = customer.CustomerId + }; + } + + public override void ResolveParameterInChildMode(IDataContext context, IQueryResult parentQueryResult) + { + // Does not execute as child to any query. + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerParameter.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerParameter.cs new file mode 100644 index 0000000..7e2a7a0 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerParameter.cs @@ -0,0 +1,7 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + public class CustomerParameter : IQueryParameter + { + public int CustomerId { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerQuery.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerQuery.cs new file mode 100644 index 0000000..77debdd --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerQuery.cs @@ -0,0 +1,20 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + public class CustomerQuery : BaseQuery + { + public override void ResolveParameterInParentMode(IDataContext context) + { + // Executes as root or level 1 query. + var customer = (CustomerContext)context; + QueryParameter = new CustomerParameter + { + CustomerId = customer.CustomerId + }; + } + + public override void ResolveParameterInChildMode(IDataContext context, IQueryResult parentQueryResult) + { + // Does not execute as child to any query. + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerResult.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerResult.cs new file mode 100644 index 0000000..c67dd65 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/CustomerResult.cs @@ -0,0 +1,9 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + public class CustomerResult : IQueryResult + { + public int Id { get; set; } + public string CustomerCode { get; set; } + public string CustomerName { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderCollectionResult.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderCollectionResult.cs new file mode 100644 index 0000000..7ceb0d6 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderCollectionResult.cs @@ -0,0 +1,15 @@ +namespace Schemio.Object.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.Object.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs new file mode 100644 index 0000000..0f4bcc2 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderItemCollectionResult.cs @@ -0,0 +1,13 @@ +namespace Schemio.Object.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.Object.Tests/EntitySetup/Queries/OrderItemParameter.cs b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderItemParameter.cs new file mode 100644 index 0000000..eed9b6e --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Queries/OrderItemParameter.cs @@ -0,0 +1,7 @@ +namespace Schemio.Object.Tests.EntitySetup.Queries +{ + internal class OrderItemParameter : IQueryParameter + { + public List OrderIds { get; set; } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs new file mode 100644 index 0000000..df7b6ca --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerCommunicationTransform.cs @@ -0,0 +1,31 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.EntitySetup.Transforms +{ + public class CustomerCommunicationTransform : BaseTransformer + { + public override Customer Transform(CommunicationResult queryResult, Customer entity) + { + var customer = entity ?? new Customer(); + customer.Communication = new Communication + { + ContactId = queryResult.Id, + Email = queryResult.Email, + Phone = queryResult.Telephone + }; + + if (queryResult.HouseNo != null) + customer.Communication.Address = new Address + { + HouseNo = queryResult.HouseNo, + City = queryResult.City, + Country = queryResult.Country, + PostalCode = queryResult.PostalCode, + Region = queryResult.Region + }; + + return customer; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs new file mode 100644 index 0000000..cefb715 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrderItemsTransform.cs @@ -0,0 +1,26 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.EntitySetup.Transforms +{ + public class CustomerOrderItemsTransform : BaseTransformer, Customer> + { + public override Customer Transform(CollectionResult queryResult, Customer entity) + { + if (queryResult?.Items == null || entity?.Orders == null) + return entity; + + foreach (var item in queryResult.Items.Where(x => x.Items != null)) + foreach (var order in entity.Orders) + if (order.OrderId == item.OrderId) + order.Items = item.Items.Select(x => new OrderItem + { + ItemId = x.ItemId, + Name = x.Name, + Cost = x.Cost + }).ToArray(); + + return entity; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs new file mode 100644 index 0000000..50b0300 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerOrdersTransform.cs @@ -0,0 +1,24 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.EntitySetup.Transforms +{ + public class CustomerOrdersTransform : BaseTransformer, Customer> + { + public override Customer Transform(CollectionResult queryResult, Customer entity) + { + if (queryResult?.Items == null) + return entity; + + var customer = entity ?? new Customer(); + customer.Orders = queryResult.Items.Select(x => new Order + { + Date = x.Date, + OrderId = x.OrderId, + OrderNo = x.OrderNo + }).ToArray(); + + return customer; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerTransform.cs b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerTransform.cs new file mode 100644 index 0000000..cdc00a7 --- /dev/null +++ b/tests/Schemio.Object.Tests/EntitySetup/Transforms/CustomerTransform.cs @@ -0,0 +1,17 @@ +using Schemio.Object.Tests.EntitySetup.Entities; +using Schemio.Object.Tests.EntitySetup.Queries; + +namespace Schemio.Object.Tests.EntitySetup.Transforms +{ + public 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; + } + } +} \ No newline at end of file diff --git a/tests/Schemio.Object.Tests/Schemio.Object.Tests.csproj b/tests/Schemio.Object.Tests/Schemio.Object.Tests.csproj index 4d140f4..3aa0550 100644 --- a/tests/Schemio.Object.Tests/Schemio.Object.Tests.csproj +++ b/tests/Schemio.Object.Tests/Schemio.Object.Tests.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -10,6 +10,7 @@ + @@ -17,11 +18,8 @@ - + - - - diff --git a/tests/Schemio.Object.Tests/UnitTest1.cs b/tests/Schemio.Object.Tests/UnitTest1.cs index c8f2236..8661426 100644 --- a/tests/Schemio.Object.Tests/UnitTest1.cs +++ b/tests/Schemio.Object.Tests/UnitTest1.cs @@ -1,4 +1,4 @@ -namespace Data2Xml.Tests +namespace Schemio.Object.Tests { public class Tests {