Skip to content
This repository has been archived by the owner on Jul 12, 2020. It is now read-only.

Feature/collection mapping #97

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3289859
Merge pull request #2 from Elfocrash/develop
Elfocrash Apr 25, 2018
d2d23d5
Merge pull request #4 from Elfocrash/develop
Elfocrash Apr 30, 2018
7ad7e48
Merge pull request #5 from Elfocrash/develop
Elfocrash May 10, 2018
f639760
Update README.md
Elfocrash May 13, 2018
a30b186
Update README.md
Elfocrash May 13, 2018
2b74645
Merge pull request #6 from Elfocrash/develop
Elfocrash May 13, 2018
129f97c
Merge pull request #17 from Elfocrash/develop
Elfocrash Jul 18, 2018
887ca70
Merge pull request #21 from Elfocrash/develop
Elfocrash Aug 5, 2018
d3495e6
Merge pull request #23 from Elfocrash/develop
Elfocrash Aug 31, 2018
b08049e
Merge pull request #26 from Elfocrash/develop
Elfocrash Sep 15, 2018
5d328c3
Merge pull request #33 from Elfocrash/develop
Elfocrash Oct 9, 2018
b11ffae
Merge pull request #35 from Elfocrash/develop
Elfocrash Oct 26, 2018
e04ad09
Merge pull request #37 from Elfocrash/develop
Elfocrash Nov 10, 2018
5f8c570
Merge pull request #45 from Elfocrash/develop
Elfocrash Dec 4, 2018
4a206ab
Merge pull request #52 from Elfocrash/develop
Elfocrash Jan 13, 2019
f253567
Fluent mapping implementation poc
RobEyres May 31, 2019
056ca6a
Merge branch 'master' of https://github.com/roberino/Cosmonaut into f…
RobEyres Jul 12, 2019
a486dc8
Add tests
RobEyres Jul 12, 2019
3341101
More tests and a fix
RobEyres Jul 12, 2019
c08dcc1
Add more tests and collection name default behaviour
RobEyres Jul 12, 2019
3acf371
Merge branch 'develop' of https://github.com/roberino/Cosmonaut into …
RobEyres Jul 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var user = await cosmosStore.FindAsync("userId", new RequestOptions());
##### Querying for entities using LINQ

In order to query for entities all you have to do is call the `.Query()` method and then use LINQ to create the query you want.
It is HIGHLY recommended that you use one of the `Async` methods to get the results back, such as `ToListAsync` or `FirstOrDefaultAsync` , when available.
It is HIGHLY recommended that you use one of the `Async` extension methods to get the results back, such as `ToListAsync` or `FirstOrDefaultAsync` , when available.

```csharp
var user = await cosmoStore.Query().FirstOrDefaultAsync(x => x.Username == "elfocrash");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Cosmonaut.Extensions;
using Cosmonaut.Configuration;
using Cosmonaut.WebJobs.Extensions.Config;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.ChangeFeedProcessor;
Expand All @@ -12,6 +9,9 @@
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;
using System.Threading.Tasks;

namespace Cosmonaut.WebJobs.Extensions.Trigger
{
Expand Down Expand Up @@ -170,10 +170,9 @@ private string GetMonitoredCollectionName(CosmosStoreTriggerAttribute attribute)
if (!string.IsNullOrEmpty(ResolveAttributeValue(attribute.CollectionName)))
return ResolveAttributeValue(attribute.CollectionName);

var entityType = typeof(T);
var isSharedCollection = entityType.UsesSharedCollection();

return isSharedCollection ? entityType.GetSharedCollectionName() : entityType.GetCollectionName();
var mapping = DefaultEntityConfigurationProvider.DefaultMapping<T>();

return mapping.CollectionName;
}

internal static TimeSpan ResolveTimeSpanFromMilliseconds(string nameOfProperty, TimeSpan baseTimeSpan, int? attributeValue)
Expand Down Expand Up @@ -254,10 +253,11 @@ internal string ResolveConnectionString(string unresolvedConnectionString, strin
private ChangeFeedProcessorOptions BuildProcessorOptions(CosmosStoreTriggerAttribute attribute)
{
var leasesOptions = _bindingOptions.LeaseOptions;
var entityType = typeof(T);
var mapping = DefaultEntityConfigurationProvider.DefaultMapping<T>();

var processorOptions = new ChangeFeedProcessorOptions
{
LeasePrefix = ResolveAttributeValue(attribute.LeaseCollectionPrefix) ?? (entityType.UsesSharedCollection() ? $"{entityType.GetSharedCollectionName()}_{entityType.GetSharedCollectionEntityName()}_" : $"{entityType.GetCollectionName()}_"),
LeasePrefix = ResolveAttributeValue(attribute.LeaseCollectionPrefix) ?? (mapping.IsShared ? $"{mapping.CollectionName}_{mapping.SharedCollectionEntityName}_" : $"{mapping.CollectionName}_"),
FeedPollDelay = ResolveTimeSpanFromMilliseconds(nameof(CosmosStoreTriggerAttribute.FeedPollDelay), leasesOptions.FeedPollDelay, attribute.FeedPollDelay),
LeaseAcquireInterval = ResolveTimeSpanFromMilliseconds(nameof(CosmosStoreTriggerAttribute.LeaseAcquireInterval), leasesOptions.LeaseAcquireInterval, attribute.LeaseAcquireInterval),
LeaseExpirationInterval = ResolveTimeSpanFromMilliseconds(nameof(CosmosStoreTriggerAttribute.LeaseExpirationInterval), leasesOptions.LeaseExpirationInterval, attribute.LeaseExpirationInterval),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Cosmonaut.Configuration;
using Cosmonaut.Extensions;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.ChangeFeedProcessor.FeedProcessing;
Expand Down Expand Up @@ -40,9 +41,9 @@ public Task OpenAsync(IChangeFeedObserverContext context)

public Task ProcessChangesAsync(IChangeFeedObserverContext context, IReadOnlyList<Document> docs, CancellationToken cancellationToken)
{
var entityType = typeof(T);
var isSharedCollection = entityType.UsesSharedCollection();
var sharedCollectionEntityName = isSharedCollection ? entityType.GetSharedCollectionEntityName() : string.Empty;
var mapping = DefaultEntityConfigurationProvider.DefaultMapping<T>();

var sharedCollectionEntityName = mapping.IsShared ? mapping.SharedCollectionEntityName : string.Empty;

if (string.IsNullOrEmpty(sharedCollectionEntityName))
{
Expand Down
9 changes: 9 additions & 0 deletions src/Cosmonaut/Attributes/ISharedCosmosCollectionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Cosmonaut.Attributes
{
public interface ISharedCosmosCollectionInfo
{
string SharedCollectionName { get; }
string EntityName { get; }
bool UseEntityFullName { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Cosmonaut.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class SharedCosmosCollectionAttribute : Attribute
public class SharedCosmosCollectionAttribute : Attribute, ISharedCosmosCollectionInfo
{
public string SharedCollectionName { get; }

Expand Down
36 changes: 36 additions & 0 deletions src/Cosmonaut/Configuration/DefaultEntityConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Cosmonaut.Extensions;

namespace Cosmonaut.Configuration
{
internal class DefaultEntityConfigurationProvider : IEntityConfigurationProvider
{
private DefaultEntityConfigurationProvider()
{
}

internal static EntityCollectionMapping DefaultMapping<TEntity>()
{
return Instance.GetEntityCollectionMapping<TEntity>();
}

internal static EntityCollectionMapping DefaultMapping(Type entityType)
{
return Instance.GetEntityCollectionMapping(entityType);
}

public static readonly IEntityConfigurationProvider Instance = new DefaultEntityConfigurationProvider();

public EntityCollectionMapping GetEntityCollectionMapping<TEntity>() =>
GetEntityCollectionMapping(typeof(TEntity));

public EntityCollectionMapping GetEntityCollectionMapping(Type entityType)
{
var pkd = entityType.GetPartitionKeyDefinitionForEntity();
var collName = entityType.GetCollectionName();
var sharedCollInfo = entityType.GetSharedCollectionInfo();

return new EntityCollectionMapping(entityType, pkd, sharedCollInfo, collName);
}
}
}
61 changes: 61 additions & 0 deletions src/Cosmonaut/Configuration/EntityCollectionMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Cosmonaut.Attributes;
using Humanizer;
using Microsoft.Azure.Documents;

namespace Cosmonaut.Configuration
{
public class EntityCollectionMapping
{
public EntityCollectionMapping(
Type entityType,
PartitionKeyDefinition partitionKeyDefinition,
ISharedCosmosCollectionInfo sharedCollectionInfo,
string collectionName)
{
PartitionKeyDefinition = partitionKeyDefinition;
CollectionName = sharedCollectionInfo?.SharedCollectionName ?? collectionName;
SharedCollectionEntityName = GetSharedCollectionEntityName(entityType, sharedCollectionInfo);
IsShared = sharedCollectionInfo != null;
}

public EntityCollectionMapping(
Type entityType,
PartitionKeyDefinition partitionKeyDefinition,
string collectionName,
string sharedCollectionEntityName = null)
{
PartitionKeyDefinition = partitionKeyDefinition;
CollectionName = collectionName;
SharedCollectionEntityName = sharedCollectionEntityName;
IsShared = sharedCollectionEntityName != null;
}

public PartitionKeyDefinition PartitionKeyDefinition { get; }

public string CollectionName { get; }

public string SharedCollectionEntityName { get; }

public bool IsShared { get; }

internal string GetCosmosStoreCollectionName(string collectionPrefix, string overridenCollectionName)
{
var hasOverridenName = !string.IsNullOrEmpty(overridenCollectionName);

return $"{collectionPrefix ?? string.Empty}{(hasOverridenName ? overridenCollectionName : CollectionName)}";
}

private static string GetSharedCollectionEntityName(Type entityType, ISharedCosmosCollectionInfo collectionNameAttribute)
{
if (collectionNameAttribute == null)
{
return null;
}

var collectionName = collectionNameAttribute.UseEntityFullName ? entityType.FullName : collectionNameAttribute.EntityName;

return !string.IsNullOrEmpty(collectionName) ? collectionName : entityType.Name.ToLower().Pluralize();
}
}
}
131 changes: 131 additions & 0 deletions src/Cosmonaut/Configuration/FluentCollectionMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Cosmonaut.Attributes;
using Microsoft.Azure.Documents;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;

namespace Cosmonaut.Configuration
{
public interface IFluentCollectionMappingBuilder
{
IFluentCollectionMappingBuilder Configure<TEntity>(Action<FluentCollectionMapping<TEntity>> config)
where TEntity : class;
}

internal sealed class FluentCollectionMappingBuilder : IFluentCollectionMappingBuilder
{
private readonly ProviderImpl provider;

public FluentCollectionMappingBuilder(IEntityConfigurationProvider baseProvider = null)
{
provider = new ProviderImpl(baseProvider ?? DefaultEntityConfigurationProvider.Instance);
}

public IFluentCollectionMappingBuilder Configure<TEntity>(Action<FluentCollectionMapping<TEntity>> config)
where TEntity : class
{
var mapper = new FluentCollectionMapping<TEntity>();

config(mapper);

provider.Mappings[typeof(TEntity)] = mapper.Mapping;

return this;
}

public IEntityConfigurationProvider Build() => provider;

private class ProviderImpl : IEntityConfigurationProvider
{
private readonly IEntityConfigurationProvider baseProvider;

public IDictionary<Type, EntityCollectionMapping> Mappings { get; } = new Dictionary<Type, EntityCollectionMapping>();

public ProviderImpl(IEntityConfigurationProvider baseProvider)
{
this.baseProvider = baseProvider;
}

public EntityCollectionMapping GetEntityCollectionMapping<TEntity>() =>
GetEntityCollectionMapping(typeof(TEntity));

public EntityCollectionMapping GetEntityCollectionMapping(Type entityType)
{
if (!Mappings.TryGetValue(entityType, out var val))
return baseProvider.GetEntityCollectionMapping(entityType);

if (!string.IsNullOrEmpty(val.CollectionName))
{
return val;
}

var defaultMapping = baseProvider.GetEntityCollectionMapping(entityType);

return new EntityCollectionMapping(entityType, val.PartitionKeyDefinition, defaultMapping.CollectionName, defaultMapping.SharedCollectionEntityName);
}
}
}

public sealed class FluentCollectionMapping<TEntity>
where TEntity : class
{
public FluentCollectionMapping()
{
Mapping = DefaultEntityConfigurationProvider.DefaultMapping<TEntity>();
}

public FluentCollectionMapping<TEntity> WithPartition(Expression<Func<TEntity, object>> partitioningExpression)
{
if (partitioningExpression.Body is MemberExpression me)
{
return SetPartition(me.Member.Name);
}

if (partitioningExpression.Body is UnaryExpression ux && ux.NodeType == ExpressionType.Convert && ux.Operand is MemberExpression me2)
{
return SetPartition(me2.Member.Name);
}

throw new NotSupportedException(partitioningExpression.ToString());
}

public FluentCollectionMapping<TEntity> WithCollection(string collectionName, bool isShared)
{
return Reconfigure(null, collectionName, isShared);
}

internal EntityCollectionMapping Mapping { get; private set; }

private FluentCollectionMapping<TEntity> SetPartition(string name)
{
if (!name.StartsWith("/"))
{
name = $"/{name}";
}

var pk = new PartitionKeyDefinition()
{
Paths = new Collection<string>(new[] {name})
};

return Reconfigure(pk, Mapping.CollectionName, Mapping.IsShared);
}

private FluentCollectionMapping<TEntity> Reconfigure(PartitionKeyDefinition partitionKeyDefinition, string collectionName, bool? isShared)
{
if (isShared.GetValueOrDefault(Mapping.IsShared))
{
Mapping = new EntityCollectionMapping(typeof(TEntity),
partitionKeyDefinition ?? Mapping.PartitionKeyDefinition, new SharedCosmosCollectionAttribute(collectionName, typeof(TEntity).Name), collectionName);
}
else
{
Mapping = new EntityCollectionMapping(typeof(TEntity),
partitionKeyDefinition ?? Mapping.PartitionKeyDefinition, collectionName ?? Mapping.CollectionName);
}

return this;
}
}
}
10 changes: 10 additions & 0 deletions src/Cosmonaut/Configuration/IEntityConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Cosmonaut.Configuration
{
public interface IEntityConfigurationProvider
{
EntityCollectionMapping GetEntityCollectionMapping<TEntity>();
EntityCollectionMapping GetEntityCollectionMapping(Type entityType);
}
}
Loading