2022-12-07 14:05:11 +01:00
|
|
|
using Raven.Client.Documents.Conventions;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text;
|
2022-12-07 15:20:53 +01:00
|
|
|
using Rv = Raven.Client;
|
2022-12-07 14:05:11 +01:00
|
|
|
|
2026-05-05 11:34:32 +02:00
|
|
|
namespace Mixtape.Raven;
|
2022-12-07 14:05:11 +01:00
|
|
|
|
2022-12-09 10:42:39 +01:00
|
|
|
public class RavenDocumentConventionsBuilder : IRavenDocumentConventionsBuilder
|
2022-12-07 14:05:11 +01:00
|
|
|
{
|
2026-05-11 15:17:00 +02:00
|
|
|
protected HashSet<Type> PolymorphTypes { get; } = [];
|
2022-12-07 14:05:11 +01:00
|
|
|
|
2026-05-05 11:34:32 +02:00
|
|
|
protected Type AcceptsMixtapeConventionsType { get; set; } = typeof(ISupportsDbConventions);
|
2022-12-07 14:05:11 +01:00
|
|
|
|
|
|
|
|
protected char IdentityPartsSeparator { get; set; } = '.';
|
|
|
|
|
|
2026-05-05 11:34:32 +02:00
|
|
|
protected IMixtapeOptions Options { get; }
|
2022-12-07 14:05:11 +01:00
|
|
|
|
|
|
|
|
protected static ConcurrentDictionary<Type, string> CachedTypeCollectionNameMap = new();
|
|
|
|
|
|
|
|
|
|
|
2026-05-05 11:34:32 +02:00
|
|
|
public RavenDocumentConventionsBuilder(IMixtapeOptions options)
|
2022-12-07 14:05:11 +01:00
|
|
|
{
|
|
|
|
|
Options = options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public void Run(DocumentConventions conventions)
|
|
|
|
|
{
|
2022-12-27 20:56:42 +01:00
|
|
|
conventions.MaxNumberOfRequestsPerSession = 100_000;
|
2022-12-07 14:05:11 +01:00
|
|
|
conventions.IdentityPartsSeparator = IdentityPartsSeparator;
|
|
|
|
|
conventions.TransformTypeCollectionNameToDocumentIdPrefix = TransformTypeCollectionNameToDocumentIdPrefix;
|
|
|
|
|
conventions.FindCollectionName = FindCollectionName;
|
2026-05-05 11:34:32 +02:00
|
|
|
conventions.RegisterAsyncIdConvention<MixtapeIdEntity>((_, entity) => GetDocumentId(conventions, entity));
|
2022-12-07 14:05:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get a document ID from an entity
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual Task<string> GetDocumentId<T>(DocumentConventions conventions, T entity)
|
|
|
|
|
{
|
|
|
|
|
string collection = conventions.GetCollectionName(entity);
|
|
|
|
|
|
|
|
|
|
StringBuilder documentId = new();
|
|
|
|
|
documentId.Append(conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection));
|
|
|
|
|
documentId.Append(IdentityPartsSeparator);
|
|
|
|
|
documentId.Append(IdGenerator.Create());
|
|
|
|
|
|
|
|
|
|
return Task.FromResult(documentId.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the collection name for a certain type based on internal rules
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual string FindCollectionName(Type originalType)
|
|
|
|
|
{
|
|
|
|
|
string collection = null;
|
|
|
|
|
|
|
|
|
|
Type type = originalType;
|
|
|
|
|
|
|
|
|
|
Func<string, string> cache = name =>
|
|
|
|
|
{
|
|
|
|
|
CachedTypeCollectionNameMap.TryAdd(type, name);
|
|
|
|
|
return name;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// get inner type for revisions
|
2022-12-07 15:20:53 +01:00
|
|
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Rv.Documents.Subscriptions.Revision<>))
|
2022-12-07 14:05:11 +01:00
|
|
|
{
|
|
|
|
|
type = type.GetGenericArguments().FirstOrDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// try to resolve from cache
|
|
|
|
|
if (CachedTypeCollectionNameMap.TryGetValue(type, out collection))
|
|
|
|
|
{
|
|
|
|
|
return collection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// do not alter non-internal entities
|
2026-05-05 11:34:32 +02:00
|
|
|
if (!AcceptsMixtapeConventionsType.IsAssignableFrom(type))
|
2022-12-07 14:05:11 +01:00
|
|
|
{
|
|
|
|
|
return cache(DocumentConventions.DefaultGetCollectionName(type));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// use name from attribute if available
|
|
|
|
|
RavenCollectionAttribute collectionAttribute = type.GetCustomAttribute<RavenCollectionAttribute>(true);
|
|
|
|
|
|
|
|
|
|
if (collectionAttribute != null)
|
|
|
|
|
{
|
|
|
|
|
return cache(collectionAttribute.Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// use base interface if available
|
2026-05-05 11:34:32 +02:00
|
|
|
Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && AcceptsMixtapeConventionsType.IsAssignableFrom(x) && x.Name != AcceptsMixtapeConventionsType.Name);
|
2022-12-07 14:05:11 +01:00
|
|
|
|
|
|
|
|
if (interfaceBaseType != null)
|
|
|
|
|
{
|
|
|
|
|
// use name from attribute if available
|
|
|
|
|
collectionAttribute = interfaceBaseType.GetCustomAttribute<RavenCollectionAttribute>(true);
|
|
|
|
|
if (collectionAttribute != null)
|
|
|
|
|
{
|
|
|
|
|
return cache(collectionAttribute.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// use base type for polymorphism
|
|
|
|
|
Type polymorphBaseType = PolymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x));
|
|
|
|
|
|
|
|
|
|
if (polymorphBaseType != null)
|
|
|
|
|
{
|
|
|
|
|
return cache(DocumentConventions.DefaultGetCollectionName(polymorphBaseType));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cache(DocumentConventions.DefaultGetCollectionName(type));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Translates the types collection name to the document id prefix
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual string TransformTypeCollectionNameToDocumentIdPrefix(string name)
|
|
|
|
|
{
|
|
|
|
|
RavenOptions options = Options.For<RavenOptions>();
|
|
|
|
|
if (options != null && !options.CollectionPrefix.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
name = options.CollectionPrefix.EnsureEndsWith(IdentityPartsSeparator) + name.TrimStart(options.CollectionPrefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return name.ToCamelCaseId();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-12-09 10:42:39 +01:00
|
|
|
public interface IRavenDocumentConventionsBuilder
|
2022-12-07 14:05:11 +01:00
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Applies internal rules to the RavenDB document conventions
|
|
|
|
|
/// </summary>
|
|
|
|
|
void Run(DocumentConventions conventions);
|
|
|
|
|
}
|