diff --git a/Expressions.sln b/Expressions.sln index ace1aaa..6e67915 100644 --- a/Expressions.sln +++ b/Expressions.sln @@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Tests", "tests\Commo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Expressions.Testing", "src\Expressions.Testing\Expressions.Testing.csproj", "{C981D09E-DFC0-4DB5-AFED-793251F72FE3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Expressions.Reading.Tests", "tests\Expressions.Reading.Tests\Expressions.Reading.Tests.csproj", "{5E890439-44D5-4332-8269-4CA153834BF3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,7 @@ Global {C0BA44CB-2303-4BC0-A4BF-A0195E5CF6DC} = {D47941D6-CDE2-4B95-B20B-7FE8F94F772B} {81C39727-4C58-4079-98DF-782073DBC61E} = {D47941D6-CDE2-4B95-B20B-7FE8F94F772B} {C981D09E-DFC0-4DB5-AFED-793251F72FE3} = {4695B427-BD61-4FA2-A3F4-995AC92C9B02} + {5E890439-44D5-4332-8269-4CA153834BF3} = {D47941D6-CDE2-4B95-B20B-7FE8F94F772B} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CC54C0A1-1E81-4D00-9EB0-C2D7711A574D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -114,5 +117,9 @@ Global {C981D09E-DFC0-4DB5-AFED-793251F72FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {C981D09E-DFC0-4DB5-AFED-793251F72FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C981D09E-DFC0-4DB5-AFED-793251F72FE3}.Release|Any CPU.Build.0 = Release|Any CPU + {5E890439-44D5-4332-8269-4CA153834BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E890439-44D5-4332-8269-4CA153834BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E890439-44D5-4332-8269-4CA153834BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E890439-44D5-4332-8269-4CA153834BF3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/samples/Helpdesk.Relational/Incidents/GetDetails/GetIncidentDetailsQueryModel.cs b/samples/Helpdesk.Relational/Incidents/GetDetails/GetIncidentDetailsQueryModel.cs index a0524c9..2311284 100644 --- a/samples/Helpdesk.Relational/Incidents/GetDetails/GetIncidentDetailsQueryModel.cs +++ b/samples/Helpdesk.Relational/Incidents/GetDetails/GetIncidentDetailsQueryModel.cs @@ -3,7 +3,7 @@ namespace Helpdesk.Relational.Incidents.GetDetails; -public class GetIncidentDetailsQueryModel : QueryModel +public class GetIncidentDetailsQueryModel : EntityQueryModel { public GetIncidentDetailsQueryModel(Guid incidentId) => IncidentId = incidentId; diff --git a/samples/Helpdesk.Relational/Incidents/GetShortInfo/GetIncidentShortInfoQueryModel.cs b/samples/Helpdesk.Relational/Incidents/GetShortInfo/GetIncidentShortInfoQueryModel.cs index a3c0813..cdbc892 100644 --- a/samples/Helpdesk.Relational/Incidents/GetShortInfo/GetIncidentShortInfoQueryModel.cs +++ b/samples/Helpdesk.Relational/Incidents/GetShortInfo/GetIncidentShortInfoQueryModel.cs @@ -3,7 +3,7 @@ namespace Helpdesk.Relational.Incidents.GetShortInfo; -public class GetIncidentShortInfoQueryModel : QueryModel +public class GetIncidentShortInfoQueryModel : EntityQueryModel { public GetIncidentShortInfoQueryModel(Guid customerId, int pageNumber, int pageSize) { diff --git a/src/Expressions.EntityFrameworkCore/Sessions/EfDbSession.cs b/src/Expressions.EntityFrameworkCore/Sessions/EfDbSession.cs index 2b19dab..c800349 100644 --- a/src/Expressions.EntityFrameworkCore/Sessions/EfDbSession.cs +++ b/src/Expressions.EntityFrameworkCore/Sessions/EfDbSession.cs @@ -48,15 +48,7 @@ await Context .ConfigureAwait(false); } - public IQuery Query(IQueryModel queryModel) - where TEntity : class - { - return new EfQuery( - _logger, - DataSourceFactory.GetDbSet(Context, Tracking).Apply(queryModel)); - } - - public IQuery Query(IMultiQueryModel queryModel) + public IQuery Query(IQueryModel queryModel) { return new EfQuery( _logger, diff --git a/src/Expressions.Marten/Sessions/MartenDbQuerySession.cs b/src/Expressions.Marten/Sessions/MartenDbQuerySession.cs index c32b349..35c1c57 100644 --- a/src/Expressions.Marten/Sessions/MartenDbQuerySession.cs +++ b/src/Expressions.Marten/Sessions/MartenDbQuerySession.cs @@ -19,13 +19,7 @@ public MartenDbQuerySession(ILogger logger, IQuerySession _querySource = new MartenQuerySource(session); } - public IQuery Query(IQueryModel queryModel) - where TEntity : class - { - return new MartenQuery(_logger, _session.Query().Apply(queryModel)); - } - - public IQuery Query(IMultiQueryModel queryModel) + public IQuery Query(IQueryModel queryModel) { return new MartenQuery(_logger, queryModel.Execute(_querySource)); } diff --git a/src/Expressions.Reading/Queries/EntityQueryModel.cs b/src/Expressions.Reading/Queries/EntityQueryModel.cs new file mode 100644 index 0000000..af74f18 --- /dev/null +++ b/src/Expressions.Reading/Queries/EntityQueryModel.cs @@ -0,0 +1,21 @@ +using Raiqub.Expressions.Queries.Internal; + +namespace Raiqub.Expressions.Queries; + +public static class EntityQueryModel +{ + /// + /// Returns a query model for the specified type that does nothing. + /// + /// The type of the elements of query model. + /// A query model for the specified type . + public static IEntityQueryModel Create() where T : class => + AllQueryModel.Instance; + + public static IEntityQueryModel Create(Specification specification) where T : class => + new SpecificationQueryModel(specification); + + public static IEntityQueryModel Create( + Func, IQueryable> queryModel) where TSource : class => + new AnonymousEntityQueryModel(queryModel); +} diff --git a/src/Expressions/Queries/QueryModel{TSource,TResult}.cs b/src/Expressions.Reading/Queries/EntityQueryModel{TSource,TResult}.cs similarity index 73% rename from src/Expressions/Queries/QueryModel{TSource,TResult}.cs rename to src/Expressions.Reading/Queries/EntityQueryModel{TSource,TResult}.cs index 453a5b5..9d6bf13 100644 --- a/src/Expressions/Queries/QueryModel{TSource,TResult}.cs +++ b/src/Expressions.Reading/Queries/EntityQueryModel{TSource,TResult}.cs @@ -1,37 +1,39 @@ namespace Raiqub.Expressions.Queries; /// -/// Represents a base implementation of the interface that provides +/// Represents a base implementation of the interface that provides /// a mechanism for defining and executing queries. /// /// The type of the data source for the query model. /// The type of the query result. -public abstract class QueryModel : IQueryModel +public abstract class EntityQueryModel + : IEntityQueryModel, IQueryModel + where TSource : class { private readonly IEnumerable> _restrictions; /// - /// Initializes a new instance of the class with no restrictions. + /// Initializes a new instance of the class with no restrictions. /// - protected QueryModel() + protected EntityQueryModel() { _restrictions = Enumerable.Empty>(); } /// - /// Initializes a new instance of the class with the specified restrictions. + /// Initializes a new instance of the class with the specified restrictions. /// /// The restrictions to apply to the query. - protected QueryModel(params Specification[] restrictions) + protected EntityQueryModel(params Specification[] restrictions) : this((IEnumerable>)restrictions) { } /// - /// Initializes a new instance of the class with the specified restrictions. + /// Initializes a new instance of the class with the specified restrictions. /// /// The restrictions to apply to the query. - protected QueryModel(IEnumerable> restrictions) + protected EntityQueryModel(IEnumerable> restrictions) { _restrictions = restrictions.ToList(); } @@ -59,6 +61,11 @@ public IEnumerable Execute(IEnumerable source) return Execute(source.AsQueryable()); } + public IQueryable Execute(IQuerySource source) + { + return Execute(source.GetSet()); + } + /// Gets the mandatory preconditions to execute the query. /// An that represents the preconditions for the query model. protected virtual IEnumerable> GetPreconditions() => diff --git a/src/Expressions.Reading/Queries/EntityQueryModel{T}.cs b/src/Expressions.Reading/Queries/EntityQueryModel{T}.cs new file mode 100644 index 0000000..c259c04 --- /dev/null +++ b/src/Expressions.Reading/Queries/EntityQueryModel{T}.cs @@ -0,0 +1,7 @@ +namespace Raiqub.Expressions.Queries; + +public abstract class EntityQueryModel : EntityQueryModel, IEntityQueryModel + where T : class +{ + protected override IQueryable ExecuteCore(IQueryable source) => source; +} diff --git a/src/Expressions/Queries/QueryModel.cs b/src/Expressions.Reading/Queries/EntiyQueryModelExtensions.cs similarity index 50% rename from src/Expressions/Queries/QueryModel.cs rename to src/Expressions.Reading/Queries/EntiyQueryModelExtensions.cs index 1827483..a46734f 100644 --- a/src/Expressions/Queries/QueryModel.cs +++ b/src/Expressions.Reading/Queries/EntiyQueryModelExtensions.cs @@ -1,29 +1,16 @@ -namespace Raiqub.Expressions.Queries; +using Raiqub.Expressions.Queries.Internal; -public static class QueryModel -{ - /// - /// Returns a query model for the specified type that does nothing. - /// - /// The type of the elements of query model. - /// A query model for the specified type . - public static IQueryModel Create() => - AllQueryModel.Instance; - - public static IQueryModel Create(Specification specification) => - new SpecificationQueryModel(specification); - - public static IQueryModel Create( - Func, IQueryable> queryModel) => - new AnonymousQueryModel(queryModel); +namespace Raiqub.Expressions.Queries; +public static class EntiyQueryModelExtensions +{ /// Prepares a query model for down-casting. /// The type of the data source. /// The type of the query result. /// The query model to downcast. /// An instance that allow down-casting the specified query model. public static QueryModelDownCasting DownCast( - this IQueryModel queryModel) + this IEntityQueryModel queryModel) where TSource : class, TResult => new(queryModel); /// Prepares a query model for casting the source type. @@ -32,5 +19,16 @@ public static QueryModelDownCasting DownCast /// The query model to cast. /// An instance that allow casting the source type of the specified query model. public static QueryModelSourceCasting SourceCast( - this IQueryModel queryModel) => new(queryModel); + this IEntityQueryModel queryModel) => new(queryModel); + + /// + /// Convert a to . + /// + /// The entity query model to convert. + /// The source type of the query model. + /// The result type of the query model. + /// An instance of representing the exact query model as specified by . + public static IQueryModel ToQueryModel( + this IEntityQueryModel queryModel) where TSource : class => + queryModel as IQueryModel ?? new EntityQueryModelWrapper(queryModel); } diff --git a/src/Expressions/Queries/IQueryModel.cs b/src/Expressions.Reading/Queries/IEntityQueryModel.cs similarity index 92% rename from src/Expressions/Queries/IQueryModel.cs rename to src/Expressions.Reading/Queries/IEntityQueryModel.cs index 3164af4..b4b2ef0 100644 --- a/src/Expressions/Queries/IQueryModel.cs +++ b/src/Expressions.Reading/Queries/IEntityQueryModel.cs @@ -6,7 +6,7 @@ /// /// The type of the data source. /// The type of the query result. -public interface IQueryModel +public interface IEntityQueryModel { /// /// Executes the query on the specified data source of type and returns a query @@ -30,6 +30,6 @@ public interface IQueryModel /// a result of the same type. /// /// The type of the data source and the query result. -public interface IQueryModel : IQueryModel +public interface IEntityQueryModel : IEntityQueryModel { } diff --git a/src/Expressions.Reading/Queries/IMultiQueryModel.cs b/src/Expressions.Reading/Queries/IQueryModel.cs similarity index 93% rename from src/Expressions.Reading/Queries/IMultiQueryModel.cs rename to src/Expressions.Reading/Queries/IQueryModel.cs index c8f6295..cc5f298 100644 --- a/src/Expressions.Reading/Queries/IMultiQueryModel.cs +++ b/src/Expressions.Reading/Queries/IQueryModel.cs @@ -5,7 +5,7 @@ /// and return a result of type . /// /// The type of the query result. -public interface IMultiQueryModel +public interface IQueryModel { /// /// Executes the query using the specified query source and diff --git a/src/Expressions.Reading/Queries/Internal/AllQueryModel.cs b/src/Expressions.Reading/Queries/Internal/AllQueryModel.cs new file mode 100644 index 0000000..604e6b9 --- /dev/null +++ b/src/Expressions.Reading/Queries/Internal/AllQueryModel.cs @@ -0,0 +1,13 @@ +namespace Raiqub.Expressions.Queries.Internal; + +internal sealed class AllQueryModel : IEntityQueryModel, IQueryModel + where T : class +{ + public static readonly AllQueryModel Instance = new AllQueryModel(); + + public IQueryable Execute(IQueryable source) => source; + + public IEnumerable Execute(IEnumerable source) => source; + + public IQueryable Execute(IQuerySource source) => source.GetSet(); +} diff --git a/src/Expressions.Reading/Queries/Internal/AnonymousEntityQueryModel.cs b/src/Expressions.Reading/Queries/Internal/AnonymousEntityQueryModel.cs new file mode 100644 index 0000000..56a9785 --- /dev/null +++ b/src/Expressions.Reading/Queries/Internal/AnonymousEntityQueryModel.cs @@ -0,0 +1,19 @@ +namespace Raiqub.Expressions.Queries.Internal; + +internal sealed class AnonymousEntityQueryModel + : IEntityQueryModel, IQueryModel + where TSource : class +{ + private readonly Func, IQueryable> _queryModel; + + public AnonymousEntityQueryModel(Func, IQueryable> queryModel) + { + _queryModel = queryModel; + } + + public IQueryable Execute(IQueryable source) => _queryModel(source); + + public IEnumerable Execute(IEnumerable source) => _queryModel(source.AsQueryable()); + + public IQueryable Execute(IQuerySource source) => _queryModel(source.GetSet()); +} diff --git a/src/Expressions.Reading/Queries/Internal/AnonymousQueryModel.cs b/src/Expressions.Reading/Queries/Internal/AnonymousQueryModel.cs new file mode 100644 index 0000000..977050c --- /dev/null +++ b/src/Expressions.Reading/Queries/Internal/AnonymousQueryModel.cs @@ -0,0 +1,10 @@ +namespace Raiqub.Expressions.Queries.Internal; + +internal sealed class AnonymousQueryModel : IQueryModel +{ + private readonly Func> _queryModel; + + public AnonymousQueryModel(Func> queryModel) => _queryModel = queryModel; + + public IQueryable Execute(IQuerySource source) => _queryModel(source); +} diff --git a/src/Expressions.Reading/Queries/Internal/DerivedEntityQueryModel.cs b/src/Expressions.Reading/Queries/Internal/DerivedEntityQueryModel.cs new file mode 100644 index 0000000..845193f --- /dev/null +++ b/src/Expressions.Reading/Queries/Internal/DerivedEntityQueryModel.cs @@ -0,0 +1,20 @@ +namespace Raiqub.Expressions.Queries.Internal; + +internal sealed class DerivedEntityQueryModel + : IEntityQueryModel, IQueryModel + where TDerived : class, TParent +{ + private readonly IEntityQueryModel _originalQueryModel; + + public DerivedEntityQueryModel(IEntityQueryModel originalQueryModel) => + _originalQueryModel = originalQueryModel; + + public IQueryable Execute(IQueryable source) => + _originalQueryModel.Execute(source).OfType(); + + public IEnumerable Execute(IEnumerable source) => + _originalQueryModel.Execute(source).OfType(); + + public IQueryable Execute(IQuerySource source) => + Execute(source.GetSet()); +} diff --git a/src/Expressions.Reading/Queries/Internal/EntityQueryModelWrapper.cs b/src/Expressions.Reading/Queries/Internal/EntityQueryModelWrapper.cs new file mode 100644 index 0000000..bb0ae0c --- /dev/null +++ b/src/Expressions.Reading/Queries/Internal/EntityQueryModelWrapper.cs @@ -0,0 +1,14 @@ +namespace Raiqub.Expressions.Queries.Internal; + +internal sealed class EntityQueryModelWrapper : IQueryModel + where TSource : class +{ + private readonly IEntityQueryModel _entityQueryModel; + + public EntityQueryModelWrapper(IEntityQueryModel entityQueryModel) + { + _entityQueryModel = entityQueryModel; + } + + public IQueryable Execute(IQuerySource source) => source.GetSetUsing(_entityQueryModel); +} diff --git a/src/Expressions.Reading/Queries/NestingQueryModel.cs b/src/Expressions.Reading/Queries/Internal/NestingEntityQueryModel.cs similarity index 53% rename from src/Expressions.Reading/Queries/NestingQueryModel.cs rename to src/Expressions.Reading/Queries/Internal/NestingEntityQueryModel.cs index 7066676..2a7dce6 100644 --- a/src/Expressions.Reading/Queries/NestingQueryModel.cs +++ b/src/Expressions.Reading/Queries/Internal/NestingEntityQueryModel.cs @@ -1,15 +1,17 @@ using System.Linq.Expressions; -namespace Raiqub.Expressions.Queries; +namespace Raiqub.Expressions.Queries.Internal; -internal sealed class NestingQueryModel : IQueryModel +internal sealed class NestingEntityQueryModel + : IEntityQueryModel, IQueryModel + where TEntity : class { private readonly Expression>> _selector; - private readonly IQueryModel _nestedQueryModel; + private readonly IEntityQueryModel _nestedQueryModel; - public NestingQueryModel( + public NestingEntityQueryModel( Expression>> selector, - IQueryModel nestedQueryModel) + IEntityQueryModel nestedQueryModel) { _selector = selector; _nestedQueryModel = nestedQueryModel; @@ -20,4 +22,6 @@ public IQueryable Execute(IQueryable source) => public IEnumerable Execute(IEnumerable source) => _nestedQueryModel.Execute(source.SelectMany(_selector.Compile())); + + public IQueryable Execute(IQuerySource source) => Execute(source.GetSet()); } diff --git a/src/Expressions/Queries/SpecificationQueryModel.cs b/src/Expressions.Reading/Queries/Internal/SpecificationQueryModel.cs similarity index 57% rename from src/Expressions/Queries/SpecificationQueryModel.cs rename to src/Expressions.Reading/Queries/Internal/SpecificationQueryModel.cs index f4f0ff3..08fde1a 100644 --- a/src/Expressions/Queries/SpecificationQueryModel.cs +++ b/src/Expressions.Reading/Queries/Internal/SpecificationQueryModel.cs @@ -1,6 +1,7 @@ -namespace Raiqub.Expressions.Queries; +namespace Raiqub.Expressions.Queries.Internal; -internal sealed class SpecificationQueryModel : IQueryModel +internal sealed class SpecificationQueryModel : IEntityQueryModel, IQueryModel + where T : class { private readonly Specification _specification; @@ -9,4 +10,6 @@ internal sealed class SpecificationQueryModel : IQueryModel public IQueryable Execute(IQueryable source) => source.Where(_specification); public IEnumerable Execute(IEnumerable source) => source.Where(_specification); + + public IQueryable Execute(IQuerySource source) => source.GetSet().Where(_specification); } diff --git a/src/Expressions.Reading/Queries/QueryModel.cs b/src/Expressions.Reading/Queries/QueryModel.cs new file mode 100644 index 0000000..5f57cd3 --- /dev/null +++ b/src/Expressions.Reading/Queries/QueryModel.cs @@ -0,0 +1,9 @@ +using Raiqub.Expressions.Queries.Internal; + +namespace Raiqub.Expressions.Queries; + +public static class QueryModel +{ + public static IQueryModel Create(Func> queryModel) => + new AnonymousQueryModel(queryModel); +} diff --git a/src/Expressions/Queries/QueryModelDownCasting.cs b/src/Expressions.Reading/Queries/QueryModelDownCasting.cs similarity index 59% rename from src/Expressions/Queries/QueryModelDownCasting.cs rename to src/Expressions.Reading/Queries/QueryModelDownCasting.cs index af6a5d6..be9bc45 100644 --- a/src/Expressions/Queries/QueryModelDownCasting.cs +++ b/src/Expressions.Reading/Queries/QueryModelDownCasting.cs @@ -1,24 +1,26 @@ -namespace Raiqub.Expressions.Queries; +using Raiqub.Expressions.Queries.Internal; -/// Represents the operation of down-casting a . +namespace Raiqub.Expressions.Queries; + +/// Represents the operation of down-casting a . /// The type of the data source. /// The type of the query result. public readonly ref struct QueryModelDownCasting where TSource : class, TResult { - private readonly IQueryModel _queryModel; + private readonly IEntityQueryModel _queryModel; - public QueryModelDownCasting(IQueryModel queryModel) => _queryModel = queryModel; + public QueryModelDownCasting(IEntityQueryModel queryModel) => _queryModel = queryModel; /// Returns a query model that has been downcasted to the source type. /// A query model that has been downcasted to the source type. - public IQueryModel Create() => - new DerivedQueryModel(_queryModel); + public IEntityQueryModel Create() => + new DerivedEntityQueryModel(_queryModel); /// Returns a query model that has been downcasted to the specified derived type. /// The derived type of the query model. /// A query model that has been downcasted to the specified derived type. - public IQueryModel To() + public IEntityQueryModel To() where TDerived : class, TSource => - new DerivedQueryModel(_queryModel); + new DerivedEntityQueryModel(_queryModel); } diff --git a/src/Expressions/Queries/QueryModelSourceCasting.cs b/src/Expressions.Reading/Queries/QueryModelSourceCasting.cs similarity index 70% rename from src/Expressions/Queries/QueryModelSourceCasting.cs rename to src/Expressions.Reading/Queries/QueryModelSourceCasting.cs index adc3eda..a6dc905 100644 --- a/src/Expressions/Queries/QueryModelSourceCasting.cs +++ b/src/Expressions.Reading/Queries/QueryModelSourceCasting.cs @@ -1,19 +1,19 @@ namespace Raiqub.Expressions.Queries; /// -/// Represents the operation of casting the source type of . +/// Represents the operation of casting the source type of . /// /// The type of the data source. /// The type of the query result. public readonly ref struct QueryModelSourceCasting { - private readonly IQueryModel _queryModel; + private readonly IEntityQueryModel _queryModel; - public QueryModelSourceCasting(IQueryModel queryModel) => _queryModel = queryModel; + public QueryModelSourceCasting(IEntityQueryModel queryModel) => _queryModel = queryModel; /// Returns a query model with the source type casted to the specified derived type. /// The derived type to cast the source type to. /// A query model with the source type casted to the specified derived type. - public IQueryModel Of() + public IEntityQueryModel Of() where TDerived : class, TSource => _queryModel; } diff --git a/src/Expressions.Reading/Queries/QuerySourceExtensions.cs b/src/Expressions.Reading/Queries/QuerySourceExtensions.cs new file mode 100644 index 0000000..8326d42 --- /dev/null +++ b/src/Expressions.Reading/Queries/QuerySourceExtensions.cs @@ -0,0 +1,32 @@ +namespace Raiqub.Expressions.Queries; + +/// Provides extensions for interface. +public static class QuerySourceExtensions +{ + /// Gets the data source using the query defined by the specified entity query model. + /// The provider to get data source from. + /// The query model to apply to the data source. + /// The type of the data source. + /// The type of the query result. + /// The data source of type using the specified query model. + public static IQueryable GetSetUsing( + this IQuerySource querySource, + IEntityQueryModel queryModel) + where TSource : class + { + return queryModel.Execute(querySource.GetSet()); + } + + /// Gets the data source applying the specified criteria. + /// The provider to get data source from. + /// The specification to apply to the data source. + /// The type of the data source. + /// The data source of type returning data that matches the specified criteria. + public static IQueryable GetSetUsing( + this IQuerySource querySource, + Specification specification) + where TSource : class + { + return querySource.GetSet().Where(specification); + } +} diff --git a/src/Expressions.Reading/QueryModelEnumerableExtensions.cs b/src/Expressions.Reading/QueryModelEnumerableExtensions.cs new file mode 100644 index 0000000..a57447f --- /dev/null +++ b/src/Expressions.Reading/QueryModelEnumerableExtensions.cs @@ -0,0 +1,14 @@ +using Raiqub.Expressions.Queries; + +namespace Raiqub.Expressions; + +public static class QueryModelEnumerableExtensions +{ + public static IEnumerable Apply( + this IEnumerable source, + IEntityQueryModel queryModel) => queryModel.Execute(source); + + public static IQueryable Apply( + this IQueryable queryable, + IEntityQueryModel queryModel) => queryModel.Execute(queryable); +} diff --git a/src/Expressions.Reading/Sessions/DbQuerySessionExtensions.cs b/src/Expressions.Reading/Sessions/DbQuerySessionExtensions.cs index 099c2fe..0ad8cb7 100644 --- a/src/Expressions.Reading/Sessions/DbQuerySessionExtensions.cs +++ b/src/Expressions.Reading/Sessions/DbQuerySessionExtensions.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using Raiqub.Expressions.Queries; +using Raiqub.Expressions.Queries.Internal; namespace Raiqub.Expressions.Sessions; @@ -14,7 +15,7 @@ public static IQuery Query( this IDbQuerySession session) where TEntity : class { - return session.Query(QueryModel.Create()); + return session.Query(AllQueryModel.Instance); } /// Creates a new query using the criteria specification. @@ -27,7 +28,15 @@ public static IQuery Query( Specification specification) where TEntity : class { - return session.Query(QueryModel.Create(specification)); + return session.Query(new SpecificationQueryModel(specification)); + } + + public static IQuery Query( + this IDbQuerySession session, + IEntityQueryModel entityQueryModel) + where TEntity : class + { + return session.Query(entityQueryModel.ToQueryModel()); } /// @@ -42,8 +51,10 @@ public static IQuery QueryNested( this IDbQuerySession session, Expression>> selector) where TEntity : class + where TNested : class { - return session.Query(new NestingQueryModel(selector, QueryModel.Create())); + return session.Query( + new NestingEntityQueryModel(selector, EntityQueryModel.Create())); } /// @@ -59,10 +70,11 @@ public static IQuery QueryNested( public static IQuery QueryNested( this IDbQuerySession session, Expression>> selector, - IQueryModel queryModel) + IEntityQueryModel queryModel) where TEntity : class + where TNested : class { - return session.Query(new NestingQueryModel(selector, queryModel)); + return session.Query(new NestingEntityQueryModel(selector, queryModel)); } /// @@ -79,8 +91,9 @@ public static IQuery QueryNested( Expression>> selector, Specification specification) where TEntity : class + where TNested : class { return session.Query( - new NestingQueryModel(selector, QueryModel.Create(specification))); + new NestingEntityQueryModel(selector, EntityQueryModel.Create(specification))); } } diff --git a/src/Expressions.Reading/Sessions/IDbQuerySession.cs b/src/Expressions.Reading/Sessions/IDbQuerySession.cs index 80b728f..c91378f 100644 --- a/src/Expressions.Reading/Sessions/IDbQuerySession.cs +++ b/src/Expressions.Reading/Sessions/IDbQuerySession.cs @@ -6,18 +6,9 @@ namespace Raiqub.Expressions.Sessions; public interface IDbQuerySession : IAsyncDisposable, IDisposable { /// Creates a new query using the specified query model. - /// The type of entity to query. /// The type of result to return. /// The query model to use. /// A new query object. /// Thrown when is null. - IQuery Query(IQueryModel queryModel) - where TEntity : class; - - /// Creates a new query using the specified query model. - /// The type of result to return. - /// The query model to use. - /// A new query object. - /// Thrown when is null. - IQuery Query(IMultiQueryModel queryModel); + IQuery Query(IQueryModel queryModel); } diff --git a/src/Expressions/EnumerableExtensions.cs b/src/Expressions/EnumerableExtensions.cs deleted file mode 100644 index 5549d80..0000000 --- a/src/Expressions/EnumerableExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Raiqub.Expressions.Queries; - -namespace Raiqub.Expressions; - -public static class EnumerableExtensions -{ - public static IEnumerable Apply( - this IEnumerable source, - IQueryModel queryModel) => queryModel.Execute(source); - - public static IQueryable Apply( - this IQueryable queryable, - IQueryModel queryModel) => queryModel.Execute(queryable); - - public static IEnumerable Where(this IEnumerable source, Specification specification) => - source.Where(specification.IsSatisfiedBy); - - public static IQueryable Where(this IQueryable queryable, Specification specification) => - queryable.Where(specification.ToExpression()); -} diff --git a/src/Expressions/Queries/AllQueryModel.cs b/src/Expressions/Queries/AllQueryModel.cs deleted file mode 100644 index dabb22e..0000000 --- a/src/Expressions/Queries/AllQueryModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Raiqub.Expressions.Queries; - -internal sealed class AllQueryModel : IQueryModel -{ - public static readonly IQueryModel Instance = new AllQueryModel(); - - public IQueryable Execute(IQueryable source) => source; - - public IEnumerable Execute(IEnumerable source) => source; -} diff --git a/src/Expressions/Queries/AnonymousQueryModel.cs b/src/Expressions/Queries/AnonymousQueryModel.cs deleted file mode 100644 index 30aa800..0000000 --- a/src/Expressions/Queries/AnonymousQueryModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Raiqub.Expressions.Queries; - -internal sealed class AnonymousQueryModel : IQueryModel -{ - private readonly Func, IQueryable> _queryModel; - - public AnonymousQueryModel(Func, IQueryable> queryModel) - { - _queryModel = queryModel; - } - - public IQueryable Execute(IQueryable source) => _queryModel(source); - - public IEnumerable Execute(IEnumerable source) => _queryModel(source.AsQueryable()); -} diff --git a/src/Expressions/Queries/DerivedQueryModel.cs b/src/Expressions/Queries/DerivedQueryModel.cs deleted file mode 100644 index 809e3ae..0000000 --- a/src/Expressions/Queries/DerivedQueryModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Raiqub.Expressions.Queries; - -internal sealed class DerivedQueryModel : IQueryModel - where TDerived : class, TParent -{ - private readonly IQueryModel _originalQueryModel; - - public DerivedQueryModel(IQueryModel originalQueryModel) => - _originalQueryModel = originalQueryModel; - - public IQueryable Execute(IQueryable source) => - _originalQueryModel.Execute(source).OfType(); - - public IEnumerable Execute(IEnumerable source) => - _originalQueryModel.Execute(source).OfType(); -} diff --git a/src/Expressions/Queries/QueryModel{T}.cs b/src/Expressions/Queries/QueryModel{T}.cs deleted file mode 100644 index 5c8e8a4..0000000 --- a/src/Expressions/Queries/QueryModel{T}.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Raiqub.Expressions.Queries; - -public abstract class QueryModel : QueryModel, IQueryModel -{ -} diff --git a/src/Expressions/SpecificationEnumerableExtensions.cs b/src/Expressions/SpecificationEnumerableExtensions.cs new file mode 100644 index 0000000..9317398 --- /dev/null +++ b/src/Expressions/SpecificationEnumerableExtensions.cs @@ -0,0 +1,10 @@ +namespace Raiqub.Expressions; + +public static class SpecificationEnumerableExtensions +{ + public static IEnumerable Where(this IEnumerable source, Specification specification) => + source.Where(specification.IsSatisfiedBy); + + public static IQueryable Where(this IQueryable queryable, Specification specification) => + queryable.Where(specification.ToExpression()); +} diff --git a/tests/Common.Tests/Examples/GetBlogByNameQueryModel.cs b/tests/Common.Tests/Examples/GetBlogByNameQueryModel.cs index 7e427b4..6196172 100644 --- a/tests/Common.Tests/Examples/GetBlogByNameQueryModel.cs +++ b/tests/Common.Tests/Examples/GetBlogByNameQueryModel.cs @@ -3,7 +3,7 @@ namespace Raiqub.Common.Tests.Examples; -public class GetBlogByNameQueryModel : QueryModel +public class GetBlogByNameQueryModel : EntityQueryModel { public GetBlogByNameQueryModel(string name) => Name = name; diff --git a/tests/Common.Tests/Examples/GetBlogPostsAggregateQueryModel.cs b/tests/Common.Tests/Examples/GetBlogPostsAggregateQueryModel.cs index b5a6246..a58b0ce 100644 --- a/tests/Common.Tests/Examples/GetBlogPostsAggregateQueryModel.cs +++ b/tests/Common.Tests/Examples/GetBlogPostsAggregateQueryModel.cs @@ -3,7 +3,7 @@ namespace Raiqub.Common.Tests.Examples; -public class GetBlogPostsAggregateQueryModel : QueryModel +public class GetBlogPostsAggregateQueryModel : EntityQueryModel { public GetBlogPostsAggregateQueryModel(string name) => Name = name; diff --git a/tests/Common.Tests/Queries/QueryTestBase.cs b/tests/Common.Tests/Queries/QueryTestBase.cs index 5c879ef..712acfd 100644 --- a/tests/Common.Tests/Queries/QueryTestBase.cs +++ b/tests/Common.Tests/Queries/QueryTestBase.cs @@ -92,7 +92,7 @@ public async Task ToListShouldReturnAll() { await AddBlogs(GetBlogs()); await using var session = CreateSession(); - var query = session.Query(QueryModel.Create((IQueryable source) => source.SelectMany(b => b.Posts))); + var query = session.Query(EntityQueryModel.Create((IQueryable source) => source.SelectMany(b => b.Posts))); var posts = await query.ToListAsync(); @@ -106,7 +106,7 @@ public async Task ToListShouldReturnExpected() await AddBlogs(GetBlogs()); await using var session = CreateSession(); var query = session.Query( - QueryModel.Create( + EntityQueryModel.Create( (IQueryable source) => source .SelectMany(b => b.Posts) .Where(p => p.Content.StartsWith("You")))); diff --git a/tests/Common.Tests/Sessions/SessionFactoryTestBase.cs b/tests/Common.Tests/Sessions/SessionFactoryTestBase.cs index e9d116d..eaeaa59 100644 --- a/tests/Common.Tests/Sessions/SessionFactoryTestBase.cs +++ b/tests/Common.Tests/Sessions/SessionFactoryTestBase.cs @@ -128,7 +128,7 @@ public async Task UpdateRangeAndSaveShouldCommitChanges() await using (var session = sessionFactory.Create()) { finalBlogs = await session - .Query(QueryModel.Create((IQueryable source) => source.OrderBy(b => b.Name))) + .Query(EntityQueryModel.Create((IQueryable source) => source.OrderBy(b => b.Name))) .ToListAsync(); } diff --git a/tests/Expressions.Reading.Tests/Examples/BlogPost.cs b/tests/Expressions.Reading.Tests/Examples/BlogPost.cs new file mode 100644 index 0000000..a2bc251 --- /dev/null +++ b/tests/Expressions.Reading.Tests/Examples/BlogPost.cs @@ -0,0 +1,39 @@ +namespace Raiqub.Expressions.Reading.Tests.Examples; + +public class BlogPost +{ + public BlogPost(int id, string title, string content, string postType) + { + Id = id; + Title = title; + Content = content; + PostType = postType; + } + + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public string PostType { get; set; } // discriminator column +} + +public class NewsPost : BlogPost +{ + public NewsPost(int id, string title, string content, string postType, string author) + : base(id, title, content, postType) + { + Author = author; + } + + public string Author { get; set; } +} + +public class VideoPost : BlogPost +{ + public VideoPost(int id, string title, string content, string postType, string videoUrl) + : base(id, title, content, postType) + { + VideoUrl = videoUrl; + } + + public string VideoUrl { get; set; } +} diff --git a/tests/Expressions.Tests/Examples/BlogPostContentSearchQueryModel.cs b/tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchQueryModel.cs similarity index 76% rename from tests/Expressions.Tests/Examples/BlogPostContentSearchQueryModel.cs rename to tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchQueryModel.cs index 9d8067b..ef845e9 100644 --- a/tests/Expressions.Tests/Examples/BlogPostContentSearchQueryModel.cs +++ b/tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchQueryModel.cs @@ -1,8 +1,8 @@ using Raiqub.Expressions.Queries; -namespace Raiqub.Expressions.Tests.Examples; +namespace Raiqub.Expressions.Reading.Tests.Examples; -public class BlogPostContentSearchQueryModel : QueryModel +public class BlogPostContentSearchQueryModel : EntityQueryModel { public BlogPostContentSearchQueryModel(string search) => Search = search; diff --git a/tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchSpecification.cs b/tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchSpecification.cs new file mode 100644 index 0000000..bae53b8 --- /dev/null +++ b/tests/Expressions.Reading.Tests/Examples/BlogPostContentSearchSpecification.cs @@ -0,0 +1,12 @@ +using System.Linq.Expressions; + +namespace Raiqub.Expressions.Reading.Tests.Examples; + +public class BlogPostContentSearchSpecification : Specification +{ + public BlogPostContentSearchSpecification(string search) => Search = search; + + public string Search { get; } + + public override Expression> ToExpression() => p => p.Content.Contains(Search); +} diff --git a/tests/Expressions.Tests/Examples/JohnDeeQueryModel.cs b/tests/Expressions.Reading.Tests/Examples/JohnDeeQueryModel.cs similarity index 81% rename from tests/Expressions.Tests/Examples/JohnDeeQueryModel.cs rename to tests/Expressions.Reading.Tests/Examples/JohnDeeQueryModel.cs index ac50fd2..5732272 100644 --- a/tests/Expressions.Tests/Examples/JohnDeeQueryModel.cs +++ b/tests/Expressions.Reading.Tests/Examples/JohnDeeQueryModel.cs @@ -1,8 +1,8 @@ using Raiqub.Expressions.Queries; -namespace Raiqub.Expressions.Tests.Examples; +namespace Raiqub.Expressions.Reading.Tests.Examples; -public class JohnDeeQueryModel : QueryModel +public class JohnDeeQueryModel : EntityQueryModel { public JohnDeeQueryModel() { diff --git a/tests/Expressions.Tests/Examples/NewsPostContentSearchQueryModel.cs b/tests/Expressions.Reading.Tests/Examples/NewsPostContentSearchQueryModel.cs similarity index 76% rename from tests/Expressions.Tests/Examples/NewsPostContentSearchQueryModel.cs rename to tests/Expressions.Reading.Tests/Examples/NewsPostContentSearchQueryModel.cs index e605f5f..ef85d64 100644 --- a/tests/Expressions.Tests/Examples/NewsPostContentSearchQueryModel.cs +++ b/tests/Expressions.Reading.Tests/Examples/NewsPostContentSearchQueryModel.cs @@ -1,8 +1,8 @@ using Raiqub.Expressions.Queries; -namespace Raiqub.Expressions.Tests.Examples; +namespace Raiqub.Expressions.Reading.Tests.Examples; -public class NewsPostContentSearchQueryModel : QueryModel +public class NewsPostContentSearchQueryModel : EntityQueryModel { public NewsPostContentSearchQueryModel(string search) => Search = search; diff --git a/tests/Expressions.Reading.Tests/Examples/StringBeginsWithJohnSpecification.cs b/tests/Expressions.Reading.Tests/Examples/StringBeginsWithJohnSpecification.cs new file mode 100644 index 0000000..8ce2bc7 --- /dev/null +++ b/tests/Expressions.Reading.Tests/Examples/StringBeginsWithJohnSpecification.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace Raiqub.Expressions.Reading.Tests.Examples; + +internal sealed class StringBeginsWithJohnSpecification : Specification +{ + public override Expression> ToExpression() + { + return input => input.StartsWith("john", StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/tests/Expressions.Tests/Examples/StringContainsMoorSpecification.cs b/tests/Expressions.Reading.Tests/Examples/StringContainsMoorSpecification.cs similarity index 88% rename from tests/Expressions.Tests/Examples/StringContainsMoorSpecification.cs rename to tests/Expressions.Reading.Tests/Examples/StringContainsMoorSpecification.cs index d82945a..5ceaa88 100644 --- a/tests/Expressions.Tests/Examples/StringContainsMoorSpecification.cs +++ b/tests/Expressions.Reading.Tests/Examples/StringContainsMoorSpecification.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace Raiqub.Expressions.Tests.Examples; +namespace Raiqub.Expressions.Reading.Tests.Examples; internal sealed class StringContainsMoorSpecification : Specification { diff --git a/tests/Expressions.Reading.Tests/Examples/StringEndsWithDeeSpecification.cs b/tests/Expressions.Reading.Tests/Examples/StringEndsWithDeeSpecification.cs new file mode 100644 index 0000000..79ab46d --- /dev/null +++ b/tests/Expressions.Reading.Tests/Examples/StringEndsWithDeeSpecification.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace Raiqub.Expressions.Reading.Tests.Examples; + +internal sealed class StringEndsWithDeeSpecification : Specification +{ + public override Expression> ToExpression() + { + return input => input.EndsWith("dee", StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/tests/Expressions.Reading.Tests/Expressions.Reading.Tests.csproj b/tests/Expressions.Reading.Tests/Expressions.Reading.Tests.csproj new file mode 100644 index 0000000..6e46274 --- /dev/null +++ b/tests/Expressions.Reading.Tests/Expressions.Reading.Tests.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + false + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/Expressions.Tests/Queries/QueryModelClassTest.cs b/tests/Expressions.Reading.Tests/Queries/QueryModelClassTest.cs similarity index 96% rename from tests/Expressions.Tests/Queries/QueryModelClassTest.cs rename to tests/Expressions.Reading.Tests/Queries/QueryModelClassTest.cs index e7ad3b4..bb97386 100644 --- a/tests/Expressions.Tests/Queries/QueryModelClassTest.cs +++ b/tests/Expressions.Reading.Tests/Queries/QueryModelClassTest.cs @@ -1,8 +1,8 @@ using FluentAssertions; using Raiqub.Expressions.Queries; -using Raiqub.Expressions.Tests.Examples; +using Raiqub.Expressions.Reading.Tests.Examples; -namespace Raiqub.Expressions.Tests.Queries; +namespace Raiqub.Expressions.Reading.Tests.Queries; public class QueryModelClassTest { diff --git a/tests/Expressions.Tests/Queries/QueryModelTest.cs b/tests/Expressions.Reading.Tests/Queries/QueryModelTest.cs similarity index 63% rename from tests/Expressions.Tests/Queries/QueryModelTest.cs rename to tests/Expressions.Reading.Tests/Queries/QueryModelTest.cs index dbf5b33..403890d 100644 --- a/tests/Expressions.Tests/Queries/QueryModelTest.cs +++ b/tests/Expressions.Reading.Tests/Queries/QueryModelTest.cs @@ -1,16 +1,16 @@ using FluentAssertions; using Raiqub.Expressions.Queries; -using Raiqub.Expressions.Tests.Examples; +using Raiqub.Expressions.Reading.Tests.Examples; -namespace Raiqub.Expressions.Tests.Queries; +namespace Raiqub.Expressions.Reading.Tests.Queries; public class QueryModelTest { [Fact] public void CreateShouldAlwaysReturnAll() { - string?[] source = { "john", "jane", null, "hugo", "jack" }; - var queryModel = QueryModel.Create(); + string[] source = { "john", "jane", "", "hugo", "jack" }; + var queryModel = EntityQueryModel.Create(); string?[] result1 = source .Apply(queryModel) @@ -20,15 +20,15 @@ public void CreateShouldAlwaysReturnAll() .Apply(queryModel) .ToArray(); - result1.Should().Equal("john", "jane", null, "hugo", "jack"); - result2.Should().Equal("john", "jane", null, "hugo", "jack"); + result1.Should().Equal("john", "jane", "", "hugo", "jack"); + result2.Should().Equal("john", "jane", "", "hugo", "jack"); } [Fact] public void CreateShouldEvaluateSpecificationCorrectly() { string[] source = { "john", "jane", "hugo", "jack" }; - var queryModel = QueryModel.Create(new StringBeginsWithJohnSpecification()); + var queryModel = EntityQueryModel.Create(new StringBeginsWithJohnSpecification()); string[] result1 = source .Apply(queryModel) diff --git a/tests/Expressions.Tests/Expressions.Tests.csproj b/tests/Expressions.Tests/Expressions.Tests.csproj index fdcbe7a..bee74ed 100644 --- a/tests/Expressions.Tests/Expressions.Tests.csproj +++ b/tests/Expressions.Tests/Expressions.Tests.csproj @@ -6,7 +6,7 @@ - +