From b40c1e1578840e0515c2bd9f57bcb6b5304ce206 Mon Sep 17 00:00:00 2001 From: Christiaan Knaap <2102470+cknaap@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:03:30 +0100 Subject: [PATCH 1/2] fix: add support for ResourceTypesNotValue --- .../RelationalQueryFactory.cs | 660 ++++++++++-------- 1 file changed, 352 insertions(+), 308 deletions(-) diff --git a/Vonk.Facade.Relational/RelationalQueryFactory.cs b/Vonk.Facade.Relational/RelationalQueryFactory.cs index 4e3ee2a..e75adb3 100644 --- a/Vonk.Facade.Relational/RelationalQueryFactory.cs +++ b/Vonk.Facade.Relational/RelationalQueryFactory.cs @@ -1,368 +1,412 @@ -using Microsoft.EntityFrameworkCore; -using System; +using System; using System.Linq; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using Vonk.Core.Common; using Vonk.Core.Repository; using Vonk.Core.Repository.ResultShaping; using Vonk.Core.Support; -namespace Vonk.Facade.Relational +namespace Vonk.Facade.Relational; + +public abstract class RelationalQueryFactory : IRepoQueryFactory where E : class where Q : RelationalQuery, new() { - public abstract class RelationalQueryFactory : IRepoQueryFactory where E : class where Q : RelationalQuery, new() + protected string ForResourceType { get; } + protected DbContext OnContext { get; } + + protected RelationalQueryFactory(string forResourceType, DbContext onContext) { - protected string ForResourceType { get; } - protected DbContext OnContext { get; } + Check.NotNull(forResourceType); + Check.NotNull(onContext); + ForResourceType = forResourceType; + OnContext = onContext; + } - protected RelationalQueryFactory(string forResourceType, DbContext onContext) - { - Check.NotNull(forResourceType, nameof(forResourceType)); - Check.NotNull(onContext, nameof(onContext)); - ForResourceType = forResourceType; - OnContext = onContext; - } + /// + /// Default implementation of And, can be used on any subquery. + /// + /// + /// + /// + public virtual Q And(Q left, Q right) + { + if (left == null) + return right; + if (right == null) + return left; - /// - /// Default implementation of And, can be used on any subquery. - /// - /// - /// - /// - public virtual Q And(Q left, Q right) + return new Q() { - if (left == null) - return right; - if (right == null) - return left; + InternalShapes = left.Shapes.SafeUnion(right.Shapes)?.ToArray(), + Predicate = LinqKitExtensions.And(left.Predicate, right.Predicate) + }; + } - return new Q() - { - InternalShapes = left.Shapes.SafeUnion(right.Shapes)?.ToArray(), - Predicate = LinqKitExtensions.And(left.Predicate, right.Predicate) - }; - } + /// + /// Default implementation of Or, only valid for combining values from a choice. + /// + /// + /// + /// + public virtual Q Or(Q left, Q right) + { + if (left == null) + return right; + if (right == null) + return left; + if (left.Shapes.HasAny() || right.Shapes.HasAny()) + throw new NotSupportedException("Or is only allowed for choice values"); + return new Q() { Predicate = LinqKitExtensions.Or(left?.Predicate, right?.Predicate) }; + } - /// - /// Default implementation of Or, only valid for combining values from a choice. - /// - /// - /// - /// - public virtual Q Or(Q left, Q right) - { - if (left == null) - return right; - if (right == null) - return left; - if (left.Shapes.HasAny() || right.Shapes.HasAny()) - throw new NotSupportedException("Or is only allowed for choice values"); - return new Q() { Predicate = LinqKitExtensions.Or(left?.Predicate, right?.Predicate) }; - } + public Q Not(Q query) + { + return new Q() { Predicate = Expression.Lambda>(Expression.Not(query.Predicate.Body), query.Predicate.Parameters) }; + } - public Q Not(Q query) + /// + /// Default implementation for filtering on . + /// The assumption is that most Facades will not support Contained resources. + /// Therefore it throws a if only is specified. + /// Does not apply a filter otherwise. + /// + /// + /// + public virtual Q EntryContained(ResourceContained contained) + { + if (contained == ResourceContained.Container || contained == ResourceContained.All) { - return new Q() { Predicate = Expression.Lambda>(Expression.Not(query.Predicate.Body), query.Predicate.Parameters) }; + return default(Q); } - - /// - /// Default implementation for filtering on . - /// The assumption is that most Facades will not support Contained resources. - /// Therefore it throws a if only is specified. - /// Does not apply a filter otherwise. - /// - /// - /// - public virtual Q EntryContained(ResourceContained contained) { - if (contained == ResourceContained.Container || contained == ResourceContained.All) - { - return default(Q); - } - { - throw new NotSupportedException("System does not support contained resources."); - } + throw new NotSupportedException("System does not support contained resources."); } + } - /// - /// Default implementation for filtering on . - /// The assumption is that most Facades will not support old versions of resources. - /// Therefore it throws a if only is specified. - /// Does not apply a filter otherwise. - /// - /// - /// - public virtual Q EntryCurrency(ResourceCurrency currency) + /// + /// Default implementation for filtering on . + /// The assumption is that most Facades will not support old versions of resources. + /// Therefore it throws a if only is specified. + /// Does not apply a filter otherwise. + /// + /// + /// + public virtual Q EntryCurrency(ResourceCurrency currency) + { + if (currency == ResourceCurrency.Current || currency == ResourceCurrency.All) { - if (currency == ResourceCurrency.Current || currency == ResourceCurrency.All) - { - return default(Q); - } - else - { - throw new NotSupportedException("System does not support selection of only history items"); - } + return default(Q); } - - /// - /// Default implementation for filtering on . - /// The assumption is that most Facades will not support deleted resources, and cannot distinguish created from updated resources. - /// Therefore it throws a if only is specified. - /// Does not apply a filter otherwise. - /// - /// - /// - public virtual Q EntryChange(ResourceChange[] changes) + else { - if (changes.Contains(ResourceChange.Created) || changes.Contains(ResourceChange.Updated)) - { - return default(Q); - } - else - { - throw new NotSupportedException("System does not support selection of deleted items"); - } + throw new NotSupportedException("System does not support selection of only history items"); } + } - /// - /// Repositories can in theory support >1 versions of FHIR. (Firely Server itself does that.) - /// This method should return a that filters on the FHIR version. - /// If your facade only supports 1 FHIR version, you only need to check whether - /// the provided value of represents that version. - /// You can check it against the values in . Example: - /// - /// if (VonkConstants.Model.FhirR3.Equals(informationModel)) - /// { - /// return default; - /// } - /// - /// - /// - /// - /// if (VonkConstants.Model.FhirR3.Equals(informationModel)) - /// { - /// return default; - /// } - /// - /// - /// FHIR version that the resource must have. - /// - public virtual Q EntryInformationModel(string informationModel) + /// + /// Default implementation for filtering on . + /// The assumption is that most Facades will not support deleted resources, and cannot distinguish created from updated resources. + /// Therefore it throws a if only is specified. + /// Does not apply a filter otherwise. + /// + /// + /// + public virtual Q EntryChange(ResourceChange[] changes) + { + if (changes.Contains(ResourceChange.Created) || changes.Contains(ResourceChange.Updated)) { - throw new NotImplementedException($"Implement this method in your QueryFactory. If your implementation only supports 1 FHIR version, check whether the parameter matches that (check against VonkConstants.Model.*) and return 'default'. Otherwise use the informationModel parameter in your WHERE clause."); + return default(Q); } - - public virtual Q Filter(string parameterName, IFilterValue value) + else { - try - { - return AddValueFilter(parameterName, (dynamic)value); - } - catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) - { - throw new NotSupportedException($"{this.GetType().Name} cannot add a filter of type {value.GetType().Name} for parameter {parameterName}"); - } + throw new NotSupportedException("System does not support selection of deleted items"); } + } - /// - /// Default implementation of Shape handling. Handles count and skip. - /// Override this method for handling other shapes, call base() at the end to let this handle count and skip. - /// - /// - /// - public virtual Q ResultShape(IShapeValue shape) - { - try - { - return AddResultShape((dynamic)shape); - } - catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) - { - throw new NotSupportedException($"{this.GetType().Name} cannot add a shape of type {shape.GetType().Name}"); - } - } + /// + /// Repositories can in theory support >1 versions of FHIR. (Firely Server itself does that.) + /// This method should return a that filters on the FHIR version. + /// If your facade only supports 1 FHIR version, you only need to check whether + /// the provided value of represents that version. + /// You can check it against the values in . Example: + /// + /// if (VonkConstants.Model.FhirR3.Equals(informationModel)) + /// { + /// return default; + /// } + /// + /// + /// + /// + /// if (VonkConstants.Model.FhirR3.Equals(informationModel)) + /// { + /// return default; + /// } + /// + /// + /// FHIR version that the resource must have. + /// + public virtual Q EntryInformationModel(string informationModel) + { + throw new NotImplementedException($"Implement this method in your QueryFactory. If your implementation only supports 1 FHIR version, check whether the parameter matches that (check against VonkConstants.Model.*) and return 'default'. Otherwise use the informationModel parameter in your WHERE clause."); + } - protected virtual Q AddResultShape(CountShape count) + public virtual Q Filter(string parameterCode, IFilterValue value) + { + try { - return ShapeQuery(count); + return AddValueFilter(parameterCode, (dynamic)value); } - - protected virtual Q AddResultShape(SkipShape skip) + catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { - return ShapeQuery(skip); + throw new NotSupportedException($"{this.GetType().Name} cannot add a filter of type {value.GetType().Name} for parameter {parameterCode}"); } + } - protected virtual Q AddResultShape(SortShape sort) + /// + /// Default implementation of Shape handling. Handles count and skip. + /// Override this method for handling other shapes, call base() at the end to let this handle count and skip. + /// + /// + /// + public virtual Q ResultShape(IShapeValue shape) + { + try { - return ShapeQuery(sort); + return AddResultShape((dynamic)shape); } - - /// - /// Override this method to create queries for all the parameters of type String that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, ResourceTypesValue value) + catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { - if (value.ResourceTypes?.Count() != 1 || value.ResourceTypes.First() != ForResourceType) - throw new NotSupportedException($"Query of type {this.GetType().Name} can only be used for {ForResourceType}, not for {value.ResourceTypes?.CommaSeparated(true)}."); - return new Q(); + throw new NotSupportedException($"{this.GetType().Name} cannot add a shape of type {shape.GetType().Name}"); } + } - /// - /// Override this method to create queries for all the parameters of type String that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, StringValue value) - { - throw new NotSupportedException(); - } + protected virtual Q AddResultShape(CountShape count) + { + return ShapeQuery(count); + } - /// - /// Override this method to create queries for all the parameters of type Date, DateTime and Instant that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, DateTimeValue value) - { - throw new NotSupportedException(); - } + protected virtual Q AddResultShape(SkipShape skip) + { + return ShapeQuery(skip); + } - /// - /// Override this method to create queries for all the parameters of type Token that you want to support. - /// This is the method to implement for supporting search on _id. - /// - /// - /// - /// + protected virtual Q AddResultShape(SortShape sort) + { + return ShapeQuery(sort); + } - public virtual Q AddValueFilter(string parameterName, TokenValue value) - { - throw new NotSupportedException(); - } + /// + /// Override this method to create queries for the _type parameter. + /// + /// Code of the search parameter as used in the request, in this case always _type + /// Represents one or more resource types. The default implementation rejects queries on >1 resource type + /// + public virtual Q AddValueFilter(string parameterCode, ResourceTypesValue value) + { + //CK: string.Empty is used if there is _type except _type:not is empty. Since that is logically false, let's return that. + if (value.ResourceTypes.Count() == 1 && value.ResourceTypes.First() == string.Empty) + return AlwaysFalse(); + if (value.ResourceTypes?.Count() != 1 || value.ResourceTypes.First() != ForResourceType) + throw new NotSupportedException($"Query of type {this.GetType().Name} can only be used for {ForResourceType}, not for {value.ResourceTypes?.CommaSeparated(true)}."); + return new Q(); + } - /// - /// Override this method to create queries for all the parameters of type Number that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, NumberValue value) + /// + /// Override this method to create queries for supporting _type:not parameters. + /// Since the resource types in the query are so essential for building the right query, the :not modifier is relayed as a special type of value, + /// while other :not modifiers are relayed to the method. + /// + /// Code of the search parameter as used in the request, in this case always _type + /// Represents one or more resource types that must be excluded from the result. + /// + public virtual Q AddValueFilter(string parameterCode, ResourceTypesNotValue value) + { + if (value.ResourceTypes.Count() == 1) { - throw new NotSupportedException(); - } + if(value.ResourceTypes.First() == ForResourceType) + { + return AlwaysFalse(); + } + else + { + // When ForResourceType_type == Patient, then _type != Observation is implied + // so no new predicate is needed. + return null; + } + } - /// - /// Override this method to create queries for all the parameters of type Quantity that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, QuantityValue value) - { - throw new NotSupportedException(); - } + throw new NotSupportedException($"_type:not={value.ResourceTypes?.CommaSeparated(true)} is not supported."); + } + + /// + /// Produce a query that always evaluates to False. If you create a query that is recognizable, eg. AlwaysFalseQuery, you can optimize And and Or operations. + /// + /// + public virtual Q AlwaysFalse() + { + return PredicateQuery(_ => false); + } - /// - /// Override this method to create queries for all the parameters of type Uri that you want to support. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, UriValue value) - { - throw new NotSupportedException(); - } + /// + /// Produce a query that always evaluates to True. If you create a query that is recognizable, eg. AlwaysTrueQuery, you can optimize And and Or operations. + /// + /// + public virtual Q AlwaysTrue() + { + return PredicateQuery(_ => true); + } - /// - /// Override this method to create queries for all the parameters of type Reference that you want to support. - /// This method is only for direct references in the form of parameter=Resourcetype/id. - /// Use for chaining, - /// and for reverse chaining. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, ReferenceValue value) - { - throw new NotSupportedException(); - } + /// + /// Override this method to create queries for all the parameters of type String that you want to support. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, StringValue value) + { + throw new NotSupportedException(); + } - /// - /// Override this method to create queries for all the parameters that link to another Resourcetype through a reference, called chaining (a.b.c=xyz). - /// You will want to store the neccessary data in to perform a join later on. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, ReferenceToValue value) - { - throw new NotSupportedException(); - } + /// + /// Override this method to create queries for all the parameters of type Date, DateTime and Instant that you want to support. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, DateTimeValue value) + { + throw new NotSupportedException(); + } - /// - /// Override this method to create queries for all the parameters that are linked to this through a reference, called reverse chaining (_has:Resourcetype.a=xyz). - /// You will want to store the neccessary data in to perform a join later on. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, ReferenceFromValue value) - { - throw new NotSupportedException(); - } - - /// - /// Override this method to create queries for all the parameter types for which you want to support the missing modifier. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, MissingValue value) - { - throw new NotSupportedException(); - } + /// + /// Override this method to create queries for all the parameters of type Token that you want to support. + /// This is the method to implement for supporting search on _id. + /// + /// + /// + /// - /// - /// Override this method to create queries for parameters for which no SearchParameter is known to Vonk, and hence no SearchParameterType. - /// This allows you to support custom parameters without explicitly defining them. Remember to register such parameters in an IConformanceContributor though, otherwise they don't get mentioned in the CapabilityStatement. - /// - /// - /// - /// - public virtual Q AddValueFilter(string parameterName, RawValue value) - { - throw new NotSupportedException(); - } + public virtual Q AddValueFilter(string parameterCode, TokenValue value) + { + throw new NotSupportedException(); + } - protected virtual Q PredicateQuery(Expression> predicate) - { - return new Q() { Predicate = predicate }; - } + /// + /// Override this method to create queries for all the parameters of type Number that you want to support. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, NumberValue value) + { + throw new NotSupportedException(); + } - protected virtual Q ShapeQuery(IShapeValue shape) - { - return new Q() { InternalShapes = new[] { shape } }; - } + /// + /// Override this method to create queries for all the parameters of type Quantity that you want to support. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, QuantityValue value) + { + throw new NotSupportedException(); + } - protected virtual Q SortQuery

(SortShape sort, Expression> fieldSelector) - { - return ShapeQuery(new RelationalSortShape(sort, - (IQueryable source) => - sort.Direction == SortDirection.ascending - ? source.OrderBy(fieldSelector) - : source.OrderByDescending(fieldSelector) - )); - } - protected virtual Q SortQuery

(SortShape sort, Func, IQueryable> sortFunction) - { - return ShapeQuery(new RelationalSortShape(sort, sortFunction)); - } + ///

+ /// Override this method to create queries for all the parameters of type Uri that you want to support. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, UriValue value) + { + throw new NotSupportedException(); + } + + /// + /// Override this method to create queries for all the parameters of type Reference that you want to support. + /// This method is only for direct references in the form of parameter=Resourcetype/id. + /// Use for chaining, + /// and for reverse chaining. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, ReferenceValue value) + { + throw new NotSupportedException(); + } + + /// + /// Override this method to create queries for all the parameters that link to another Resourcetype through a reference, called chaining (a.b.c=xyz). + /// You will want to store the neccessary data in to perform a join later on. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, ReferenceToValue value) + { + throw new NotSupportedException(); } + /// + /// Override this method to create queries for all the parameters that are linked to this through a reference, called reverse chaining (_has:Resourcetype.a=xyz). + /// You will want to store the neccessary data in to perform a join later on. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, ReferenceFromValue value) + { + throw new NotSupportedException(); + } + /// + /// Override this method to create queries for all the parameter types for which you want to support the missing modifier. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, MissingValue value) + { + throw new NotSupportedException(); + } + /// + /// Override this method to create queries for parameters for which no SearchParameter is known to Vonk, and hence no SearchParameterType. + /// This allows you to support custom parameters without explicitly defining them. Remember to register such parameters in an IConformanceContributor though, otherwise they don't get mentioned in the CapabilityStatement. + /// + /// + /// + /// + public virtual Q AddValueFilter(string parameterCode, RawValue value) + { + throw new NotSupportedException(); + } + + protected virtual Q PredicateQuery(Expression> predicate) + { + return new Q() { Predicate = predicate }; + } + + protected virtual Q ShapeQuery(IShapeValue shape) + { + return new Q() { InternalShapes = new[] { shape } }; + } + + protected virtual Q SortQuery

(SortShape sort, Expression> fieldSelector) + { + return ShapeQuery(new RelationalSortShape(sort, + (IQueryable source) => + sort.Direction == SortDirection.ascending + ? source.OrderBy(fieldSelector) + : source.OrderByDescending(fieldSelector) + )); + } + protected virtual Q SortQuery

(SortShape sort, Func, IQueryable> sortFunction) + { + return ShapeQuery(new RelationalSortShape(sort, sortFunction)); + } } From dc317aa1f24bcee9de6b59a219da4a29b2a6489a Mon Sep 17 00:00:00 2001 From: Christiaan Knaap <2102470+cknaap@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:05:04 +0100 Subject: [PATCH 2/2] update: Any other changes, license and dependencies --- README.md | 8 +- Vonk.Facade.Relational/LICENSE.md | 180 ++++++++++++++ Vonk.Facade.Relational/LinqKitExtensions.cs | 56 ++--- Vonk.Facade.Relational/QueryExtensions.cs | 22 +- Vonk.Facade.Relational/RelationalQuery.cs | 251 ++++++++++---------- Vonk.Facade.Relational/SearchRepository.cs | 115 +++++---- Vonk.Facade.Relational/Vonk.props | 10 +- 7 files changed, 407 insertions(+), 235 deletions(-) create mode 100644 Vonk.Facade.Relational/LICENSE.md diff --git a/README.md b/README.md index 32f179b..28c71d7 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ For more details about developing a Firely Server facade, please consult [Firely ### Build dependencies The following configuration has been succesfully tested for building and running the project: -* Firely Server (Vonk) - Version 4.0.0 -* Visual Studio for Mac - Version 8.x.x -* Visual Studio for Windows - Version 16.x.x -* .Net Core - Version 3.1 +* Firely Server (Vonk) - Version 5.x.0 +* Visual Studio for Mac - Version 8.x.x and newer +* Visual Studio for Windows - Version 16.x.x and newer +* .Net 8.0 ## License diff --git a/Vonk.Facade.Relational/LICENSE.md b/Vonk.Facade.Relational/LICENSE.md new file mode 100644 index 0000000..fa2090c --- /dev/null +++ b/Vonk.Facade.Relational/LICENSE.md @@ -0,0 +1,180 @@ +# Firely Terms of Service +By creating an account on Simplifier.net, or by using any other Products or Services of Firely B.V., you agree to being bound by the following terms and conditions. + +## 1. Definitions +1.1. Content: all computer programs, source code, data, or other material which can be obtained through the Platform, not being Firely Products; + +1.2. Contract: an agreement for additional Services, such as a Paid Plan or a licence to Firely Products; + +1.3. Firely: Firely B.V., with its registered offices at Westerdok 442, 1013 BH Amsterdam, The Netherlands; + +1.4. Firely Products: all software that belongs to Firely and that can be obtained through the Platform, such as Forge and Firely Server; + +1.5. Paid Plan: an upgrade to the default User Account which grants User additional features as specified on the Platform; + +1.6. Parties: User and Firely; + +1.7. Platform: the website Simplifier.net, with the exception of the Content on the Platform and other Firely Products; + +1.8. Services: all services that Firely can deliver to User, such as the right to use the Platform, the right to use the Firely Products, maintenance, support, advice, training, hosting and cloud services; + +1.9. Terms: the present terms of service; + +1.10. User: the person or organisation that creates a User Account on the Platform; + +1.11. User Account: the personal account of User on the Platform. + +## 2. General +2.1. The Terms apply to User and Firely for all Services. + +2.2. Firely is entitled to amend these Terms. The most recent version of the Terms is published on the Platform. User will be notified by email of any upcoming changes in the Terms. Amendments become effective on the date referred to in the relevant publication. Derogations from the Terms will only be valid if explicitly agreed on in writing and laid down in a document signed by both Parties. + +2.3. The Terms apply to the exclusion of User's terms or purchase terms, if any; Firely does not accept, now for then, User's terms being declared applicable or any referral by User to other terms being applicable, whether its own purchase terms or terms applied by a third party. + +2.4. Firely is entitled to transfer any rights and obligations under the Terms or a Contract to third parties. + +2.5. User is not entitled to transfer any rights and obligation under the Terms or a Contract to third parties without Firely's written permission. Permission will not be withheld without reasonable cause; Firely will be entitled to make this permission subject to further conditions. + +2.6. In the event any of the provisions in the Terms should be void, the other provisions will remain in full force. Parties will then be obliged to consult each other and draft a new provision or provisions to replace any of the provisions that are void and to draft the new provision(s) in such a way that either the purport and intention of the void provision(s) is retained as much as possible, or the relevant provision is deemed to have retained its validity by conversion. + +2.7. HL7 and FHIR are the registered trademarks of Health Level Seven International, Ann Arbor, MI USA. + +## 3. Contracts +3.1. User can request a Contract for a Paid Plan or Firely Product by contacting Firely. Firely will then send User a contract form. A Contract is entered into when the contract form has been signed by both Parties. + +3.2. All Contracts are automatically renewed for the same period as the initial term, until User or Firely terminates the Contract in accordance with article 11. + +3.3. The Terms apply to all Contracts. If there should be any inconsistencies, the provisions in the Contract prevail over those in the Terms. + +## 4. User Account +4.1. When creating a User Account, User is to provide a name, a valid email address and any other information requested to complete the signup process. The creation of a User Account is free of charge. The creation of a User Account involves Firely collecting personal data provided by User to Firely, which data is needed by Firely to fulfil its obligations under the Terms. + +4.2. A User Account may only be used by one person. A single login shared by more than one person is not permitted. User must keep their account information and password confidential. Firely cannot and will not be liable for any loss or damage resulting from the failure to comply with this security obligation. + +4.3. User is responsible for all activities that take place in the context of the User Account. The Platform may not be used for any illegal or unauthorized purpose. + +4.4. If User's bandwidth usage on the Platform significantly exceeds the average bandwidth usage of other Firely customers (fair use; as determined by Firely), Firely reserves the right to throttle file hosting until User has reduced bandwidth consumption. If User, after having been notified by Firely, still exceeds the average bandwidth usage, Firely has the right to disable the User Account. + +## 5. Usage of the Platform +5.1. The Platform is made available by Firely as a remote service. Firely will make reasonable efforts to repair defects in the Platform within a reasonable period of time. + +5.2. The Content on the Platform is created and made available by other users of the Platform, without any intervention by Firely. All Content is provided on an "as is", "where is" and "as available" basis. Firely is neither responsible nor liable in any way for the Content available on the Platform. + +5.3. Firely does not guarantee that (i) the Platform and the Content will meet specific requirements, (ii) the Platform and Content will be uninterrupted, timely, secure, or error-free, (iii) the results that may be obtained from the use of the Platform or Content will be accurate or reliable, (iv) the quality of the Platform or Content will meet User's expectations. + +5.4. Firely reserves the right, at any time, to modify or discontinue, temporarily or permanently, the Platform (or any part thereof). In case of discontinuation of the Platform, Firely will make a reasonable attempt to warn User prior to discontinuation. + +5.5. Cancelling a Paid Plan may cause loss of access to Content, features, or capacity. Firely does not accept any liability for such loss. + +5.6. Firely is never obliged to repair Content, settings, or any other uploaded data on the Platform that have been corrupted or lost. + +5.7. User is not allowed to reproduce, duplicate, copy, sell, resell or exploit any portion of the Platform. User may specifically not duplicate, copy, or reuse any portion of the HTML/CSS, Javascript, or visual design elements or concepts without Firely's explicit permission in writing. + +5.8. Firely is entitled to collect data on the usage of the Platform, for the purpose of improving the Services, in accordance with the privacy notification on the Platform. + +5.9. User may access the Platform data via an API (Application Program Interface). Firely reserves the right, at any time, to modify or discontinue, temporarily or permanently, with or without notice, access to the API (or any part thereof). + +5.10. Abuse or excessively frequent requests to the Platform via the API may result in temporary or permanent suspension of the User Account's access to the API. Firely will determine abuse or excessive usage of the API at its sole discretion. Firely will make a reasonable attempt to warn User prior to said suspension. + +5.11. Firely is not liable for any direct, indirect or consequential damages, including but not limited to, damages for loss of profits, goodwill, use, data or other intangible losses (even if Firely has been advised of the possibility of such damages), resulting from the use of the Platform, the Content, the API or third party products that access data via the API. + +## 6. Uploading Content +6.1. By uploading any data or Content to the Platform, User grants Firely the right to save and host the Content. User guarantees that no third party intellectual property rights prohibit uploading the Content to the Platform. + +6.2. By setting Content to be viewed publicly, User grants Firely and all users of the Platform the irrevocable right to download, use and link the Content. User guarantees that no third party intellectual property rights to the Content, prevent User from granting such right. User guarantees that it is fully entitled to upload the data or Content and to grant other users full access to and use of the Content. + +6.3. Firely will make reasonable efforts that User has exclusive access to Content on the Platform which is uploaded and set to be viewed privately. User can delete uploaded private Content from the Platform. + +6.4. User is strictly prohibited to upload any personal data within the meaning of article 4 of GDPR, or any illegal Content, whether set to private or public access, such as material protected by intellectual property rights of third parties, worms, viruses or any code of a destructive nature, or to upload, post, host, or transmit unsolicited (spam) messages. + +6.5. User will defend Firely against any claim, demand, suit or proceedings made or brought against Firely by a third party alleging that the Content uploaded by User, or the use of the Platform by User, is in violation of these Terms, infringes or misappropriates the intellectual property rights of that third party or violates applicable law, and will indemnify and hold Firely harmless against any damages finally awarded, and against reasonable attorney's fees incurred by Firely in connection with any such claim, demand, suit or proceedings, provided always that Firely (a) promptly gives User written notice of the claim, demand, suit or proceedings; (b) gives User sole control of the defence and settlement of the claim, demand, suit or proceedings (provided that User does not settle any claim, demand, suit or proceedings unless the settlement unconditionally releases Firely of all liability); and (c) provides User all reasonable assistance, at User's expense. + +6.6. Firely has the right to refuse or remove Content that is a breach to the Firely Terms of Service or laws and regulations. + +## 7. Firely Products +7.1. When Parties enter into a Contract regarding Firely Products, Firely grants User, for the duration of the Contract exclusively, a non-exclusive licence to use the Firely Products under the conditions set forth in the Terms and Contract. + +7.2. All Firely Products are provided on an "as is" basis. Firely is neither obliged to maintain the Firely Products nor to provide support to users and/or administrators of the Firely Products, unless specifically agreed upon in the applicable Contract. + +7.3. All intellectual rights and intellectual property rights that may or will be exercised -wherever and whenever- with respect to the Firely Products, are exclusively vested or will be exclusively vested in Firely, its licensors or suppliers. + +7.4. User is not entitled to: (a) modify, adapt, create derivative works from or translate any part of the Firely Products, (b) reverse engineer, decompile or disassemble the Firely Products or otherwise attempt to obtain their source codes, (c) remove or alter any copyright, trademark or other proprietary notice contained in the Firely Products, (d) reproduce, duplicate, copy, sell, resell or exploit the Firely Products, or (e) use the Firely Products in any manner not set forth in these Terms. + +7.5. Firely may make updates, from time to time, for the Firely Products available. Firely may conditionally release, at Firely's sole discretion, such upgrades to User. All updates are subject to the applicable Contract and these Terms. + +7.6. Firely's obligations to make the Firely Products available and User's rights to use the Firely Products are strictly restricted to the so-called object codes of the Firely Products and they are explicitly not related to making the Firely Products' source codes available. Neither the Firely Products' source codes nor the technical documentation will be made available to User, unless explicitly specified in the Contract. + +7.7. User may exclusively use the Firely Products in and for the benefit of its own business or organisation and only in so far as this is required for the intended use. + +7.8 User is strictly prohibited to upload any personal data within the meaning of article 4 of GDPR to any website or other product hosted by or on behalf of Firely. + +7.9. Firely will not have access to any data stored in products of Firely -like Firely Server- installed in the infrastructure of User. User shall not grant access to Firely to any data stored in Firely products, including personal data within the meaning of article 4 of GDPR. A Data Processing Agreement is not applicable for Firely's Products or Services. + +7.10. Upon Firely's request, User will promptly render assistance in an investigation to be carried out by or on behalf of Firely in the context of User's compliance with the restricted user rights. Upon Firely's first request, User will allow Firely access to its premises and systems. Firely will observe confidentiality with respect to any confidential business information obtained from User in the context of such investigation, in so far as that information is not related to the use of the Firely Products itself. The audit will be performed no more than once per year, unless Firely has concrete reasons to perform an audit on an ad hoc basis. + +7.11. Immediately following the Contract's termination, User will return to Firely all copies of the Firely Products that User has in its possession, unless Firely has indicated that User is to destroy these. User will report said destruction in writing, without delay. Following the Contract's termination, Firely will not be obliged to assist User in a possible data conversion or migration to another system, as required by User, unless explicitly agreed upon otherwise. + +## 8. Payment +8.1. Firely is entitled to change the prices for renewals of Contracts in line with inflation and rising prices. Firely will notify User in advance of any upcoming price changes. + +8.2. For monthly payment plans, Firely invoices, in advance, on a monthly basis. For yearly payment plans, Firely invoices, in advance, on a yearly basis. When changing from a monthly invoicing cycle to a yearly invoicing cycle, Firely will invoice a full year at the next monthly invoice date. + +8.3. For 'pay per use' payment plans, Firely invoices, at the end of each month, the actual usage by User during that month. In the event of pay per use payment plans, usage is measured by Firely. + +8.4. All fees are in euros and exclusive of all taxes, levies, or duties imposed by taxing authorities. + +8.5. Payment is non-refundable. No refunds or credits are due for partial months of service, for upgrade/downgrade or for months not used in an open account. + +8.6. User is obliged to pay invoices within thirty (30) days of the invoice date in the way and into the account indicated by or on behalf of Firely. Payment is to take place without any discount or setoff. + +8.7. User is neither liable for payment of wages to Firely's employees nor for payment of social insurance premiums and wage taxes. + +8.8. For any upgrade or downgrade in plan level of the Platform during a monthly invoicing cycle, the credit card provided will automatically be charged at the new rate at the beginning of the next invoice cycle. For upgrades during a yearly invoicing cycle, Firely will immediately charge the difference in plan costs, prorated the remaining time in the yearly invoicing cycle. + +8.9. If User fails to meet the payment term, User is in default by operation of law and default interest is due and payable to Firely, which default interest is equal to the level of the statutory commercial interest rate, to be calculated as from the due date. + +8.10. For as long as User should fail to meet any of its obligations towards Firely and has not remedied this failure, even after having been served a default notice, Firely is entitled either to suspend the performance of its obligations towards User for as long as User continues failing to meet its obligations towards Firely as referred to above or to discontinue the delivery of Products and Services until User has furnished adequate security -at Firely's discretion- for the performance of its obligations. Firely is also entitled, without being held to any compensation of damages and if there is probable cause (such as User's repeated failure to meet its payment obligations), to change the payment terms and/or to demand sufficient security before performing any other or new Services and/or delivering Products. + +8.11. User is not entitled to suspend payment obligations towards Firely in connection with possible counterclaims towards Firely or to set these payment obligations off, except if and in so far as these claims and obligations have been explicitly acknowledged by Firely in writing. + +8.12. In the event User defaults on the (timely) performance of its obligations, Firely may pass on the claim for collection, in which case User will be obliged to pay, apart from the total amount due, including default interest, all judicial and extrajudicial costs, including the costs charged by external experts. All this does not affect any of the other statutory and contractual rights Firely may have. + +## 9. Liability +9.1. Firely's total liability for an attributable failure to meet its obligations under the Terms, or on any other legal basis, explicitly including every failure to meet guarantee commitments and indemnity obligations agreed on with User, is limited to the compensation of direct damage to the maximum of the aggregate amount of fees (exclusive of VAT) User has paid to Firely under the Terms for the past year. Firely's total liability for direct damages, or on whatever legal basis, will never exceed €100,000 (one hundred thousand euros). + +9.2. Firely is not liable for indirect damages, consequential damages, lost profits, missed savings, damage to goodwill, damage as a result of business interruptions, damage arising from claims filed by User's customers, damage related to the use of third party property, materials or software that User advised Firely to use and damage related to Firely's engaging certain suppliers at User's request. Firely is not liable either for any modification, suspension or discontinuance of the Services. + +9.3. The exemptions and restrictions of liability referred to in articles 9.1 and 9.2 do not affect any of the other exemptions and restrictions of Parties' liability laid down in these Terms. + +9.4. The exemptions and restrictions of liability referred to in the articles 9.1 to 9.3 do not apply if and in so far as the damage results from intent or deliberate recklessness on the side of Firely. + +9.5. Unless performance is permanently impossible for a Party, liability of that Party for the imputable failure to meet its obligations under the Contract only arises if the other Party immediately serves that Party a notice of default, in which a reasonable term is set for that Party to remedy the failure and the Party who has failed to meet its obligations continues doing so even after the deadline. The default notice is to contain a detailed description of the failure so that the Party that has failed to meet its obligations can respond adequately. + +9.6. Any right to compensation of damages is conditional on the Party's notifying the other Party in writing of the damage as soon as reasonably possible after it has been noticed. Any claim for compensation of damages lapses by the mere expiry of a period of twelve (12) months after the claim has arisen, unless the Party in question has instituted legal action for compensation of the damages suffered before the lapse of that period. + +9.7. User indemnifies and holds Firely harmless against all third party claims, of whatever nature and regardless of the cause, that may arise in relation to the performance of the Services by User and Firely, unless and in so far as User proves that the damage was caused by an imputable act or omission on the side of Firely, for which act or omission Firely would have been liable towards User under these Terms if the damage had been suffered by User itself. + +## 10. Force Majeure +10.1. Neither Party is obliged to meet any of its obligations, including any statutory and/or contractual guarantee commitments, if it is prevented from doing so as a result of circumstances beyond its control. Circumstances beyond Firely's control are understood to mean, amongst other things: (i) circumstances beyond Firely's suppliers' control, (ii) suppliers that User advised Firely to use not properly meeting their obligations, (iii) the defective condition of items, hardware, software or material of third parties which User advised Firely to use, (iv) government measures, (v) electricity failures, (vi) failures of internet, data network or telecommunication facilities, (vii) war and (viii) general transportation problems. + +10.2. If a force majeure event lasts for more than sixty (60) days, each Party will be entitled to terminate (ontbinden) the Contract in writing. Everything that has already been performed under the Contract will then be settled proportionally, without anything being payable by either Party to the other Party in any other respect. For the period of sixty (60) days referred to, performance of the obligations affected by the force majeure event will be suspended, but the Contract entered into between Parties will remain in effect as much as possible. + +## 11. Termination +11.1. User can terminate (opzeggen) a Contract by written notice of termination to Firely. If the notice is received by Firely before the end of the current month or year, which has already been paid, the termination will take effect immediately and User will not be charged for the next month or year. + +11.2. Firely has the right to suspend the use of the Platform and refuse all current or future use of the Platform for User's failure to meet its obligations under the Terms. Such suspension or termination will result in the deactivation or deletion of the User Account or the access to the User Account. + +11.3. Either party is only entitled to terminate (ontbinden) the Contract because of the other party's failure to meet its obligations under that Contract if the other party imputably fails to meet essential obligations under the Contract and after having been served a notice of default, by email and as detailed as possible, in which notice a reasonable period of time is set for the other party to remedy the failure. + +11.4. Either Party may terminate a Contract by serving written notice of termination (opzeggen), without a notice of default, either in full or in part, with immediate effect if the other Party is granted suspension of payments -either provisionally or not- if a petition is filed for the other Party's liquidation or a liquidation order is given, if the other Party's enterprise is liquidated (faillissement) or terminated other than for the purpose of a reconstruction or of a merger of enterprises. Parties will never be obliged to refund any money received or to compensate damages because of termination as referred to in the present paragraph. In the event User's estate is being liquidated, User's right to use the software, websites and so on made available and User's right to access and/or use the Products and Services delivered by Firely end without Firely having to perform any act to terminate (opzeggen) the Contract. + +11.5. Any provisions in these Terms and in a Contract that are meant to survive the termination of the Contract, will remain in effect after the termination - such as provisions on liability, intellectual property rights and the dispute settlement rules. + +## 12. Applicable law and disputes +12.1. These Terms and/or any Contract and/or any additional contract arising from them between Firely and User are exclusively governed by the laws of the Netherlands. Applicability of the Vienna Convention for the Sale of Goods 1980 is excluded. + +12.2. Any dispute that arises in connection with these Terms and/or any Contract and/or any additional contract arising from them is subject to arbitration proceedings in accordance with the Arbitration Regulations of the Foundation for the Settlement of Automation Disputes (Stichting Geschillenoplossing Automatisering), (http://www.sgoa.org/ts offices in Heemstede, without prejudice, however, to either Party's right to request preliminary relief in preliminary relief proceedings, in arbitrational proceedings or in a court of law, and without prejudice to either Party's right to take conservatory measures (e.g. to attach property before judgment). + +12.3. Either Party is always entitled to initiate an ICT Mediation procedure in accordance with the Mediation Regulations of the Foundation for the Settlement of Automation Disputes (Stichting Geschillenoplossing Auromatisering) in the event of a dispute arising from these Terms and/or a Contract and/or any other, additional contract arising from them. The other Party is then obliged to actively participate in the ICT Mediation initiated, which legally enforceable obligation entails that the other Party should at least attend one joint meeting of mediators and Parties in order to give this extrajudicial form of dispute resolution a chance. Each Party is entitled to end the ICT Mediation procedure at any time following this joint meeting of mediators and Parties. The provisions in this Article do not affect the right of either Party, in so far as this Party should think this is necessary, to request preliminary relief in preliminary relief proceedings, in arbitrational proceedings or in a court of law, or to take conservatory measures (e.g. to attach property before judgment). + +Version 1.34, 27 September 2022 \ No newline at end of file diff --git a/Vonk.Facade.Relational/LinqKitExtensions.cs b/Vonk.Facade.Relational/LinqKitExtensions.cs index 5ed9671..8253feb 100644 --- a/Vonk.Facade.Relational/LinqKitExtensions.cs +++ b/Vonk.Facade.Relational/LinqKitExtensions.cs @@ -1,42 +1,38 @@ using System; -using System.Linq; using System.Linq.Expressions; -using System.Collections.Generic; using LinqKit; -namespace Vonk.Core.Support +namespace Vonk.Core.Support; + +public static class LinqKitExtensions { - public static class LinqKitExtensions - { - public static Expression> And(params Expression>[] expressions) + public static Expression> And(params Expression>[] expressions) + { + if (expressions == null) + return null; + var result = expressions[0]; + for (int i = 1; i < expressions.Length; i++) { - if (expressions == null) - return null; - var result = expressions[0]; - for (int i = 1; i < expressions.Length; i++) - { - if (result == null) - result = expressions[i]; - else if (expressions[i] != null) - result = result.And(expressions[i]); - } - return result; + if (result == null) + result = expressions[i]; + else if (expressions[i] != null) + result = result.And(expressions[i]); } + return result; + } - public static Expression> Or(params Expression>[] expressions) + public static Expression> Or(params Expression>[] expressions) + { + if (expressions == null) + return null; + var result = expressions[0]; + for (int i = 1; i < expressions.Length; i++) { - if (expressions == null) - return null; - var result = expressions[0]; - for (int i = 1; i < expressions.Length; i++) - { - if (result == null) - result = expressions[i]; - else if (expressions[i] != null) - result = result.Or(expressions[i]); - } - return result; + if (result == null) + result = expressions[i]; + else if (expressions[i] != null) + result = result.Or(expressions[i]); } + return result; } - } diff --git a/Vonk.Facade.Relational/QueryExtensions.cs b/Vonk.Facade.Relational/QueryExtensions.cs index 5ecd2bd..6b11757 100644 --- a/Vonk.Facade.Relational/QueryExtensions.cs +++ b/Vonk.Facade.Relational/QueryExtensions.cs @@ -1,19 +1,17 @@ using Vonk.Core.Repository; -namespace Vonk.Facade.Relational +namespace Vonk.Facade.Relational; + +public static class QueryExtensions { - public static class QueryExtensions + public static Q CreateQuery(this ReferenceFromValue refFromValue, RelationalQueryFactory queryFactory) where E : class where Q : RelationalQuery, new() { - //TODO: Move these to Vonk.Core.Repository, they are also useful for MemoryQueryRepo and probably also DocumentQueryRepo - public static Q CreateQuery(this ReferenceFromValue refFromValue, RelationalQueryFactory queryFactory) where E : class where Q : RelationalQuery, new() - { - return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level); - } - - public static Q CreateQuery(this ReferenceToValue refFromValue, RelationalQueryFactory queryFactory) where E : class where Q : RelationalQuery, new() - { - return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level); - } + return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level); + } + public static Q CreateQuery(this ReferenceToValue refFromValue, RelationalQueryFactory queryFactory) where E : class where Q : RelationalQuery, new() + { + return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level); } + } diff --git a/Vonk.Facade.Relational/RelationalQuery.cs b/Vonk.Facade.Relational/RelationalQuery.cs index ce0b08f..f5c294b 100644 --- a/Vonk.Facade.Relational/RelationalQuery.cs +++ b/Vonk.Facade.Relational/RelationalQuery.cs @@ -1,152 +1,151 @@ -using LinqKit; -using Microsoft.EntityFrameworkCore; -using System; +using System; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using LinqKit; +using Microsoft.EntityFrameworkCore; using Vonk.Core.Repository; using Vonk.Core.Repository.ResultShaping; using Vonk.Core.Support; -namespace Vonk.Facade.Relational +namespace Vonk.Facade.Relational; + +public class RelationalSortShape : SortShape { - public class RelationalSortShape : SortShape + public RelationalSortShape(SortShape originalSort, Func, IQueryable> sortFunction) : base(originalSort.ParameterCode, originalSort.ParameterType, originalSort.Direction, originalSort.Priority) { - public RelationalSortShape(SortShape originalSort, Func, IQueryable> sortFunction) : base(originalSort.ParameterCode, originalSort.ParameterType, originalSort.Direction, originalSort.Priority) - { - Sort = sortFunction; - } - - public Func, IQueryable> Sort { get; } + Sort = sortFunction; } - public class RelationalQuery : BaseQuery where E : class - { - public Expression> Predicate { get; internal set; } - internal IShapeValue[] InternalShapes { get; set; } - public override IShapeValue[] Shapes { get => InternalShapes; } + public Func, IQueryable> Sort { get; } +} - ///

- /// Get the entities of type that adhere to the , with any provided Skip and Count applied. - /// - /// The containing a of . We use so it can also contain s for entities that are referenced to or from.. - /// - public virtual IQueryable Execute(DbContext dbContext) - { - Check.NotNull(dbContext, nameof(dbContext)); - return HandleShapes(Filter(dbContext)); - } +public class RelationalQuery : BaseQuery where E : class +{ + public Expression> Predicate { get; internal set; } + internal IShapeValue[] InternalShapes { get; set; } + public override IShapeValue[] Shapes { get => InternalShapes; } - /// - /// Get the total number of entities that adhere to the , irrespective of the Skip and Count. - /// - /// The containing a of . We use so it can also contain s for entities that are referenced to or from.. - /// - public virtual async Task ExecuteCount(DbContext dbContext) - { - var entries = Filter(dbContext); - if (entries == null) - return 0; - else - return await entries.CountAsync(); - } + /// + /// Get the entities of type that adhere to the , with any provided Skip and Count applied. + /// + /// The containing a of . We use so it can also contain s for entities that are referenced to or from.. + /// + public virtual IQueryable Execute(DbContext dbContext) + { + Check.NotNull(dbContext); + return HandleShapes(Filter(dbContext)); + } - /// - /// Apply the on entities of type in . - /// - /// - /// - protected virtual IQueryable Filter(DbContext dbContext) - { - var entities = GetEntitySet(dbContext).AsExpandable(); - if (Predicate != null) - return entities.Where(LinqKitExtensions.And(Predicate)); - return entities; - } + /// + /// Get the total number of entities that adhere to the , irrespective of the Skip and Count. + /// + /// The containing a of . We use so it can also contain s for entities that are referenced to or from.. + /// + public virtual async Task ExecuteCount(DbContext dbContext) + { + var entries = Filter(dbContext); + if (entries == null) + return 0; + else + return await entries.CountAsync(); + } - /// - /// By default this will return dbContext.Set, without change tracking. - /// Override it to add custom .Include() statements or other adjustments. - /// - /// - /// A set of entities that will be used for filtering and counting - protected virtual IQueryable GetEntitySet(DbContext dbContext) - { - return dbContext.Set().AsNoTracking(); - } + /// + /// Apply the on entities of type in . + /// + /// + /// + protected virtual IQueryable Filter(DbContext dbContext) + { + var entities = GetEntitySet(dbContext).AsExpandable(); + if (Predicate != null) + return entities.Where(LinqKitExtensions.And(Predicate)); + return entities; + } - /// - /// Apply the on . - /// By default only Skip, Count and Sort are applied. - /// is always applied. - /// - /// - /// - protected virtual IQueryable HandleShapes(IQueryable source) - { - if (Shapes.HasAny()) - return HandleCount( - HandleSkip( - HandleSort( - SortByDefault(source) - ))); - return SortByDefault(source); - } + /// + /// By default this will return dbContext.Set, without change tracking. + /// Override it to add custom .Include() statements or other adjustments. + /// + /// + /// A set of entities that will be used for filtering and counting + protected virtual IQueryable GetEntitySet(DbContext dbContext) + { + return dbContext.Set().AsNoTracking(); + } - /// - /// Apply the on , if provided in . - /// - /// - /// - protected virtual IQueryable HandleSkip(IQueryable source) - { - var skip = Shapes.OfType().FirstOrDefault(); - if (skip != null) - return source.Skip(skip.Skip); - return source; - } + /// + /// Apply the on . + /// By default only Skip, Count and Sort are applied. + /// is always applied. + /// + /// + /// + protected virtual IQueryable HandleShapes(IQueryable source) + { + if (Shapes.HasAny()) + return HandleCount( + HandleSkip( + HandleSort( + SortByDefault(source) + ))); + return SortByDefault(source); + } - /// - /// Apply the on , if provided in . - /// - /// - /// - protected virtual IQueryable HandleCount(IQueryable source) - { - var count = Shapes.OfType().FirstOrDefault(); - if (count != null) - return source.Take(count.Count); - return source; - } + /// + /// Apply the on , if provided in . + /// + /// + /// + protected virtual IQueryable HandleSkip(IQueryable source) + { + var skip = Shapes.OfType().FirstOrDefault(); + if (skip != null) + return source.Skip(skip.Skip); + return source; + } + + /// + /// Apply the on , if provided in . + /// + /// + /// + protected virtual IQueryable HandleCount(IQueryable source) + { + var count = Shapes.OfType().FirstOrDefault(); + if (count != null) + return source.Take(count.Count); + return source; + } - /// - /// Apply al the s on . - /// Take into account the and apply them in the right order. - /// - /// - /// - protected virtual IQueryable HandleSort(IQueryable source) + /// + /// Apply al the s on . + /// Take into account the and apply them in the right order. + /// + /// + /// + protected virtual IQueryable HandleSort(IQueryable source) + { + if (Shapes.HasAny()) { - if (Shapes.HasAny()) + foreach (var relationalSort in Shapes.OfType>().OrderByDescending(rss => rss.Priority)) { - foreach (var relationalSort in Shapes.OfType>().OrderByDescending(rss => rss.Priority)) - { - source = relationalSort.Sort(source); - } + source = relationalSort.Sort(source); } - return source; } + return source; + } - /// - /// Lets you supply an OrderBy on e.g. a technical id so at least the order of the results is predictable. - /// Also, EFCore complains if there is no sorting at all. - /// Example: return source.OrderBy(e => e.SomeId); - /// - /// - /// - protected virtual IQueryable SortByDefault(IQueryable source) - { - return source; - } + /// + /// Lets you supply an OrderBy on e.g. a technical id so at least the order of the results is predictable. + /// Also, EFCore complains if there is no sorting at all. + /// Example: return source.OrderBy(e => e.SomeId); + /// + /// + /// + protected virtual IQueryable SortByDefault(IQueryable source) + { + return source; } } diff --git a/Vonk.Facade.Relational/SearchRepository.cs b/Vonk.Facade.Relational/SearchRepository.cs index a5046cd..1f8de06 100644 --- a/Vonk.Facade.Relational/SearchRepository.cs +++ b/Vonk.Facade.Relational/SearchRepository.cs @@ -1,58 +1,57 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Vonk.Core.Context; -using Vonk.Core.Repository; -using Vonk.Core.Repository.ResultShaping; -using Vonk.Core.Support; -using static Vonk.Core.Context.VonkOutcome; - -namespace Vonk.Facade.Relational -{ - public abstract class SearchRepository : ISearchRepository - { - protected readonly QueryContext _queryContext; - - public SearchRepository(QueryContext queryContext) - { - Check.NotNull(queryContext, nameof(queryContext)); - - _queryContext = queryContext; - } - - public virtual Task Search(IArgumentCollection arguments, SearchOptions options) - { - var types = arguments?.ResourceTypes(true); - if (!types.HasAny() || types.Count() > 1) - throw new NotSupportedException("Searching across multiple types is not supported."); - - var type = types.First(); - - // Argument "_total" is meant to be handled in the implementation of ISearchRepository of a facade, - // however since "_total=accurate" was added as a default shape argument and it is always the case when we perform a search, - // so we simply handle it here for a facade implementation. - // For arguments "_total=none" and "_total=estimate", they still need to be handled in a facade. - var totalArgument = arguments.GetArgument(ArgumentNames.total); - if (totalArgument != null && TotalOptions.accurate.ToString().Equals(totalArgument.ArgumentValue)) - { - totalArgument.Handled(); - } - - try - { - return Search(type, arguments, options); - } - catch (NotSupportedException nse) - { - foreach (var arg in arguments.ResourceTypeArguments()) - { - var issueComponent = new VonkIssue(IssueSeverity.Error, IssueType.NotSupported, null, nse.Message); - arg.Error(issueComponent); - } - throw; - } - } - - protected abstract Task Search(string resourceType, IArgumentCollection arguments, SearchOptions options); - } -} +using System; +using System.Linq; +using System.Threading.Tasks; +using Vonk.Core.Context; +using Vonk.Core.Repository; +using Vonk.Core.Repository.ResultShaping; +using Vonk.Core.Support; +using static Vonk.Core.Context.VonkOutcome; + +namespace Vonk.Facade.Relational; + +public abstract class SearchRepository : ISearchRepository +{ + protected readonly QueryContext _queryContext; + + public SearchRepository(QueryContext queryContext) + { + Check.NotNull(queryContext); + + _queryContext = queryContext; + } + + public virtual Task Search(IArgumentCollection arguments, SearchOptions options) + { + var types = arguments?.ResourceTypes(true); + if (!types.HasAny() || types.Count() > 1) + throw new NotSupportedException("Searching across multiple types is not supported."); + + var type = types.First(); + + // Argument "_total" is meant to be handled in the implementation of ISearchRepository of a facade, + // however since "_total=accurate" was added as a default shape argument and it is always the case when we perform a search, + // so we simply handle it here for a facade implementation. + // For arguments "_total=none" and "_total=estimate", they still need to be handled in a facade. + var totalArgument = arguments.GetArgument(ArgumentNames.total); + if (totalArgument != null && TotalOptions.accurate.ToString().Equals(totalArgument.ArgumentValue)) + { + totalArgument.Handled(); + } + + try + { + return Search(type, arguments, options); + } + catch (NotSupportedException nse) + { + foreach (var arg in arguments.ResourceTypeArguments()) + { + var issueComponent = new VonkIssue(IssueSeverity.Error, IssueType.NotSupported, null, nse.Message); + arg.Error(issueComponent); + } + throw; + } + } + + protected abstract Task Search(string resourceType, IArgumentCollection arguments, SearchOptions options); +} diff --git a/Vonk.Facade.Relational/Vonk.props b/Vonk.Facade.Relational/Vonk.props index 326656e..451d75b 100644 --- a/Vonk.Facade.Relational/Vonk.props +++ b/Vonk.Facade.Relational/Vonk.props @@ -4,18 +4,18 @@ Christiaan Knaap and contributors Firely (https://fire.ly) - Copyright 2015-2021 Firely. Contains materials (C) HL7 International + Copyright 2015-2025 Firely. Contains materials (C) HL7 International https://fire.ly/products/server See https://docs.fire.ly/vonk/releasenotes/releasenotes.html - https://simplifier.net/vonk/download - 5.0.0 + LICENSE.md + 6.0.0 alpha net8.0 2.0.0 false false false - 8.0 + 12.0 @@ -25,7 +25,7 @@ - 5.*-* + 6.*-*