diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..577b8eb
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,86 @@
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+
+[*.cs]
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = when_multiline:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_methods = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = true:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = true:silent
+csharp_indent_labels = one_less_than_current
+csharp_style_throw_expression = true:suggestion
+csharp_space_around_binary_operators = before_and_after
\ No newline at end of file
diff --git a/Deveel.Repository.sln b/Deveel.Repository.sln
index d692e2a..d0c2834 100644
--- a/Deveel.Repository.sln
+++ b/Deveel.Repository.sln
@@ -33,7 +33,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.EntityMod
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.DynamicLinq", "src\Deveel.Repository.DynamicLinq\Deveel.Repository.DynamicLinq.csproj", "{8B09EBB3-F8D5-483D-AFD6-FCCC5F186324}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.MongoFramework.TestApi", "test\Deveel.Repository.MongoFramework.TestApi\Deveel.Repository.MongoFramework.TestApi.csproj", "{9B43B5FA-4990-4A34-B7A3-4996D9E93504}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.MongoFramework.TestApi", "test\Deveel.Repository.MongoFramework.TestApi\Deveel.Repository.MongoFramework.TestApi.csproj", "{9B43B5FA-4990-4A34-B7A3-4996D9E93504}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{603E47A0-B1D9-4B4D-AEA7-2E9272CE5C87}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj b/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj
index 5680a71..309fde9 100644
--- a/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj
+++ b/src/Deveel.Repository.Core/Deveel.Repository.Core.csproj
@@ -42,8 +42,4 @@
-
-
-
-
diff --git a/src/Deveel.Repsotiory.MongoFramework/Data/EntityExtensions.cs b/src/Deveel.Repsotiory.MongoFramework/Data/EntityExtensions.cs
deleted file mode 100644
index 1ccc06d..0000000
--- a/src/Deveel.Repsotiory.MongoFramework/Data/EntityExtensions.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Deveel.Data {
- static class EntityExtensions {
- private static bool TryGetMemberValue(this object entity, string memberName, [MaybeNullWhen(false)] out TValue value) {
- var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
- var property = entity.GetType().GetProperty(memberName, binding);
- if (property == null || !property.CanRead) {
- value = default;
- return false;
- }
-
- var propValue = property.GetValue(entity, null);
- if (propValue == null) {
- value = default;
- return false;
- }
-
- if (propValue is TValue v) {
- value = v;
- return true;
- }
-
- value = (TValue) Convert.ChangeType(propValue, typeof(TValue), CultureInfo.InvariantCulture);
- return true;
- }
-
- public static bool TryGetId(this object entity, [MaybeNullWhen(false)] out string? value) {
- if (entity == null) {
- value = null;
- return false;
- }
-
- if (entity is IDataEntity dataEntity) {
- value = dataEntity.Id;
- return true;
- }
-
- if (TryGetMemberValue(entity, "Id", out var id)) {
- value = id;
- return true;
- }
-
- value = null;
- return false;
- }
- }
-}
diff --git a/src/Deveel.Repsotiory.MongoFramework/Data/MongoRepository.cs b/src/Deveel.Repsotiory.MongoFramework/Data/MongoRepository.cs
index 910e025..2e270eb 100644
--- a/src/Deveel.Repsotiory.MongoFramework/Data/MongoRepository.cs
+++ b/src/Deveel.Repsotiory.MongoFramework/Data/MongoRepository.cs
@@ -9,11 +9,21 @@
using MongoFramework;
using MongoFramework.Infrastructure;
-using MongoFramework.Infrastructure.Commands;
using MongoFramework.Infrastructure.Mapping;
using MongoFramework.Linq;
namespace Deveel.Data {
+ ///
+ /// An implementation of contract
+ /// that uses the MongoDB system to store and retrieve data.
+ ///
+ ///
+ /// The type of the that is used to
+ /// handling the connection to the MongoDB server.
+ ///
+ ///
+ /// The type of the entity that is stored in the repository.
+ ///
public class MongoRepository : IRepository,
IQueryableRepository,
IPageableRepository,
@@ -28,6 +38,15 @@ public class MongoRepository : IRepository,
private IMongoDbSet? _dbSet;
private bool disposed;
+ ///
+ /// Constructs the repository with the given context and logger.
+ ///
+ ///
+ /// The context that is used to handle the connection to the MongoDB server.
+ ///
+ ///
+ /// A logger instance that is used to log messages from the repository.
+ ///
protected internal MongoRepository(TContext context, ILogger? logger = null) {
Context = context;
Logger = logger ?? NullLogger.Instance;
@@ -36,17 +55,40 @@ protected internal MongoRepository(TContext context, ILogger? logger = null) {
TenantId = tenantContext.TenantId;
}
+ ///
+ /// Constructs the repository with the given context and logger.
+ ///
+ ///
+ /// The context that is used to handle the connection to the MongoDB server.
+ ///
+ ///
+ /// A logger instance that is used to log messages from the repository.
+ ///
public MongoRepository(TContext context, ILogger>? logger = null)
: this(context, (ILogger?)logger) {
}
-
+ ///
+ /// Gets the context that is used to handle the connection to the MongoDB server.
+ ///
protected TContext Context { get; }
+ ///
+ /// Gets the that is used to handle the
+ /// repository operations.
+ ///
protected IMongoDbSet DbSet => GetEntitySet();
+ ///
+ /// Gets the instance that is used to log messages
+ ///
protected ILogger Logger { get; }
+ ///
+ /// When the underlying context is a ,
+ /// this property returns the tenant identifier that is used to filter
+ /// the data in the repository.
+ ///
protected string? TenantId { get; }
string? IMultiTenantRepository.TenantId => TenantId;
@@ -55,6 +97,10 @@ public MongoRepository(TContext context, ILogger typeof(TEntity);
+ ///
+ /// Gets the instance that is used
+ /// to handle the data in the repository.
+ ///
protected IMongoCollection Collection {
get {
var entityDef = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
@@ -75,13 +121,25 @@ private static string RequireString(object value) {
return result;
}
+ ///
+ /// Throws an exception if the repository has been disposed.
+ ///
+ ///
+ /// Thrown when the repository has been disposed.
+ ///
protected void ThrowIfDisposed() {
if (disposed)
throw new ObjectDisposedException(GetType().Name);
}
+ ///
+ /// Constructs a new that is
+ /// coherent with the context and the entity type.
+ ///
+ ///
+ ///
protected virtual IMongoDbSet MakeEntitySet() {
- if (Context is MongoDbTenantContext tenantContext &&
+ if (Context is IMongoDbTenantContext tenantContext &&
typeof(IHaveTenantId).IsAssignableFrom(typeof(TEntity))) {
var dbSetType = typeof(MongoDbTenantSet<>).MakeGenericType(typeof(TEntity));
var result = (IMongoDbSet?)Activator.CreateInstance(dbSetType, new object[] { Context });
@@ -105,7 +163,8 @@ private IMongoDbSet GetEntitySet() {
string? IRepository.GetEntityId(object entity) => ((IRepository)this).GetEntityId(Assert(entity));
string? IRepository.GetEntityId(TEntity entity) {
- var value = GetIdValue(entity);
+ var value = GetEntityId(entity);
+
if (value == null)
return null;
@@ -117,10 +176,46 @@ private IMongoDbSet GetEntitySet() {
return Convert.ToString(value, CultureInfo.InvariantCulture);
}
- protected virtual object? GetIdValue(TEntity entity)
- => entity.TryGetId(out var id) ? GetIdValue(id) : null;
+ ///
+ /// Gets the value of the ID property of the given entity.
+ ///
+ ///
+ /// The entity whose ID property value is to be retrieved.
+ ///
+ ///
+ /// Returns the value of the ID property of the given entity.
+ ///
+ protected virtual object? GetEntityId(TEntity entity) {
+ var entityDef = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
+
+ var idProperty = entityDef.GetIdProperty();
+
+ if (idProperty == null)
+ throw new RepositoryException($"The type '{typeof(TEntity)}' has no ID property specified");
- protected virtual object? GetIdValue(string? id) {
+ return idProperty.PropertyInfo.GetValue(entity);
+ }
+
+ ///
+ /// Converts the given string value to the type of the ID property of the
+ /// entity managed by this repository.
+ ///
+ ///
+ /// The string representation of the ID value.
+ ///
+ ///
+ /// Returns the value converted accordingly to the type of the ID property
+ /// of the entity managed by this repository, or null if the given
+ /// string is null or empty.
+ ///
+ ///
+ /// Thrown if the entity managed by this repository has no ID property
+ ///
+ ///
+ /// Thrown when the value cannot be converted to the type of the ID
+ /// property of the entity managed by this repository.
+ ///
+ protected virtual object? ConvertIdValue(string? id) {
if (String.IsNullOrWhiteSpace(id))
return null;
@@ -145,6 +240,20 @@ private IMongoDbSet GetEntitySet() {
throw new NotSupportedException($"It is not possible to convert the ID to '{valueType}'");
}
+ ///
+ /// Asserts that the given entity is of the type managed by this repository.
+ ///
+ ///
+ /// The object that has to be asserted.
+ ///
+ ///
+ /// Returns an instance of the object casted to the type managed by this
+ /// repository.
+ ///
+ ///
+ /// Thrown when the given entity is not of the type managed by this
+ /// repository
+ ///
protected static TEntity Assert(object entity) {
if (!(entity is TEntity entityObj))
throw new ArgumentException($"The type '{entity.GetType()}' is not assignable from '{typeof(TEntity)}'");
@@ -152,6 +261,19 @@ protected static TEntity Assert(object entity) {
return entityObj;
}
+ ///
+ /// Gets the MongoDB filter definition for the given query filter.
+ ///
+ ///
+ /// The query filter to be converted to a MongoDB filter definition.
+ ///
+ ///
+ /// Returns an instance of that
+ /// is mapped from the given query filter.
+ ///
+ ///
+ /// Thrown when the given query filter is not supported by this repository.
+ ///
protected virtual FilterDefinition GetFilterDefinition(IQueryFilter? filter) {
if (filter == null || filter.IsEmpty())
return Builders.Filter.Empty;
@@ -174,6 +296,20 @@ protected virtual Expression> Field(string fieldName) {
#region Controllable
+ ///
+ /// Verifies if the repository exists in the underlying database.
+ ///
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ ///
+ ///
+ /// Returns true if the repository exists in the underlying
+ /// database, otherwise false.
+ ///
+ ///
+ /// Thrown when an error occurs while verifying the existence of the
+ /// collection in the underlying database.
+ ///
public async Task ExistsAsync(CancellationToken cancellationToken = default) {
try {
var entityDef = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
@@ -219,10 +355,35 @@ public async Task DropAsync(CancellationToken cancellationToken = default) {
#region Create
+ ///
+ /// A callback method that is invoked before the entity is created.
+ ///
+ ///
+ /// The entity that is about to be created.
+ ///
+ ///
+ /// Returns the entity that is about to be created.
+ ///
protected virtual TEntity OnCreating(TEntity entity) {
return entity;
}
+ ///
+ /// Creates a new entity in the repository.
+ ///
+ ///
+ /// The entity to be created in the repository.
+ ///
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ ///
+ ///
+ /// Returns the unique identifier of the created entity.
+ ///
+ ///
+ /// Thrown when an error occurs while creating the entity in the
+ /// underlying database.
+ ///
public async Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
@@ -240,7 +401,7 @@ public async Task CreateAsync(TEntity entity, CancellationToken cancella
await DbSet.Context.SaveChangesAsync(cancellationToken);
// TODO: this is UGLY! change the IRepository to use object keys instead?
- var id = RequireString(GetIdValue(entity));
+ var id = RequireString(GetEntityId(entity));
if (!String.IsNullOrWhiteSpace(TenantId)) {
Logger.TraceCreatedForTenant(TenantId, id);
@@ -272,7 +433,7 @@ public async Task> CreateAsync(IEnumerable entities, Canc
await DbSet.Context.SaveChangesAsync(cancellationToken);
// TODO: this is UGLY! Change the IRepository to work with object keys
- return entities.Select(x => RequireString(GetIdValue(x))).ToList();
+ return entities.Select(x => RequireString(GetEntityId(x))).ToList();
} catch (Exception ex) {
Logger.LogUnknownError(ex);
throw new RepositoryException("Could not add the list of entities", ex);
@@ -283,7 +444,7 @@ public async Task> CreateAsync(IEnumerable entities, Canc
#region Update
- protected TEntity OnUpdating(TEntity entity) {
+ protected virtual TEntity OnUpdating(TEntity entity) {
return entity;
}
@@ -294,7 +455,7 @@ public async Task UpdateAsync(TEntity entity, CancellationToken cancellati
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
- var id = GetIdValue(entity);
+ var id = GetEntityId(entity);
if (id == null)
throw new ArgumentException(nameof(entity), "Cannot determine the identifier of the entity");
@@ -346,7 +507,7 @@ public async Task DeleteAsync(TEntity entity, CancellationToken cancellati
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
- var entityId = GetIdValue(entity);
+ var entityId = GetEntityId(entity);
if (entityId == null)
throw new ArgumentException("The entity does not have an ID", nameof(entity));
@@ -400,7 +561,7 @@ Task IRepository.DeleteAsync(object entity, CancellationToken cancellation
}
try {
- var idValue = GetIdValue(id);
+ var idValue = ConvertIdValue(id);
var entry = Context.ChangeTracker.GetEntryById(idValue);
if (entry != null && entry.State == EntityEntryState.Deleted)
diff --git a/src/Deveel.Repsotiory.MongoFramework/Data/VersionUtil.cs b/src/Deveel.Repsotiory.MongoFramework/Data/VersionUtil.cs
deleted file mode 100644
index f508918..0000000
--- a/src/Deveel.Repsotiory.MongoFramework/Data/VersionUtil.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-
-namespace Deveel.Data {
- static class VersionUtil {
- public static VersionFormat GetFormat(Type propertyType) {
- if (propertyType == typeof(Guid) ||
- propertyType == typeof(string))
- return VersionFormat.Guid;
- if (propertyType == typeof(int) ||
- propertyType == typeof(long))
- return VersionFormat.Integer;
-
- throw new NotSupportedException($"The type '{propertyType}' is not valid to handle versions");
- }
-
- private static object FormatInteger(object value) {
- if (value is int i)
- return i;
- if (value is long l)
- return l;
-
- if (value is string s1 && Int64.TryParse(s1, out var l2))
- return l2;
- if (value is string s2 && Int32.TryParse(s2, out var i2))
- return i2;
-
- throw new NotSupportedException($"The value {value} is not a valid version integer");
- }
-
- private static object FormatGuid(object value) {
- if (value is Guid guid)
- return guid;
- if (value is string s)
- return s;
-
- throw new NotSupportedException($"The value {value} is not a valid version GUID");
- }
-
- public static object Format(object value, VersionFormat format) {
- if (format == VersionFormat.Integer) {
- return FormatInteger(value);
- } else if (format == VersionFormat.Guid) {
- return FormatGuid(value);
- }
-
- throw new NotSupportedException();
- }
-
- public static object GetNewVersion(VersionFormat format, Type propertyType, object currentValue) {
- if (format == VersionFormat.Guid)
- return GetNewGuid(propertyType);
- if (format == VersionFormat.Integer)
- return GetNewInteger(propertyType, currentValue);
-
- throw new NotSupportedException();
- }
-
- private static object GetNewGuid(Type propertyType) {
- if (propertyType == typeof(Guid))
- return Guid.NewGuid();
- if (propertyType == typeof(string))
- return Guid.NewGuid().ToString("N");
-
- throw new NotSupportedException();
- }
-
- private static object GetNewInteger(Type propertyType, object currentValue) {
- if (propertyType == typeof(int)) {
- if (currentValue == null)
- return 0;
- if (currentValue is int i)
- return i + 1;
- if (currentValue is long l)
- return (int) l + 1;
- }
-
- if (propertyType == typeof(long)) {
- if (currentValue == null)
- return 0L;
- if (currentValue is int i)
- return (long)(i + 1);
- if (currentValue is long l)
- return l + 1;
- }
-
- throw new NotSupportedException("Unable to increment a non integer");
- }
- }
-}
diff --git a/src/Deveel.Repsotiory.MongoFramework/Deveel.Repository.MongoFramework.xml b/src/Deveel.Repsotiory.MongoFramework/Deveel.Repository.MongoFramework.xml
index 769e3f4..582a8d4 100644
--- a/src/Deveel.Repsotiory.MongoFramework/Deveel.Repository.MongoFramework.xml
+++ b/src/Deveel.Repsotiory.MongoFramework/Deveel.Repository.MongoFramework.xml
@@ -25,5 +25,96 @@
This API supports the logging infrastructure and is not intended to be used directly from your code. It is subject to change in the future.
+
+
+ An implementation of contract
+ that uses the MongoDB system to store and retrieve data.
+
+
+ The type of the that is used to
+ handling the connection to the MongoDB server.
+
+
+ The type of the entity that is stored in the repository.
+
+
+
+
+ Constructs the repository with the given context and logger.
+
+
+ The context that is used to handle the connection to the MongoDB server.
+
+
+ A logger instance that is used to log messages from the repository.
+
+
+
+
+ Constructs the repository with the given context and logger.
+
+
+ The context that is used to handle the connection to the MongoDB server.
+
+
+ A logger instance that is used to log messages from the repository.
+
+
+
+
+ Gets the context that is used to handle the connection to the MongoDB server.
+
+
+
+
+ Gets the that is used to handle the
+ repository operations.
+
+
+
+
+ Gets the instance that is used to log messages
+
+
+
+
+ When the underlying context is a ,
+ this property returns the tenant identifier that is used to filter
+ the data in the repository.
+
+
+
+
+ Gets the instance that is used
+ to handle the data in the repository.
+
+
+
+
+ Throws an exception if the repository has been disposed.
+
+
+ Thrown when the repository has been disposed.
+
+
+
+
+ Constructs a new that is
+ coherent with the context and the entity type.
+
+
+
+
+
+
+ Gets the value of the ID property of the given entity.
+
+
+ The entity whose ID property value is to be retrieved.
+
+
+ Returns the value of the ID property of the given entity.
+
+