diff --git a/Deveel.Repository.sln b/Deveel.Repository.sln
new file mode 100644
index 0000000..640a832
--- /dev/null
+++ b/Deveel.Repository.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2860FD4D-510F-43C8-870E-5559B90D0CAD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{50434E05-0F21-4871-AFB3-A483CEE4A300}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.Core", "src\Deveel.Repository.Core\Deveel.Repository.Core.csproj", "{EA6BBF15-2CFB-428F-80CB-9CEC546900CD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.MongoDb", "src\Deveel.Repository.MongoDb\Deveel.Repository.MongoDb.csproj", "{E321F743-40CD-4599-B70C-FE9694A845BE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EA6BBF15-2CFB-428F-80CB-9CEC546900CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA6BBF15-2CFB-428F-80CB-9CEC546900CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA6BBF15-2CFB-428F-80CB-9CEC546900CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA6BBF15-2CFB-428F-80CB-9CEC546900CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E321F743-40CD-4599-B70C-FE9694A845BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E321F743-40CD-4599-B70C-FE9694A845BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E321F743-40CD-4599-B70C-FE9694A845BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E321F743-40CD-4599-B70C-FE9694A845BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {EA6BBF15-2CFB-428F-80CB-9CEC546900CD} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
+ {E321F743-40CD-4599-B70C-FE9694A845BE} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {01FD9B16-84B3-4D99-80C1-11B2F3D65B56}
+ EndGlobalSection
+EndGlobal
diff --git a/EULA.md b/EULA.md
new file mode 100644
index 0000000..c069797
--- /dev/null
+++ b/EULA.md
@@ -0,0 +1,43 @@
+## End-User License Agreement (EULA) of *Deveel Framework*
+
+This End-User License Agreement ("EULA") is a legal agreement between you and *Deveel*.
+
+This EULA agreement governs your acquisition and use of our *Deveel Framework* software ("Software") directly from *Deveel* or indirectly through a *Deveel* authorized reseller or distributor (a "Reseller").
+
+Please read this EULA agreement carefully before completing the installation process and using the *Deveel Framework* software. It provides a license to use the *Deveel Framework* software and contains warranty information and liability disclaimers.
+
+If you register for a free trial of the *Deveel Framework* software, this EULA agreement will also govern that trial. By clicking "accept" or installing and/or using the *Deveel Framework* software, you are confirming your acceptance of the Software and agreeing to become bound by the terms of this EULA agreement.
+
+If you are entering into this EULA agreement on behalf of a company or other legal entity, you represent that you have the authority to bind such entity and its affiliates to these terms and conditions. If you do not have such authority or if you do not agree with the terms and conditions of this EULA agreement, do not install or use the Software, and you must not accept this EULA agreement.
+
+This EULA agreement shall apply only to the Software supplied by *Deveel* herewith regardless of whether other software is referred to or described herein. The terms also apply to any *Deveel* updates, supplements, Internet-based services, and support services for the Software, unless other terms accompany those items on delivery. If so, those terms apply.
+
+### License Grant
+
+*Deveel* hereby grants you a personal, non-transferable, non-exclusive licence to use the *Deveel Framework* software on your devices in accordance with the terms of this EULA agreement.
+
+You are permitted to load the *Deveel Framework* software (for example a PC, laptop, mobile or tablet) under your control. You are responsible for ensuring your device meets the minimum requirements of the *Deveel Framework* software.
+
+You are not permitted to:
+
+* Edit, alter, modify, adapt, translate or otherwise change the whole or any part of the Software nor permit the whole or any part of the Software to be combined with or become incorporated in any other software, nor decompile, disassemble or reverse engineer the Software or attempt to do any such things
+* Reproduce, copy, distribute, resell or otherwise use the Software for any commercial purpose
+* Allow any third party to use the Software on behalf of or for the benefit of any third party
+* Use the Software in any way which breaches any applicable local, national or international law
+* use the Software for any purpose that *Deveel* considers is a breach of this EULA agreement
+
+### Intellectual Property and Ownership
+
+*Deveel* shall at all times retain ownership of the Software as originally downloaded by you and all subsequent downloads of the Software by you. The Software (and the copyright, and other intellectual property rights of whatever nature in the Software, including any modifications made thereto) are and shall remain the property of *Deveel*.
+
+*Deveel* reserves the right to grant licences to use the Software to third parties.
+
+### Termination
+
+This EULA agreement is effective from the date you first use the Software and shall continue until terminated. You may terminate it at any time upon written notice to *Deveel*.
+
+It will also terminate immediately if you fail to comply with any term of this EULA agreement. Upon such termination, the licenses granted by this EULA agreement will immediately terminate and you agree to stop all access and use of the Software. The provisions that by their nature continue and survive will survive any termination of this EULA agreement.
+
+### Governing Law
+
+This EULA agreement, and any dispute arising out of or in connection with this EULA agreement, shall be governed by and construed in accordance with the laws of **Norway**.
\ No newline at end of file
diff --git a/deveel-logo.png b/deveel-logo.png
new file mode 100644
index 0000000..274f522
Binary files /dev/null and b/deveel-logo.png differ
diff --git a/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj b/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj
new file mode 100644
index 0000000..ea4e21d
--- /dev/null
+++ b/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Deveel
+ 1.0.0
+ false
+ Antonello Provenzano
+ Deveel AS
+ (C) 2022 Deveel
+ https://github.com/deveel/deveel.data.repository
+ https://github.com/deveel/deveel.data.repository
+ git
+ data core framework repository
+ Abstractions for the definition of repositories of data
+ EULA.md
+ deveel-logo.png
+ true
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+
diff --git a/src/Deveel.Repository.Core/Repository/ExpressionFieldRef.cs b/src/Deveel.Repository.Core/Repository/ExpressionFieldRef.cs
new file mode 100644
index 0000000..7cb9017
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/ExpressionFieldRef.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Data {
+ ///
+ /// References a expr of an entity through a selection expression
+ ///
+ /// The type of the entity defining the expr to be selected
+ public sealed class ExpressionFieldRef : IFieldRef where TEntity : class, IEntity {
+ ///
+ /// Constucts the reference with the expression to select
+ /// the expr from the entity
+ ///
+ /// The expression that is used to select the expr
+ ///
+ /// Thrown if the expression is empty
+ ///
+ public ExpressionFieldRef(Expression> expr) {
+ Expression = expr ?? throw new ArgumentNullException(nameof(expr));
+ }
+
+ ///
+ /// Gets the expression used to select a field from the
+ /// underlying entity
+ ///
+ public Expression> Expression { get; }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/ExpressionQueryFilter.cs b/src/Deveel.Repository.Core/Repository/ExpressionQueryFilter.cs
new file mode 100644
index 0000000..a8f4be7
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/ExpressionQueryFilter.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Data {
+ ///
+ /// An implementation of a query expr that uses a lambda expression
+ ///
+ /// The type of entity to construct
+ /// the expr
+ public sealed class ExpressionQueryFilter : IQueryFilter where TEntity : class, IEntity {
+ public ExpressionQueryFilter(Expression> expr) {
+ Expression = expr ?? throw new ArgumentNullException(nameof(expr));
+ }
+
+ ///
+ /// Gets the filter expression
+ ///
+ public Expression> Expression { get; }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/FacadeRepository.cs b/src/Deveel.Repository.Core/Repository/FacadeRepository.cs
new file mode 100644
index 0000000..d74b00a
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/FacadeRepository.cs
@@ -0,0 +1,84 @@
+using System;
+
+using Deveel.Data;
+using Deveel.States;
+
+using static System.Formats.Asn1.AsnWriter;
+
+namespace Deveel.Repository {
+ class FacadeRepository : IRepository
+ where TEntity : class, IEntity, TFacade
+ where TFacade : class, IEntity {
+ private readonly IRepository repository;
+
+ public FacadeRepository(IRepository repository) {
+ this.repository = repository;
+ }
+
+ public bool SupportsPaging => repository.SupportsPaging;
+
+ public bool SupportsFilters => repository.SupportsFilters;
+
+ private TEntity Assert(TFacade entity) {
+ if (!(entity is TEntity other))
+ throw new ArgumentException($"The object of type '{typeof(TFacade)}' cannot be converted to '{typeof(TEntity)}'");
+
+ return other;
+ }
+
+ private TEntity Assert(object entity) {
+ if (!(entity is TEntity other))
+ throw new ArgumentException($"The object cannot be converted to '{typeof(TEntity)}'");
+
+ return other;
+ }
+
+ public Task CreateAsync(TFacade entity, CancellationToken cancellationToken)
+ => repository.CreateAsync(Assert(entity), cancellationToken);
+
+ public Task CreateAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => repository.CreateAsync(session, Assert(entity), cancellationToken);
+
+ public Task CreateAsync(IEntity entity, CancellationToken cancellationToken)
+ => repository.CreateAsync(Assert(entity), cancellationToken);
+
+ public Task CreateAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken)
+ => repository.CreateAsync(session, Assert(entity), cancellationToken);
+
+ public Task DeleteAsync(TFacade entity, CancellationToken cancellationToken)
+ => repository.DeleteAsync(Assert(entity), cancellationToken);
+
+ public Task DeleteAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => repository.DeleteAsync(session, Assert(entity), cancellationToken);
+
+ public Task DeleteAsync(IEntity entity, CancellationToken cancellationToken)
+ => repository.DeleteAsync(Assert(entity), cancellationToken);
+
+ public Task DeleteAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken)
+ => repository.DeleteAsync(session, Assert(entity), cancellationToken);
+
+ public async Task FindByIdAsync(string id, CancellationToken cancellationToken)
+ => await repository.FindByIdAsync(id, cancellationToken);
+
+ public Task UpdateAsync(TFacade entity, CancellationToken cancellationToken)
+ => repository.UpdateAsync(Assert(entity), cancellationToken);
+
+ public Task UpdateAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => repository.UpdateAsync(session, Assert(entity), cancellationToken);
+
+ public Task UpdateAsync(IEntity entity, CancellationToken cancellationToken)
+ => repository.UpdateAsync(Assert(entity), cancellationToken);
+
+ public Task UpdateAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken)
+ => repository.UpdateAsync(session, Assert(entity), cancellationToken);
+
+ async Task IRepository.FindByIdAsync(string id, CancellationToken cancellationToken)
+ => await repository.FindByIdAsync(id, cancellationToken);
+
+ async Task IRepository.GetPageAsync(PageRequest page, CancellationToken cancellationToken)
+ => await repository.GetPageAsync(page, cancellationToken);
+
+ public Task> GetPageAsync(PageRequest request, CancellationToken cancellationToken = default)
+ => throw new NotImplementedException();
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/FacadeRepositoryProvider.cs b/src/Deveel.Repository.Core/Repository/FacadeRepositoryProvider.cs
new file mode 100644
index 0000000..066fe7b
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/FacadeRepositoryProvider.cs
@@ -0,0 +1,19 @@
+using System;
+
+using Deveel.Data;
+
+namespace Deveel.Repository {
+ class FacadeRepositoryProvider : IRepositoryProvider
+ where TEntity : class, TFacade, IEntity
+ where TFacade : class, IEntity {
+ private readonly IRepositoryProvider provider;
+
+ public FacadeRepositoryProvider(IRepositoryProvider provider) {
+ this.provider = provider;
+ }
+
+ public IRepository GetRepository(string tenantId) => (IRepository) provider.GetRepository(tenantId);
+
+ IRepository IRepositoryProvider.GetRepository(string tenantId) => provider.GetRepository(tenantId);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IConvertibleQueryFilter.cs b/src/Deveel.Repository.Core/Repository/IConvertibleQueryFilter.cs
new file mode 100644
index 0000000..a160c0e
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IConvertibleQueryFilter.cs
@@ -0,0 +1,9 @@
+using System;
+
+using Deveel.Data;
+
+namespace Deveel.Repository {
+ public interface IConvertibleQueryFilter : IQueryFilter {
+ IQueryFilter ConvertFor();
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IDataTransaction.cs b/src/Deveel.Repository.Core/Repository/IDataTransaction.cs
new file mode 100644
index 0000000..9bcbdfb
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IDataTransaction.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// Represents a transaction provided by the underlying
+ /// storage of the repository, to isolate operations
+ /// of access to the data
+ ///
+ public interface IDataTransaction : IDisposable {
+ Task CommitAsync(CancellationToken cancellationToken = default);
+
+ Task RollbackAsync(CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IDataTransactionFactory.cs b/src/Deveel.Repository.Core/Repository/IDataTransactionFactory.cs
new file mode 100644
index 0000000..dee869f
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IDataTransactionFactory.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// A factory that provides instances of transactions
+ /// used to isolate the access to data layers of an underlying storage
+ ///
+ public interface IDataTransactionFactory {
+ ///
+ /// Creates a new stransaction and starts it
+ ///
+ ///
+ ///
+ /// Returns a new instance of that
+ /// can be used in a to isolate
+ /// the access to the data of an underlying storage
+ ///
+ Task CreateTransactionAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IFieldRef.cs b/src/Deveel.Repository.Core/Repository/IFieldRef.cs
new file mode 100644
index 0000000..0a79cd3
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IFieldRef.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// A marker interface that is implemented by objects referencing
+ /// a field of an entity
+ ///
+ public interface IFieldRef {
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IQueryFilter.cs b/src/Deveel.Repository.Core/Repository/IQueryFilter.cs
new file mode 100644
index 0000000..b33f543
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IQueryFilter.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// A marker interface that is implemented by objects
+ /// representing filters of a query to a repository
+ ///
+ public interface IQueryFilter {
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IQueryableRepository.cs b/src/Deveel.Repository.Core/Repository/IQueryableRepository.cs
new file mode 100644
index 0000000..ead3b7f
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IQueryableRepository.cs
@@ -0,0 +1,9 @@
+using System;
+
+using Deveel.Data;
+
+namespace Deveel.Repository {
+ public interface IQueryableRepository : IRepository where TEntity : class, IEntity {
+ IQueryable AsQueryable();
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IRepository.cs b/src/Deveel.Repository.Core/Repository/IRepository.cs
new file mode 100644
index 0000000..5c0440a
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IRepository.cs
@@ -0,0 +1,178 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// The contract defining a repository of entities, accessible
+ /// for read and write operations
+ ///
+ public interface IRepository {
+ ///
+ /// Gets a flag indicating whether the implementation of the
+ /// repository supports paging queries
+ ///
+ ///
+ bool SupportsPaging { get; }
+
+ ///
+ /// Gets a flag indicating whether the implementation of the
+ /// repository supports filtering
+ ///
+ bool SupportsFilters { get; }
+
+ ///
+ /// Creates a new entity in the repository
+ ///
+ /// The entity to create
+ ///
+ ///
+ /// Returns the unique identifier of the entity created.
+ ///
+ ///
+ /// Thrown if it an error occurred while creating the entity
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ Task CreateAsync(IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Creates a new entity in the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity to create
+ ///
+ ///
+ /// Returns the unique identifier of the entity created.
+ ///
+ ///
+ /// Thrown if it an error occurred while creating the entity
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task CreateAsync(IDataTransaction transaction, IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deletes an entity from the repository
+ ///
+ /// The entity to be deleted
+ ///
+ ///
+ /// Returns true if the entity was successfully removed
+ /// from the repository, otherwise false.
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while deleting the entity
+ ///
+ Task DeleteAsync(IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deletes an entity from the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity to be deleted
+ ///
+ ///
+ /// Returns true if the entity was successfully removed
+ /// from the repository, otherwise false.
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while deleting the entity
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task DeleteAsync(IDataTransaction transaction, IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Updates an existing entity in the repository
+ ///
+ /// The entity instance to be updated
+ ///
+ ///
+ /// Returns true if the entity was found and updated in
+ /// the repository, otherwise false
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while updating the entity
+ ///
+ Task UpdateAsync(IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Updates an existing entity in the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity instance to be updated
+ ///
+ ///
+ /// Returns true if the entity was found and updated in
+ /// the repository, otherwise false
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while updating the entity
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task UpdateAsync(IDataTransaction transaction, IEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Attempts to find in the repository an entity with the
+ /// given unique identifier
+ ///
+ /// The unique identifier of the entity to find
+ ///
+ ///
+ /// Returns the instance of the entity associated to the given ,
+ /// or null if none entity was found.
+ ///
+ Task FindByIdAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a page of items from the repository
+ ///
+ /// The request to obtain a given page from the repository. This
+ /// object provides the number of the page, the size of the items to return, filters and
+ /// sorting order.
+ ///
+ ///
+ /// Returns an instance of that provides the
+ /// page items and a count of total items.
+ ///
+ ///
+ /// Thrown if an error occurred while retrieving the page
+ ///
+ ///
+ /// Thrown if the filters or the sorting capabilities are not provided by the
+ /// implementation of the repository
+ ///
+ ///
+ ///
+ ///
+ Task GetPageAsync(PageRequest request, CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/src/Deveel.Repository.Core/Repository/IRepositoryProvider.cs b/src/Deveel.Repository.Core/Repository/IRepositoryProvider.cs
new file mode 100644
index 0000000..2e35f6d
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IRepositoryProvider.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// Represents an provider of repositories that
+ /// are isolating the entities of a given tenant
+ ///
+ public interface IRepositoryProvider {
+ ///
+ /// Gets an instance of a repository of entities
+ /// that is isolating the scope for a given tenant
+ ///
+ /// The identifier of the tenant that
+ /// owns the repository
+ ///
+ /// The provider does not validate the format of the ,
+ /// that is used only to identify the tenant.
+ ///
+ ///
+ /// Returns an instance of that
+ /// isolates the entities for a given tenant.
+ ///
+ ///
+ /// Thrown if the given is null
+ /// or an empty string.
+ ///
+ IRepository GetRepository(string tenantId);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IRepositoryProvider_T.cs b/src/Deveel.Repository.Core/Repository/IRepositoryProvider_T.cs
new file mode 100644
index 0000000..d619550
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IRepositoryProvider_T.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// Represents an provider of strongly-typed repositories that
+ /// are isolating the entities of a given tenant
+ ///
+ /// The type of entity handled by the
+ /// repository instances
+ public interface IRepositoryProvider : IRepositoryProvider where TEntity : class, IEntity {
+ ///
+ new IRepository GetRepository(string tenantId);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IRepository_T.cs b/src/Deveel.Repository.Core/Repository/IRepository_T.cs
new file mode 100644
index 0000000..aaafd8e
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IRepository_T.cs
@@ -0,0 +1,166 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// The contract defining a repository of entities, accessible
+ /// for read and write operations
+ ///
+ /// The type of entity handled by the repository
+ public interface IRepository : IRepository where TEntity : class, IEntity {
+ ///
+ /// Creates a new entity in the repository
+ ///
+ /// The entity to create
+ ///
+ ///
+ /// Returns the unique identifier of the entity created.
+ ///
+ ///
+ /// Thrown if it an error occurred while creating the entity
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Creates a new entity in the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity to create
+ ///
+ ///
+ /// Returns the unique identifier of the entity created.
+ ///
+ ///
+ /// Thrown if it an error occurred while creating the entity
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task CreateAsync(IDataTransaction transaction, TEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Updates an existing entity in the repository
+ ///
+ /// The entity instance to be updated
+ ///
+ ///
+ /// Returns true if the entity was found and updated in
+ /// the repository, otherwise false
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while updating the entity
+ ///
+ Task UpdateAsync(TEntity entity, CancellationToken cancellationToken= default);
+
+ ///
+ /// Updates an existing entity in the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity instance to be updated
+ ///
+ ///
+ /// Returns true if the entity was found and updated in
+ /// the repository, otherwise false
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while updating the entity
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task UpdateAsync(IDataTransaction transaction, TEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deletes an entity from the repository
+ ///
+ /// The entity to be deleted
+ ///
+ ///
+ /// Returns true if the entity was successfully removed
+ /// from the repository, otherwise false.
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while deleting the entity
+ ///
+ Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deletes an entity from the repository
+ ///
+ /// A transaction that isolates the access
+ /// to the data store used by the repository
+ /// The entity to be deleted
+ ///
+ ///
+ /// Returns true if the entity was successfully removed
+ /// from the repository, otherwise false.
+ ///
+ ///
+ /// Thrown if the provided is null
+ ///
+ ///
+ /// Thrown if it an error occurred while deleting the entity
+ ///
+ ///
+ /// Thrown if the provided is not compatible
+ /// with the underlying storage of the repository
+ ///
+ ///
+ Task DeleteAsync(IDataTransaction transaction, TEntity entity, CancellationToken cancellationToken = default);
+
+ ///
+ /// Attempts to find in the repository an entity with the
+ /// given unique identifier
+ ///
+ /// The unique identifier of the entity to find
+ ///
+ ///
+ /// Returns the instance of the entity associated to the given ,
+ /// or null if none entity was found.
+ ///
+ new Task FindByIdAsync(string id, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a page of items from the repository
+ ///
+ /// The request to obtain a given page from the repository. This
+ /// object provides the number of the page, the size of the items to return, filters and
+ /// sorting order.
+ ///
+ ///
+ /// Returns an instance of that provides the
+ /// page items and a count of total items.
+ ///
+ ///
+ /// Thrown if an error occurred while retrieving the page
+ ///
+ ///
+ /// Thrown if the filters or the sorting capabilities are not provided by the
+ /// implementation of the repository
+ ///
+ ///
+ ///
+ ///
+ Task> GetPageAsync(PageRequest request, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/IResultSort.cs b/src/Deveel.Repository.Core/Repository/IResultSort.cs
new file mode 100644
index 0000000..ada9152
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/IResultSort.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// Describes a sorting rule for the results of a query
+ ///
+ public interface IResultSort {
+ ///
+ /// Gets a reference to the field used to sort
+ /// the results
+ ///
+ IFieldRef Field { get; }
+
+ ///
+ /// Gets a flag indicating whether the result
+ /// of the query should be sorted ascending, given
+ /// the value of the field
+ ///
+ bool Ascending { get; }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/LambdaExpressionExtensions.cs b/src/Deveel.Repository.Core/Repository/LambdaExpressionExtensions.cs
new file mode 100644
index 0000000..b6728e0
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/LambdaExpressionExtensions.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Repository {
+ static class LambdaExpressionExtensions {
+ public static Expression> Cast(this LambdaExpression expression) {
+ var parameter = Expression.Parameter(typeof(TFacade), "f");
+ return Expression.Lambda>(expression, parameter);
+ }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/PageRequest.cs b/src/Deveel.Repository.Core/Repository/PageRequest.cs
new file mode 100644
index 0000000..082c9ec
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/PageRequest.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// Describes the request to obtain a page of a repository of entities,
+ /// given a set of filters and a sort order of the results
+ ///
+ ///
+ /// The overall number of pages available in a repository
+ /// is given by a formula that divides the total number
+ /// of items by the size of the page requested.
+ ///
+ public class PageRequest {
+ ///
+ /// Constructs the request with the given page number and size
+ ///
+ /// The number of the page to request
+ /// The maximum number of items to be returned in the page
+ ///
+ /// If either the page number or the page size are smaller than 1.
+ ///
+ public PageRequest(int page, int size) {
+ if (page < 1)
+ throw new ArgumentOutOfRangeException(nameof(page), "The page must be at least the first");
+ if (size < 1)
+ throw new ArgumentOutOfRangeException(nameof(size), "The size of a page must be of at least one item");
+
+ Page = page;
+ Size = size;
+ }
+
+ ///
+ /// Gets the number of the page to return
+ ///
+ public int Page { get; }
+
+ ///
+ /// Gets the maximum number of items to be returned.
+ ///
+ public int Size { get; }
+
+ ///
+ /// Gets the starting offet in the repository where to start
+ /// collecting the items to return
+ ///
+ public int Offset => (Page - 1) * Size;
+
+ ///
+ /// Gets or sets a filter to restrict the context of the query
+ ///
+ public IQueryFilter? Filter { get; set; }
+
+ ///
+ /// Gets or sets an optional set of orders to sort the
+ /// result of the request
+ ///
+ public IEnumerable? SortBy { get; set; }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/PageRequest_T.cs b/src/Deveel.Repository.Core/Repository/PageRequest_T.cs
new file mode 100644
index 0000000..2839b82
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/PageRequest_T.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection.Metadata;
+
+using Deveel.Repository;
+
+namespace Deveel.Data {
+ ///
+ /// Describes the request to obtain a page of a given size
+ /// from a repository
+ ///
+ ///
+ ///
+ public class PageRequest : PageRequest where TEntity : class, IEntity {
+ public PageRequest(int page, int size)
+ : base(page, size) {
+ }
+
+ ///
+ /// Gets or sets a filter expression that restricts the
+ /// context of the page request
+ ///
+ public new Expression>? Filter {
+ get => (base.Filter as ExpressionQueryFilter)?.Expression;
+ set => base.Filter = value == null ? null : new ExpressionQueryFilter(value);
+ }
+
+ ///
+ /// Sets or appends a new filter
+ ///
+ /// The filter expression to add
+ ///
+ /// Returns this page request with the new filter
+ ///
+ ///
+ /// Thrown if the is null.
+ ///
+ public PageRequest Where(Expression> expression) {
+ if (expression is null)
+ throw new ArgumentNullException(nameof(expression));
+
+ var expr = Filter;
+ if (expr == null) {
+ expr = expression;
+ } else {
+ var body = Expression.AndAlso(expr.Body, expression.Body);
+ expr = Expression.Lambda>(body, expr.Parameters[0]);
+ }
+
+ Filter = expr;
+
+ return this;
+ }
+
+ public PageRequest Cast() where TFacade : class, IEntity {
+ var page = new PageRequest(Page, Size);
+
+ if (Filter != null) {
+ page.Filter = Filter.Cast();
+ }
+
+ return page;
+ }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/PaginatedResult.cs b/src/Deveel.Repository.Core/Repository/PaginatedResult.cs
new file mode 100644
index 0000000..25bd24b
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/PaginatedResult.cs
@@ -0,0 +1,65 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// The result of the request to obtain a page
+ /// of entities from a repository
+ ///
+ ///
+ public class PaginatedResult {
+ ///
+ /// Constructs the result referencing the original request, a count
+ /// of the items in the repository and optionally a list of items in the page
+ ///
+ /// The original page request
+ /// The total number of items in the context
+ /// of the request given (filtered and sorted).
+ /// The list of items included in the page
+ ///
+ /// Thrown if the number of total items is smaller than zero.
+ ///
+ ///
+ /// Thrown if the is null.
+ ///
+ public PaginatedResult(PageRequest request, int totalItems, IEnumerable? items = null) {
+ if (totalItems < 0)
+ throw new ArgumentOutOfRangeException(nameof(totalItems), "The number of total items must be zero or more");
+
+ Request = request ?? throw new ArgumentNullException(nameof(request));
+ TotalItems = totalItems;
+ Items = items;
+ }
+
+ ///
+ /// Gets a reference to the request
+ ///
+ public PageRequest Request { get; }
+
+ ///
+ /// Gets a count of the total items in the repository
+ /// for the context of the request
+ ///
+ public int TotalItems { get; }
+
+ ///
+ /// Gets a list of items included in the page
+ ///
+ public IEnumerable? Items { get; set; }
+
+ ///
+ /// Gets a count of the total available pages
+ /// that can be requested from the repository
+ ///
+ public int TotalPages => (int)Math.Ceiling((double)TotalItems / Request.Size);
+
+ ///
+ /// Creates an empty page result
+ ///
+ /// The original page request
+ ///
+ /// Returns an instance of that
+ /// contains no results and no pages.
+ ///
+ public static PaginatedResult Empty(PageRequest request) => new PaginatedResult(request, 0);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/PaginatedResult_T.cs b/src/Deveel.Repository.Core/Repository/PaginatedResult_T.cs
new file mode 100644
index 0000000..9bcf382
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/PaginatedResult_T.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// The strongly typed page from a repository, obtained from a query
+ ///
+ ///
+ ///
+ ///
+ public class PaginatedResult : PaginatedResult where TEntity : class, IEntity {
+ ///
+ public PaginatedResult(PageRequest request, int totalItems, IEnumerable? items = null)
+ : base(request, totalItems, items) {
+ }
+
+ public new PageRequest Request {
+ get => (PageRequest)base.Request;
+ }
+
+ ///
+ public new IEnumerable? Items {
+ get => base.Items?.Cast();
+ set => base.Items = value;
+ }
+
+ public PaginatedResult CastTo() where TOther : class, IEntity {
+ var request = Request.Cast();
+ var items = Items?.Cast();
+
+ return new PaginatedResult(request, TotalItems, items);
+ }
+
+ ///
+ /// Creates an empty page result
+ ///
+ /// The original page request
+ ///
+ /// Returns an instance of that
+ /// contains no results and no pages.
+ ///
+ public static PaginatedResult Empty(PageRequest request)
+ => new PaginatedResult(request, 0);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/RepositoryException.cs b/src/Deveel.Repository.Core/Repository/RepositoryException.cs
new file mode 100644
index 0000000..4191603
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/RepositoryException.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Deveel.Data {
+ ///
+ /// An exception that is thrown during the execution
+ /// of an operation on the repository
+ ///
+ public class RepositoryException : Exception {
+ public RepositoryException() {
+ }
+
+ public RepositoryException(string? message) : base(message) {
+ }
+
+ public RepositoryException(string? message, Exception? innerException) : base(message, innerException) {
+ }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/ResultSort.cs b/src/Deveel.Repository.Core/Repository/ResultSort.cs
new file mode 100644
index 0000000..067a4df
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/ResultSort.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Data {
+ ///
+ /// Provides a default implementation of the result sort
+ ///
+ ///
+ public class ResultSort : IResultSort {
+ public ResultSort(IFieldRef field, bool ascending = false) {
+ Field = field ?? throw new ArgumentNullException(nameof(field));
+ Ascending = ascending;
+ }
+
+ ///
+ public IFieldRef Field { get; }
+
+ ///
+ public bool Ascending { get; }
+
+ public static ResultSort Create(string fieldName, bool ascending = false)
+ => new ResultSort(new StringFieldRef(fieldName), ascending);
+
+ public static ResultSort Create(Expression> fieldSelector, bool ascending = false)
+ where TEntity : class, IEntity
+ => new ResultSort(new ExpressionFieldRef(fieldSelector), ascending);
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/ServiceCollectionExtensions.cs b/src/Deveel.Repository.Core/Repository/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..b31431e
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/ServiceCollectionExtensions.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Deveel.Data;
+using Deveel.States;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Deveel.Repository {
+ public static class ServiceCollectionExtensions {
+ public static IServiceCollection AddStore(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped)
+ where TStore : class, IRepository {
+ services.TryAdd(new ServiceDescriptor(typeof(IRepository), typeof(TStore), lifetime));
+ services.Add(new ServiceDescriptor(typeof(TStore), typeof(TStore), lifetime));
+
+ return services;
+ }
+
+ public static IServiceCollection AddStore(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped)
+ where TStore : class, IRepository
+ where TEntity : class, IEntity {
+ services.TryAdd(new ServiceDescriptor(typeof(IRepository), typeof(TStore), lifetime));
+ services.TryAdd(new ServiceDescriptor(typeof(IRepository), typeof(TStore), lifetime));
+ services.Add(new ServiceDescriptor(typeof(TStore), typeof(TStore), lifetime));
+
+ return services;
+ }
+
+ public static IServiceCollection AddStoreFacade(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped)
+ where TEntity : class, IEntity, TInterface
+ where TInterface : class, IEntity {
+ services.TryAdd(new ServiceDescriptor(typeof(IRepository), typeof(FacadeRepository), lifetime));
+
+ return services;
+ }
+
+ public static IServiceCollection AddStoreProvider(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
+ where TProvider : class, IRepositoryProvider {
+
+ services.TryAdd(new ServiceDescriptor(typeof(IRepositoryProvider), typeof(TProvider), lifetime));
+ services.Add(new ServiceDescriptor(typeof(TProvider), typeof(TProvider), lifetime));
+
+ return services;
+ }
+
+ public static IServiceCollection AddStoreProvider(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
+ where TProvider : class, IRepositoryProvider
+ where TEntity : class, IEntity {
+ services.TryAdd(new ServiceDescriptor(typeof(IRepositoryProvider), typeof(TProvider), lifetime));
+ services.TryAdd(new ServiceDescriptor(typeof(IRepositoryProvider), typeof(TProvider), lifetime));
+
+ services.Add(new ServiceDescriptor(typeof(TProvider), typeof(TProvider), lifetime));
+
+ return services;
+ }
+
+ public static IServiceCollection AddStoreProviderFacade(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
+ where TEntity : class, TInterface
+ where TInterface : class, IEntity {
+ services.TryAdd(new ServiceDescriptor(typeof(IRepositoryProvider), typeof(FacadeRepositoryProvider), lifetime));
+ services.TryAdd(new ServiceDescriptor(typeof(IRepositoryProvider), typeof(FacadeRepositoryProvider), lifetime));
+
+ return services;
+ }
+ }
+}
diff --git a/src/Deveel.Repository.Core/Repository/StringFieldRef.cs b/src/Deveel.Repository.Core/Repository/StringFieldRef.cs
new file mode 100644
index 0000000..dca8e97
--- /dev/null
+++ b/src/Deveel.Repository.Core/Repository/StringFieldRef.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Deveel.Data {
+ ///
+ /// References a field of an entity by its name
+ ///
+ public sealed class StringFieldRef : IFieldRef {
+ ///
+ /// Constructs the reference with the name of the field
+ ///
+ /// The name of the field
+ ///
+ /// Thrown if the field is null or empty.
+ ///
+ public StringFieldRef(string fieldName) {
+ if (string.IsNullOrWhiteSpace(fieldName))
+ throw new ArgumentException($"'{nameof(fieldName)}' cannot be null or whitespace.", nameof(fieldName));
+
+ FieldName = fieldName;
+ }
+
+ ///
+ /// Gets the name of the field referenced
+ ///
+ public string FieldName { get; }
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Deveel.Repository.MongoDb.csproj b/src/Deveel.Repository.MongoDb/Deveel.Repository.MongoDb.csproj
new file mode 100644
index 0000000..a19c4ae
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Deveel.Repository.MongoDb.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Deveel
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Deveel.Repository.MongoDb/Repository/DelegatedDocumentFieldMapper.cs b/src/Deveel.Repository.MongoDb/Repository/DelegatedDocumentFieldMapper.cs
new file mode 100644
index 0000000..a1f7acf
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/DelegatedDocumentFieldMapper.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Data {
+ public sealed class DelegatedDocumentFieldMapper : IDocumentFieldMapper where TDocument : class {
+ private readonly Func mapper;
+
+ public DelegatedDocumentFieldMapper(Func mapper) {
+ this.mapper = mapper;
+ }
+
+ public string MapField(string fieldName) => mapper(fieldName);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/IDocumentFieldMapper.cs b/src/Deveel.Repository.MongoDb/Repository/IDocumentFieldMapper.cs
new file mode 100644
index 0000000..30d9023
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/IDocumentFieldMapper.cs
@@ -0,0 +1,8 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Deveel.Data {
+ public interface IDocumentFieldMapper where TDocument : class {
+ string MapField(string fieldName);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoDbRepository_Facade.cs b/src/Deveel.Repository.MongoDb/Repository/MongoDbRepository_Facade.cs
new file mode 100644
index 0000000..e775cde
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoDbRepository_Facade.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Threading;
+
+using Microsoft.Extensions.Options;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+
+namespace Deveel.Data {
+ public class MongoRepository : MongoRepository, IRepository
+ where TEntity : class, TFacade, IEntity
+ where TFacade : class, IEntity {
+ public MongoRepository(IOptions> options, IDocumentFieldMapper fieldMapper = null, ILogger> logger = null)
+ : base(options, fieldMapper, logger) {
+ }
+
+ protected static TEntity Assert(TFacade obj) {
+ if (!(obj is TEntity entity))
+ throw new ArgumentException($"Cannot cast object of type '{typeof(TFacade)}' to '{typeof(TEntity)}' entity type");
+
+ return entity;
+ }
+
+ Task IRepository.CreateAsync(TFacade entity, CancellationToken cancellationToken)
+ => CreateAsync(Assert(entity), cancellationToken);
+
+ Task IRepository.CreateAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => CreateAsync(AssertMongoDbSession(session), Assert(entity), cancellationToken);
+
+ Task IRepository.DeleteAsync(TFacade entity, CancellationToken cancellationToken)
+ => DeleteAsync(Assert(entity), cancellationToken);
+
+ Task IRepository.DeleteAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => DeleteAsync(AssertMongoDbSession(session), Assert(entity), cancellationToken);
+
+ async Task IRepository.FindByIdAsync(string id, CancellationToken cancellationToken)
+ => await FindByIdAsync(id, cancellationToken);
+
+ async Task> IRepository.GetPageAsync(PageRequest page, CancellationToken cancellationToken) {
+ var newPage = new PageRequest(page.Page, page.Size);
+ var result = await GetPageAsync(newPage, cancellationToken);
+
+ if (result == null)
+ return PaginatedResult.Empty(page);
+
+ return result.CastTo();
+ }
+
+ Task IRepository.UpdateAsync(TFacade entity, CancellationToken cancellationToken)
+ => UpdateAsync(Assert(entity), cancellationToken);
+
+ Task IRepository.UpdateAsync(IDataTransaction session, TFacade entity, CancellationToken cancellationToken)
+ => UpdateAsync(AssertMongoDbSession(session), Assert(entity), cancellationToken);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoDbSession.cs b/src/Deveel.Repository.MongoDb/Repository/MongoDbSession.cs
new file mode 100644
index 0000000..9502b43
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoDbSession.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using MongoDB.Driver;
+
+namespace Deveel.Data {
+ class MongoDbSession : IDataTransaction {
+ public MongoDbSession(IClientSessionHandle sessionHandle) {
+ this.SessionHandle = sessionHandle;
+ }
+
+ public IClientSessionHandle SessionHandle { get; }
+
+ public Task BeginAsync(CancellationToken cancellationToken) {
+ SessionHandle.StartTransaction();
+ return Task.CompletedTask;
+ }
+
+ public Task CommitAsync(CancellationToken cancellationToken) => SessionHandle.CommitTransactionAsync(cancellationToken);
+
+ public void Dispose() => SessionHandle?.Dispose();
+
+ public Task RollbackAsync(CancellationToken cancellationToken) => SessionHandle.AbortTransactionAsync(cancellationToken);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoDbSessionManager.cs b/src/Deveel.Repository.MongoDb/Repository/MongoDbSessionManager.cs
new file mode 100644
index 0000000..ac337a1
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoDbSessionManager.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using MongoDB.Driver;
+
+namespace Deveel.Data {
+ class MongoDbSessionManager : IDataTransactionFactory {
+ private readonly MongoSessionProvider sessionProvider;
+
+ public MongoDbSessionManager(MongoSessionProvider sessionProvider) {
+ this.sessionProvider = sessionProvider;
+ }
+
+ public async Task CreateTransactionAsync(CancellationToken cancellationToken) {
+ var session = await sessionProvider.StartSessionAsync(new ClientSessionOptions(), cancellationToken);
+ return new MongoDbSession(session);
+ }
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoQueryFilter.cs b/src/Deveel.Repository.MongoDb/Repository/MongoQueryFilter.cs
new file mode 100644
index 0000000..40c5ba6
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoQueryFilter.cs
@@ -0,0 +1,15 @@
+using System;
+
+using Deveel.Data;
+
+using MongoDB.Driver;
+
+namespace Deveel.Repository {
+ public sealed class MongoQueryFilter : IQueryFilter where TDocument : class, IEntity {
+ public MongoQueryFilter(FilterDefinition filter) {
+ Filter = filter ?? throw new ArgumentNullException(nameof(filter));
+ }
+
+ public FilterDefinition Filter { get; }
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoRepository.cs b/src/Deveel.Repository.MongoDb/Repository/MongoRepository.cs
new file mode 100644
index 0000000..732288b
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoRepository.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Deveel.Repository;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+using MongoDB.Driver;
+
+namespace Deveel.Data {
+ public class MongoRepository : MongoStore, IRepository, IQueryableRepository
+ where TDocument : class, IEntity {
+ private bool disposed;
+
+ public MongoRepository(IOptions> options, IDocumentFieldMapper? fieldMapper = null, ILogger> logger = null) : base(options, logger) {
+ FieldMapper = fieldMapper;
+ }
+
+ protected IDocumentFieldMapper? FieldMapper { get; private set; }
+
+ bool IRepository.SupportsPaging => true;
+
+ bool IRepository.SupportsFilters => true;
+
+ protected void ThrowIfDisposed() {
+ if (disposed)
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+
+ public void Dispose() {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing) {
+ disposed = true;
+ }
+
+ public void SetMapper(IDocumentFieldMapper mapper) {
+ FieldMapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+ public void SetMapper(Func mapper)
+ => SetMapper(new DelegatedDocumentFieldMapper(mapper));
+
+ IQueryable IQueryableRepository.AsQueryable() => AsQueryable();
+
+ internal IClientSessionHandle AssertMongoDbSession(IDataTransaction dataSession) {
+ if (dataSession is MongoDbSession session)
+ return session.SessionHandle;
+
+ throw new ArgumentException("The session type is invalid in this context");
+ }
+
+ private TDocument AssertIsEntity(object obj) {
+ if (!(obj is TDocument entity))
+ throw new ArgumentException($"The object provided is not of type {typeof(TDocument)}");
+
+ return entity;
+ }
+
+
+ ///
+ Task IRepository.CreateAsync(IDataTransaction session, TDocument entity, CancellationToken cancellationToken) {
+ return CreateAsync(AssertMongoDbSession(session), entity, cancellationToken);
+ }
+
+ ///
+ Task IRepository.CreateAsync(IEntity entity, CancellationToken cancellationToken) {
+ return CreateAsync(AssertIsEntity(entity), cancellationToken);
+ }
+
+ ///
+ Task IRepository.CreateAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken) {
+ return CreateAsync(AssertMongoDbSession(session), AssertIsEntity(entity), cancellationToken);
+ }
+
+
+ ///
+ async Task IRepository.FindByIdAsync(string id, CancellationToken cancellationToken) {
+ return await FindByIdAsync(id, cancellationToken);
+ }
+
+
+ ///
+ Task IRepository.UpdateAsync(IDataTransaction session, TDocument entity, CancellationToken cancellationToken) {
+ return UpdateAsync(AssertMongoDbSession(session), entity, cancellationToken);
+ }
+
+ ///
+ Task IRepository.DeleteAsync(IDataTransaction session, TDocument entity, CancellationToken cancellationToken) {
+ return DeleteAsync(AssertMongoDbSession(session), entity, cancellationToken);
+ }
+
+
+ ///
+ Task IRepository.DeleteAsync(IEntity entity, CancellationToken cancellationToken) {
+ return DeleteAsync(AssertIsEntity(entity), cancellationToken);
+ }
+
+ ///
+ Task IRepository.DeleteAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken) {
+ return DeleteAsync(AssertMongoDbSession(session), AssertIsEntity(entity), cancellationToken);
+ }
+
+ ///
+ Task IRepository.UpdateAsync(IEntity entity, CancellationToken cancellationToken) {
+ return UpdateAsync(AssertIsEntity(entity), cancellationToken);
+ }
+
+ ///
+ Task IRepository.UpdateAsync(IDataTransaction session, IEntity entity, CancellationToken cancellationToken) {
+ return UpdateAsync(AssertMongoDbSession(session), AssertIsEntity(entity), cancellationToken);
+ }
+
+ ///
+ async Task IRepository.GetPageAsync(PageRequest page, CancellationToken cancellationToken) {
+ throw new NotImplementedException();
+ }
+
+ public virtual async Task> GetPageAsync(PageRequest page, CancellationToken cancellationToken) {
+ var pageQuery = page.AsPageQuery(Field);
+ var result = await GetPageAsync(pageQuery, cancellationToken);
+
+ return new PaginatedResult(page, result.TotalItems, result.Items);
+ }
+
+ protected override FieldDefinition Field(string fieldName) {
+ if (FieldMapper != null) {
+ fieldName = FieldMapper.MapField(fieldName);
+ }
+
+ return new StringFieldDefinition(fieldName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider.cs b/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider.cs
new file mode 100644
index 0000000..bab7e29
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Xml.Linq;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Deveel.Data {
+ public class MongoRepositoryProvider : MongoStoreProvider, IRepositoryProvider where TEntity : class, IEntity {
+ public MongoRepositoryProvider(
+ IOptions baseOptions,
+ IDocumentFieldMapper? fieldMapper = null,
+ ITenantConnectionProvider? connectionProvider = null,
+ ICollectionKeyProvider? collectionNameProvider = null,
+ ILoggerFactory? loggerFactory = null)
+ : base(baseOptions, connectionProvider, collectionNameProvider, loggerFactory) {
+ FieldMapper = fieldMapper;
+ }
+
+ protected IDocumentFieldMapper? FieldMapper { get; }
+
+ ///
+ IRepository IRepositoryProvider.GetRepository(string tenantId) {
+ return (MongoRepository) GetStore(tenantId);
+ }
+
+ IRepository IRepositoryProvider.GetRepository(string tenantId) => (MongoRepository) GetStore(tenantId);
+
+ protected override MongoStore CreateStore(IOptions> options, ILogger> logger)
+ => new MongoRepository(options, FieldMapper, logger);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider_Facade.cs b/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider_Facade.cs
new file mode 100644
index 0000000..9832355
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/MongoRepositoryProvider_Facade.cs
@@ -0,0 +1,23 @@
+using System;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Deveel.Data {
+ public class MongoRepositoryProvider : MongoRepositoryProvider, IRepositoryProvider
+ where TEntity : class, TFacade, IEntity
+ where TFacade : class, IEntity {
+ public MongoRepositoryProvider(
+ IOptions baseOptions,
+ IDocumentFieldMapper? fieldMapper = null,
+ ITenantConnectionProvider? connectionProvider = null,
+ ICollectionKeyProvider? collectionNameProvider = null,
+ ILoggerFactory? loggerFactory = null) : base(baseOptions, fieldMapper, connectionProvider, collectionNameProvider, loggerFactory) {
+ }
+
+ IRepository IRepositoryProvider.GetRepository(string tenantId) => (IRepository) GetStore(tenantId);
+
+ protected override MongoStore CreateStore(IOptions> options, ILogger> logger)
+ => new MongoRepository(options, FieldMapper, logger);
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/PageRequestExtensions.cs b/src/Deveel.Repository.MongoDb/Repository/PageRequestExtensions.cs
new file mode 100644
index 0000000..cb8a573
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/PageRequestExtensions.cs
@@ -0,0 +1,60 @@
+using System;
+
+using Deveel.Repository;
+
+using MongoDB.Driver;
+
+namespace Deveel.Data {
+ static class PageRequestExtensions {
+ public static MongoPageQuery AsPageQuery(this PageRequest request, Func> fieldSelector = null)
+ where TDocument : class, IEntity {
+ var query = new MongoPageQuery(request.Page, request.Size);
+
+ if (request.Filter != null) {
+ var filter = Builders.Filter.Empty;
+
+ if (request.Filter is ExpressionQueryFilter expr) {
+ filter = Builders.Filter.Where(expr.Expression);
+ } else if (request.Filter is MongoQueryFilter filterDef) {
+ filter = filterDef.Filter;
+ }
+
+ query.Filter = filter;
+ }
+
+ if (request.SortBy != null) {
+ SortDefinition? sortBy = null;
+
+ foreach (var s in request.SortBy) {
+ SortDefinition? sort = null;
+
+ if (s.Field is ExpressionFieldRef expr) {
+ sort = s.Ascending ?
+ Builders.Sort.Ascending(expr.Expression) :
+ Builders.Sort.Descending(expr.Expression);
+ } else if (s.Field is StringFieldRef stringRef) {
+ var field = fieldSelector(stringRef.FieldName);
+
+ sort = s.Ascending ?
+ Builders.Sort.Ascending(field) :
+ Builders.Sort.Descending(field);
+ } else {
+ throw new NotSupportedException();
+ }
+
+ if (sort != null) {
+ if (sortBy == null) {
+ sortBy = sort;
+ } else {
+ sortBy = Builders.Sort.Combine(sortBy, sort);
+ }
+ }
+ }
+
+ query.SortBy = sortBy;
+ }
+
+ return query;
+ }
+ }
+}
diff --git a/src/Deveel.Repository.MongoDb/Repository/ServiceCollectionExtensions.cs b/src/Deveel.Repository.MongoDb/Repository/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..c714b04
--- /dev/null
+++ b/src/Deveel.Repository.MongoDb/Repository/ServiceCollectionExtensions.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Linq.Expressions;
+
+using Deveel.Repository;
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+using static System.Collections.Specialized.BitVector32;
+
+namespace Deveel.Data {
+ public static class ServiceCollectionExtensions {
+
+ #region AddMongoDbStore
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services)
+ where TEntity : class, IEntity
+ where TStore : MongoRepository
+ => services.AddStore();
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services)
+ where TEntity : class, IEntity
+ => services.AddMongoDbStore, TEntity>();
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services, Action configure)
+ where TEntity : class, IEntity
+ where TStore : MongoRepository
+ => services.AddMongoStoreOptions(configure).AddMongoDbStore();
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services, Action configure)
+ where TEntity : class, IEntity
+ => services.AddMongoDbStore, TEntity>(configure);
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services, string sectionName)
+ where TEntity : class, IEntity
+ where TStore : MongoRepository
+ => services.AddMongoStoreOptions(sectionName).AddMongoDbStore();
+
+ public static IServiceCollection AddMongoDbStore(this IServiceCollection services, string sectionName)
+ where TEntity : class, IEntity
+ => services.AddMongoDbStore, TEntity>(sectionName);
+
+
+ #endregion
+
+ #region AddMongoDbFacadeStore
+
+ public static IServiceCollection AddMongoDbFacadeStore(this IServiceCollection services)
+ where TEntity : class, TFacade, IEntity
+ where TFacade : class, IEntity
+ where TStore : MongoRepository
+ => services.AddStore().AddStore();
+
+ public static IServiceCollection AddMongoDbFacadeStore(this IServiceCollection services)
+ where TEntity : class, TFacade, IEntity
+ where TFacade : class, IEntity
+ => services.AddMongoDbFacadeStore, TEntity, TFacade>();
+
+
+ #endregion
+
+ #region AddMongoDbStoreProvider
+
+ public static IServiceCollection AddMongoDbStoreProvider(this IServiceCollection services)
+ where TEntity : class, IEntity
+ where TProvider : MongoRepositoryProvider
+ => services.AddStoreProvider();
+
+ public static IServiceCollection AddMongoDbStoreProvider(this IServiceCollection services)
+ where TEntity : class, IEntity
+ => services.AddMongoDbStoreProvider, TEntity>();
+
+ public static IServiceCollection AddMongoDbStoreProvider(this IServiceCollection services, string sectionName)
+ where TEntity : class, IEntity
+ where TProvider : MongoRepositoryProvider
+ => services.AddMongoStoreOptions(sectionName).AddMongoDbStoreProvider