From 0788386c1d47cd1ac3e90a6d6ac6e3559db2c63f Mon Sep 17 00:00:00 2001 From: David Pine Date: Fri, 29 Sep 2023 13:41:14 -0500 Subject: [PATCH] Primary ctors, simplify new() usage, implicit operators for serialization options, and flow LINQ serialization options in read calls. --- .editorconfig | 1 + .../Processors/BookChangeFeedProcessor.cs | 19 ++----- .../DefaultCosmosEventSourcingBuilder.cs | 25 ++++----- .../Builders/EventItemProjectionBuilder.cs | 20 +++---- .../DefaultEventSourcingProcessor.cs | 54 +++++++------------ .../DefaultEventSourcingProvider.cs | 9 +--- .../DomainEventsRequiredException.cs | 12 ++--- ...mPartitionKeyAttributeRequiredException.cs | 15 ++---- ...rtitionKeyAttributeCombinationException.cs | 15 ++---- .../InvalidPartitionKeyValueException.cs | 19 +++---- .../ReplayMethodNotDefinedException.cs | 13 ++--- .../DeadLetterProjectionDecorator.cs | 42 ++++++--------- .../DefaultDomainEventProjection.cs | 23 +++----- .../Stores/DefaultEventStore.Def.cs | 18 ++----- .../Stores/DefaultEventStore.Read.cs | 10 ++-- .../Stores/DefaultEventStore.Write.cs | 10 ++-- ...CosmosRepositoryChangeFeedHostedService.cs | 16 +++--- .../Attributes/ContainerAttribute.cs | 15 +++--- .../Attributes/PartitionKeyPathAttribute.cs | 15 +++--- .../Attributes/UniqueKeyAttribute.cs | 24 ++++----- .../Builders/ContainerOptionsBuilder.cs | 15 +++--- .../ChangeFeed/DefaultChangeFeedService.cs | 13 ++--- .../ChangeFeed/InMemory/InMemoryChangeFeed.cs | 28 ++++------ ...ultChangeFeedContainerProcessorProvider.cs | 43 +++++---------- .../DefaultChangeFeedOptionsProvider.cs | 7 +-- .../Exceptions/BatchOperationException.cs | 19 +++---- .../InvalidEtagConfigurationException.cs | 13 ++--- .../MissMatchedChangeFeedOptionsException.cs | 19 +++---- .../MissMatchedTypeDiscriminatorException.cs | 28 ++++------ .../Factories/DefaultRepositoryFactory.cs | 16 +++--- .../Factories/IRepositoryFactory.cs | 2 +- .../Internals/InternalPatchOperation.cs | 15 ++---- .../Options/ItemConfiguration.cs | 51 +++++++----------- .../Options/RepositoryOptions.cs | 1 - .../Options/RepositorySerializationOptions.cs | 26 +++++++++ .../DefaultCosmosQueryableProcessor.cs | 4 +- ...ontainerSyncContainerPropertiesProvider.cs | 7 +-- .../DefaultCosmosClientOptionsProvider.cs | 8 +-- ...osmosContainerDefaultTimeToLiveProvider.cs | 14 +++-- .../DefaultCosmosContainerNameProvider.cs | 9 +--- .../DefaultCosmosContainerProvider.cs | 7 +-- .../DefaultCosmosItemConfigurationProvider.cs | 49 ++++++----------- .../DefaultCosmosPartitionKeyPathProvider.cs | 9 +--- ...DefaultCosmosStrictTypeCheckingProvider.cs | 9 +--- .../DefaultCosmosThroughputProvider.cs | 10 ++-- .../DefaultCosmosUniqueKeyPolicyProvider.cs | 4 +- .../DefaultRepositoryExpressionProvider.cs | 11 ++-- .../QueryResult.cs | 21 +++----- .../Repositories/DefaultRepository.Batch.cs | 6 +-- .../Repositories/DefaultRepository.Count.cs | 29 +++++----- .../Repositories/DefaultRepository.Create.cs | 4 +- .../Repositories/DefaultRepository.Delete.cs | 4 +- .../Repositories/DefaultRepository.Exists.cs | 13 ++--- .../Repositories/DefaultRepository.Paging.cs | 32 ++++++----- .../Repositories/DefaultRepository.Read.cs | 38 ++++++------- .../Repositories/DefaultRepository.Specs.cs | 13 ++--- .../Repositories/DefaultRepository.Update.cs | 8 +-- .../Repositories/DefaultRepository.cs | 43 +++++---------- .../Repositories/InMemoryRepository.Create.cs | 2 +- .../Repositories/InMemoryRepository.Update.cs | 2 +- .../DefaultCosmosContainerSyncService.cs | 9 +--- .../Builder/SpecificationBuilder.cs | 7 +-- .../Specification/WhereExpressionInfo.cs | 14 +++-- .../DefaultRepositoryOptionsValidator.cs | 2 +- .../AcceptanceTests.Execute.cs | 14 +++-- .../DefaultRepositoryTests.cs | 7 ++- 66 files changed, 422 insertions(+), 658 deletions(-) diff --git a/.editorconfig b/.editorconfig index bae8194e1..378a9fc6b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,7 @@ file_header_template = Copyright (c) David Pine. All rights reserved.\nLicensed indent_size = 4 tab_width = 4 trim_trailing_whitespace = true +end_of_line = lf ############################### # .NET Coding Conventions # diff --git a/samples/Microsoft.Azure.CosmosRepository.AspNetCore/ChangedFeedSamples.Shared/Processors/BookChangeFeedProcessor.cs b/samples/Microsoft.Azure.CosmosRepository.AspNetCore/ChangedFeedSamples.Shared/Processors/BookChangeFeedProcessor.cs index c077f1789..4938ebf6b 100644 --- a/samples/Microsoft.Azure.CosmosRepository.AspNetCore/ChangedFeedSamples.Shared/Processors/BookChangeFeedProcessor.cs +++ b/samples/Microsoft.Azure.CosmosRepository.AspNetCore/ChangedFeedSamples.Shared/Processors/BookChangeFeedProcessor.cs @@ -8,29 +8,20 @@ namespace ChangedFeedSamples.Shared.Processors; -public class BookChangeFeedProcessor : IItemChangeFeedProcessor +public class BookChangeFeedProcessor(ILogger logger, + IRepository bookByIdReferenceRepository) : IItemChangeFeedProcessor { - private readonly ILogger _logger; - private readonly IRepository _bookByIdReferenceRepository; - - public BookChangeFeedProcessor(ILogger logger, - IRepository bookByIdReferenceRepository) - { - _logger = logger; - _bookByIdReferenceRepository = bookByIdReferenceRepository; - } - public async ValueTask HandleAsync(Book rating, CancellationToken cancellationToken) { - _logger.LogInformation("Change detected for book with ID: {BookId}", rating.Id); + logger.LogInformation("Change detected for book with ID: {BookId}", rating.Id); if (!rating.HasBeenUpdated) { - await _bookByIdReferenceRepository + await bookByIdReferenceRepository .CreateAsync(new BookByIdReference(rating.Id, rating.Category), cancellationToken); } - _logger.LogInformation("Processed change for book with ID: {BookId}", rating.Id); + logger.LogInformation("Processed change for book with ID: {BookId}", rating.Id); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Builders/DefaultCosmosEventSourcingBuilder.cs b/src/Microsoft.Azure.CosmosEventSourcing/Builders/DefaultCosmosEventSourcingBuilder.cs index b2a244203..19b4b5e8e 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Builders/DefaultCosmosEventSourcingBuilder.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Builders/DefaultCosmosEventSourcingBuilder.cs @@ -14,13 +14,8 @@ namespace Microsoft.Azure.CosmosEventSourcing.Builders; -internal class DefaultCosmosEventSourcingBuilder : ICosmosEventSourcingBuilder +internal class DefaultCosmosEventSourcingBuilder(IServiceCollection services) : ICosmosEventSourcingBuilder { - private readonly IServiceCollection _services; - - public DefaultCosmosEventSourcingBuilder(IServiceCollection services) => - _services = services; - public IEventItemProjectionBuilder AddEventItemProjection( Action>? optionsAction = null) where TEventItem : EventItem @@ -30,12 +25,12 @@ internal class DefaultCosmosEventSourcingBuilder : ICosmosEventSourcingBuilder EventSourcingProcessorOptions options = new(); optionsAction?.Invoke(options); - _services.AddSingleton(options); - _services.AddScoped, TProjection>(); - _services.AddSingleton>(); + services.AddSingleton(options); + services.AddScoped, TProjection>(); + services.AddSingleton>(); return new EventItemProjectionBuilder( - _services, + services, this); } @@ -47,10 +42,10 @@ internal class DefaultCosmosEventSourcingBuilder : ICosmosEventSourcingBuilder EventSourcingProcessorOptions options = new(); optionsAction?.Invoke(options); - _services.AddSingleton(options); - _services + services.AddSingleton(options); + services .AddScoped, DefaultDomainEventProjection>(); - _services.AddSingleton>(); + services.AddSingleton>(); return this; } @@ -80,7 +75,7 @@ internal class DefaultCosmosEventSourcingBuilder : ICosmosEventSourcingBuilder assemblies = AppDomain.CurrentDomain.GetAssemblies(); } - _services.Scan(x => x.FromAssemblies(assemblies) + services.Scan(x => x.FromAssemblies(assemblies) .AddClasses(classes => classes.AssignableTo(typeof(IDomainEventProjection<,,>))) .AsImplementedInterfaces() .WithScopedLifetime()); @@ -92,7 +87,7 @@ internal class DefaultCosmosEventSourcingBuilder : ICosmosEventSourcingBuilder Action? setupAction = default, Action? additionSetupAction = default) { - _services.AddCosmosRepository(options => + services.AddCosmosRepository(options => { options.ContainerPerItemType = true; setupAction?.Invoke(options); diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Builders/EventItemProjectionBuilder.cs b/src/Microsoft.Azure.CosmosEventSourcing/Builders/EventItemProjectionBuilder.cs index 28c4a8782..3d77d2faa 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Builders/EventItemProjectionBuilder.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Builders/EventItemProjectionBuilder.cs @@ -9,31 +9,23 @@ namespace Microsoft.Azure.CosmosEventSourcing.Builders; -internal class EventItemProjectionBuilder : +internal class EventItemProjectionBuilder( + IServiceCollection services, + ICosmosEventSourcingBuilder eventSourcingBuilder) : IEventItemProjectionBuilder where TEventItem : EventItem where TProjectionKey : IProjectionKey { - private readonly IServiceCollection _services; - - public EventItemProjectionBuilder( - IServiceCollection services, - ICosmosEventSourcingBuilder eventSourcingBuilder) - { - _services = services; - EventSourcingBuilder = eventSourcingBuilder; - } - - public ICosmosEventSourcingBuilder EventSourcingBuilder { get; } + public ICosmosEventSourcingBuilder EventSourcingBuilder { get; } = eventSourcingBuilder; public IEventItemProjectionBuilder WithDeadLetterDecorator( Action>? optionsAction = null) { - _services.Decorate, + services.Decorate, DeadLetterProjectionDecorator>(); DeadLetterOptions options = new(); optionsAction?.Invoke(options); - _services.AddSingleton(options); + services.AddSingleton(options); return this; } diff --git a/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProcessor.cs b/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProcessor.cs index 3012c1f01..890401834 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProcessor.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProcessor.cs @@ -13,61 +13,47 @@ namespace Microsoft.Azure.CosmosEventSourcing.ChangeFeed; -internal class DefaultEventSourcingProcessor : IEventSourcingProcessor +internal class DefaultEventSourcingProcessor( + EventSourcingProcessorOptions options, + ICosmosContainerService containerService, + ILeaseContainerProvider leaseContainerProvider, + ILogger> logger, + IServiceProvider serviceProvider) : IEventSourcingProcessor where TSourcedEvent : EventItem where TProjectionKey : IProjectionKey { - private readonly EventSourcingProcessorOptions _options; - private readonly ICosmosContainerService _containerService; - private readonly ILeaseContainerProvider _leaseContainerProvider; - private readonly ILogger> _logger; - private readonly IServiceProvider _serviceProvider; private ChangeFeedProcessor? _processor; - public DefaultEventSourcingProcessor( - EventSourcingProcessorOptions options, - ICosmosContainerService containerService, - ILeaseContainerProvider leaseContainerProvider, - ILogger> logger, - IServiceProvider serviceProvider) - { - _options = options; - _containerService = containerService; - _leaseContainerProvider = leaseContainerProvider; - _logger = logger; - _serviceProvider = serviceProvider; - } - public async Task StartAsync() { - Container itemContainer = await _containerService.GetContainerAsync(); - Container leaseContainer = await _leaseContainerProvider.GetLeaseContainerAsync(); + Container itemContainer = await containerService.GetContainerAsync(); + Container leaseContainer = await leaseContainerProvider.GetLeaseContainerAsync(); ChangeFeedProcessorBuilder builder = itemContainer - .GetChangeFeedProcessorBuilder(_options.ProcessorName, (changes, token) => + .GetChangeFeedProcessorBuilder(options.ProcessorName, (changes, token) => OnChangesAsync(changes, itemContainer.Id, token)) .WithLeaseContainer(leaseContainer) - .WithInstanceName(_options.InstanceName) + .WithInstanceName(options.InstanceName) .WithErrorNotification((_, exception) => OnErrorAsync(exception, itemContainer.Id)); - if (_options.PollInterval.HasValue) + if (options.PollInterval.HasValue) { - builder.WithPollInterval(_options.PollInterval.Value); + builder.WithPollInterval(options.PollInterval.Value); } _processor = builder.Build(); - _logger.LogInformation("Starting change feed processor for container {ContainerName} with key {ProjectionKey} and processor name {ProcessorName}", + logger.LogInformation("Starting change feed processor for container {ContainerName} with key {ProjectionKey} and processor name {ProcessorName}", itemContainer.Id, typeof(TProjectionKey).Name, - _options.ProcessorName); + options.ProcessorName); await _processor.StartAsync(); - _logger.LogInformation("Successfully started change feed processor for container {ContainerName} with key {ProjectionKey} and processor name {ProcessorName}", + logger.LogInformation("Successfully started change feed processor for container {ContainerName} with key {ProjectionKey} and processor name {ProcessorName}", itemContainer.Id, typeof(TProjectionKey).Name, - _options.ProcessorName); + options.ProcessorName); } private async Task OnChangesAsync( @@ -75,12 +61,12 @@ public async Task StartAsync() string containerName, CancellationToken cancellationToken) { - _logger.LogDebug("Detected changes for container {ContainerName} total ({ChangesCount})", + logger.LogDebug("Detected changes for container {ContainerName} total ({ChangesCount})", containerName, changes.Count); foreach (TSourcedEvent change in changes) { - using IServiceScope scope = _serviceProvider.CreateScope(); + using IServiceScope scope = serviceProvider.CreateScope(); IEventItemProjection projection = scope.ServiceProvider .GetRequiredService>(); @@ -93,7 +79,7 @@ public async Task StartAsync() } catch (Exception e) { - _logger.LogError(e, + logger.LogError(e, "Failed handling projection for container {ContainerName} source event ID {SourcedEventId}", containerName, change.Id); } @@ -102,7 +88,7 @@ public async Task StartAsync() private Task OnErrorAsync(Exception exception, string containerName) { - _logger.LogError(exception, "Failed handling when handling changes detected from container {ContainerName}", + logger.LogError(exception, "Failed handling when handling changes detected from container {ContainerName}", containerName); return Task.CompletedTask; } diff --git a/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProvider.cs b/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProvider.cs index fffedd7e6..9ac2bf152 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProvider.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/ChangeFeed/DefaultEventSourcingProvider.cs @@ -6,13 +6,8 @@ namespace Microsoft.Azure.CosmosEventSourcing.ChangeFeed; -internal class DefaultEventSourcingProvider : IChangeFeedContainerProcessorProvider +internal class DefaultEventSourcingProvider(IEnumerable processors) : IChangeFeedContainerProcessorProvider { - private readonly IEnumerable _processors; - - public DefaultEventSourcingProvider(IEnumerable processors) => - _processors = processors; - public IEnumerable GetProcessors() => - _processors; + processors; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/DomainEventsRequiredException.cs b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/DomainEventsRequiredException.cs index 94bed10d1..7db0c152d 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/DomainEventsRequiredException.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/DomainEventsRequiredException.cs @@ -10,13 +10,9 @@ namespace Microsoft.Azure.CosmosEventSourcing.Exceptions; /// An exception that is thrown when not events are provided to an . /// /// An must be provided at least one when replaying events -public class DomainEventsRequiredException : Exception +/// +/// Creates a . +/// +public class DomainEventsRequiredException(Type aggregateRootType) : Exception($"At least 1 {nameof(AtomicEvent)} must be provided for {aggregateRootType.Namespace} when replaying events") { - /// - /// Creates a . - /// - public DomainEventsRequiredException(Type aggregateRootType) : - base($"At least 1 {nameof(AtomicEvent)} must be provided for {aggregateRootType.Namespace} when replaying events") - { - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/EventItemPartitionKeyAttributeRequiredException.cs b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/EventItemPartitionKeyAttributeRequiredException.cs index a52a9fc54..87d179787 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/EventItemPartitionKeyAttributeRequiredException.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/EventItemPartitionKeyAttributeRequiredException.cs @@ -8,15 +8,10 @@ namespace Microsoft.Azure.CosmosEventSourcing.Exceptions; /// /// Exception which is thrown the PersistAsync is used with the aggregate overload and the aggregate doesn't provide the attribute /// -public class EventItemPartitionKeyAttributeRequiredException : Exception +/// +/// Ceates a new +/// +/// Type of the aggregate which had the invalid configuration. Used to build up the message +public class EventItemPartitionKeyAttributeRequiredException(Type aggregateType) : Exception($"A {nameof(EventItemPartitionKeyAttribute)} must be present on a property in {aggregateType.Name} or you must specify the partition key explicitly") { - /// - /// Ceates a new - /// - /// Type of the aggregate which had the invalid configuration. Used to build up the message - public EventItemPartitionKeyAttributeRequiredException(Type aggregateType) : - base($"A {nameof(EventItemPartitionKeyAttribute)} must be present on a property in {aggregateType.Name} or you must specify the partition key explicitly") - { - - } } diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidEventItemPartitionKeyAttributeCombinationException.cs b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidEventItemPartitionKeyAttributeCombinationException.cs index 17f97b3a3..073e1b627 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidEventItemPartitionKeyAttributeCombinationException.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidEventItemPartitionKeyAttributeCombinationException.cs @@ -8,15 +8,10 @@ namespace Microsoft.Azure.CosmosEventSourcing.Exceptions; /// /// Exception which is thrown the PersistAsync is used with the aggregate overload and the aggregate provides multiple InvalidEventItemPartitionKeyAttributeCombinationException /// -public class InvalidEventItemPartitionKeyAttributeCombinationException : Exception +/// +/// Ceates a new +/// +/// Type of the aggregate which had the invalid configuration. Used to build up the message +public class InvalidEventItemPartitionKeyAttributeCombinationException(Type aggregateType) : Exception($"{nameof(EventItemPartitionKeyAttribute)} can not be present on multiple properties in {aggregateType.Name}") { - /// - /// Ceates a new - /// - /// Type of the aggregate which had the invalid configuration. Used to build up the message - public InvalidEventItemPartitionKeyAttributeCombinationException(Type aggregateType) : - base($"{nameof(EventItemPartitionKeyAttribute)} can not be present on multiple properties in {aggregateType.Name}") - { - - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidPartitionKeyValueException.cs b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidPartitionKeyValueException.cs index 68dcb780a..83ef404a8 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidPartitionKeyValueException.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/InvalidPartitionKeyValueException.cs @@ -6,17 +6,12 @@ namespace Microsoft.Azure.CosmosEventSourcing.Exceptions; /// /// Thrown when the value of the partition key is null /// -public class InvalidPartitionKeyValueException : Exception +/// +/// Ceates a new +/// +/// The name of the property that was null +/// Type of the aggregate which had the invalid configuration. Used to build up the message +public class InvalidPartitionKeyValueException(string propertyName, Type aggregateType) : Exception( + $"{propertyName} in {aggregateType.Name} was null. This is not a valid partition key value") { - /// - /// Ceates a new - /// - /// The name of the property that was null - /// Type of the aggregate which had the invalid configuration. Used to build up the message - public InvalidPartitionKeyValueException(string propertyName, Type aggregateType) : - base( - $"{propertyName} in {aggregateType.Name} was null. This is not a valid partition key value") - { - - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/ReplayMethodNotDefinedException.cs b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/ReplayMethodNotDefinedException.cs index 18ddfc5e7..2abb3f65b 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/ReplayMethodNotDefinedException.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Exceptions/ReplayMethodNotDefinedException.cs @@ -9,14 +9,9 @@ namespace Microsoft.Azure.CosmosEventSourcing.Exceptions; /// /// Thrown when using the _eventStore.ReadAggregateAsync{TAggregateRoot} method and there is not static method named Replay defined. /// -public class ReplayMethodNotDefinedException : Exception +/// +/// Creates an +/// +public class ReplayMethodNotDefinedException(MemberInfo aggregateType) : Exception($"The {nameof(IAggregateRoot)} of type {aggregateType.Name} does not have a public static TAggregateRoot Replay(List events) method defined") { - /// - /// Creates an - /// - public ReplayMethodNotDefinedException(MemberInfo aggregateType) : - base($"The {nameof(IAggregateRoot)} of type {aggregateType.Name} does not have a public static TAggregateRoot Replay(List events) method defined") - { - - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Projections/Decorators/DeadLetterProjectionDecorator.cs b/src/Microsoft.Azure.CosmosEventSourcing/Projections/Decorators/DeadLetterProjectionDecorator.cs index d7223531f..64c5a199c 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Projections/Decorators/DeadLetterProjectionDecorator.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Projections/Decorators/DeadLetterProjectionDecorator.cs @@ -9,37 +9,22 @@ namespace Microsoft.Azure.CosmosEventSourcing.Projections.Decorators; -internal class DeadLetterProjectionDecorator : +internal class DeadLetterProjectionDecorator( + ILogger> logger, + EventSourcingProcessorOptions processorOptions, + IEventItemProjection inner, + DeadLetterOptions options, + IWriteOnlyRepository> repository) : IEventItemProjection where TEventItem : EventItem where TProjectionKey : IProjectionKey { - private readonly ILogger> _logger; - private readonly EventSourcingProcessorOptions _processorOptions; - private readonly IEventItemProjection _inner; - private readonly DeadLetterOptions _options; - private readonly IWriteOnlyRepository> _repository; - - public DeadLetterProjectionDecorator( - ILogger> logger, - EventSourcingProcessorOptions processorOptions, - IEventItemProjection inner, - DeadLetterOptions options, - IWriteOnlyRepository> repository) - { - _logger = logger; - _processorOptions = processorOptions; - _inner = inner; - _options = options; - _repository = repository; - } - public async ValueTask ProjectAsync(TEventItem eventItem, CancellationToken cancellationToken = default) { try { - await _inner.ProjectAsync(eventItem, cancellationToken); + await inner.ProjectAsync(eventItem, cancellationToken); } catch (Exception e) { @@ -49,14 +34,17 @@ public async ValueTask ProjectAsync(TEventItem eventItem, CancellationToken canc private async Task HandleFailedProjectionAsync(TEventItem eventItem, Exception exception) { - _logger.LogWarning("Writing dead lettered event item for event {EventId} with name {EventName}", + // Avoid Error CS9113 Parameter 'options' is unread. + _ = options; + + logger.LogWarning("Writing dead lettered event item for event {EventId} with name {EventName}", eventItem.Id, eventItem.EventName); DeadLetteredEventItem deadLetteredItem = new( eventItem, - _processorOptions.ProcessorName, - _processorOptions.InstanceName, + processorOptions.ProcessorName, + processorOptions.InstanceName, typeof(TProjectionKey).Name, new ExceptionDetails( exception.GetType().Name, @@ -64,11 +52,11 @@ private async Task HandleFailedProjectionAsync(TEventItem eventItem, Exception e try { - await _repository.CreateAsync(deadLetteredItem); + await repository.CreateAsync(deadLetteredItem); } catch (Exception e) { - _logger.LogError(e, "Failed to save dead lettered event item for event {EventId} with name {EventName}", + logger.LogError(e, "Failed to save dead lettered event item for event {EventId} with name {EventName}", eventItem.Id, eventItem.EventName); throw; diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Projections/DefaultDomainEventProjection.cs b/src/Microsoft.Azure.CosmosEventSourcing/Projections/DefaultDomainEventProjection.cs index eee5d9182..373505b93 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Projections/DefaultDomainEventProjection.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Projections/DefaultDomainEventProjection.cs @@ -9,32 +9,23 @@ namespace Microsoft.Azure.CosmosEventSourcing.Projections; internal class - DefaultDomainEventProjection : IEventItemProjection + DefaultDomainEventProjection( + ILogger> logger, + IServiceProvider serviceProvider) : IEventItemProjection where TEventItem : EventItem where TProjectionKey : IProjectionKey { - private readonly ILogger> _logger; - private readonly IServiceProvider _serviceProvider; - - public DefaultDomainEventProjection( - ILogger> logger, - IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - public async ValueTask ProjectAsync(TEventItem eventItem, CancellationToken cancellationToken = default) { var payloadTypeName = eventItem.DomainEvent.GetType().Name; Type handlerType = BuildEventProjectionHandlerType(eventItem); - IEnumerable handlers = _serviceProvider.GetServices(handlerType).ToList(); + IEnumerable handlers = serviceProvider.GetServices(handlerType).ToList(); if (handlers.Any() is false) { if (eventItem.DomainEvent is NonDeserializableEvent nonDeserializableEvent) { - _logger.LogError( + logger.LogError( "The event with name {EventName} could not be deserialized as it was not registered with the custom deserializer payload = {EventPayload}", nonDeserializableEvent.Name, nonDeserializableEvent.Payload.ToString()); @@ -43,7 +34,7 @@ public async ValueTask ProjectAsync(TEventItem eventItem, CancellationToken canc if (payloadTypeName is not nameof(AtomicEvent)) { - _logger.LogDebug("No IDomainEventProjection<{EventType}> found", + logger.LogDebug("No IDomainEventProjection<{EventType}> found", payloadTypeName); } @@ -64,7 +55,7 @@ public async ValueTask ProjectAsync(TEventItem eventItem, CancellationToken canc } catch (Exception e) { - _logger.LogError(e, + logger.LogError(e, "Failed when handling event {PersistedEventType} with {EventProjectionHandlerType}", payloadTypeName, handler?.GetType().Name); } diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Def.cs b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Def.cs index a93c75427..ffa543420 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Def.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Def.cs @@ -7,20 +7,10 @@ namespace Microsoft.Azure.CosmosEventSourcing.Stores; -internal partial class DefaultEventStore : +internal partial class DefaultEventStore( + IBatchRepository batchRepository, + IReadOnlyRepository readOnlyRepository, + IContextService contextService) : IEventStore where TEventItem : EventItem { - private readonly IBatchRepository _batchRepository; - private readonly IReadOnlyRepository _readOnlyRepository; - private readonly IContextService _contextService; - - public DefaultEventStore( - IBatchRepository batchRepository, - IReadOnlyRepository readOnlyRepository, - IContextService contextService) - { - _batchRepository = batchRepository; - _readOnlyRepository = readOnlyRepository; - _contextService = contextService; - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs index b92cc3fab..e88f78510 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs @@ -15,7 +15,7 @@ internal partial class DefaultEventStore { public ValueTask> ReadAsync(string partitionKey, CancellationToken cancellationToken = default) => - _readOnlyRepository.GetAsync( + readOnlyRepository.GetAsync( x => x.PartitionKey == partitionKey, cancellationToken); @@ -24,7 +24,7 @@ internal partial class DefaultEventStore CancellationToken cancellationToken = default) where TAggregateRoot : IAggregateRoot { - IEnumerable events = await _readOnlyRepository.GetAsync( + IEnumerable events = await readOnlyRepository.GetAsync( x => x.PartitionKey == partitionKey, cancellationToken); @@ -45,7 +45,7 @@ internal partial class DefaultEventStore IAggregateRootMapper rootMapper, CancellationToken cancellationToken = default) where TAggregateRoot : IAggregateRoot { - IEnumerable events = await _readOnlyRepository.GetAsync( + IEnumerable events = await readOnlyRepository.GetAsync( x => x.PartitionKey == partitionKey, cancellationToken); @@ -56,7 +56,7 @@ internal partial class DefaultEventStore string partitionKey, Expression> predicate, CancellationToken cancellationToken = default) => - _readOnlyRepository.GetAsync( + readOnlyRepository.GetAsync( predicate.Compose( x => x.PartitionKey == partitionKey, Expression.AndAlso), @@ -74,7 +74,7 @@ internal partial class DefaultEventStore do { - IPage page = await _readOnlyRepository.PageAsync( + IPage page = await readOnlyRepository.PageAsync( expression, chunkSize, token, diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Write.cs b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Write.cs index a5c5806d4..26ec98b4b 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Write.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Write.cs @@ -27,8 +27,8 @@ internal partial class DefaultEventStore throw new AtomicEventRequiredException(); } - await _batchRepository.UpdateAsBatchAsync( - SetCorrelationId(_contextService, eventItems), + await batchRepository.UpdateAsBatchAsync( + SetCorrelationId(contextService, eventItems), cancellationToken); } @@ -45,8 +45,8 @@ internal partial class DefaultEventStore IAggregateRootMapper mapper, CancellationToken cancellationToken = default) where TAggregateRoot : IAggregateRoot => - _batchRepository.UpdateAsBatchAsync( - SetCorrelationId(_contextService, mapper.MapFrom(aggregateRoot)), + batchRepository.UpdateAsBatchAsync( + SetCorrelationId(contextService, mapper.MapFrom(aggregateRoot)), cancellationToken); public async ValueTask PersistAsync( @@ -54,7 +54,7 @@ internal partial class DefaultEventStore string partitionKeyValue, CancellationToken cancellationToken = default) => await PersistAsync( - SetCorrelationId(_contextService, BuildEvents(aggregateRoot, partitionKeyValue)), + SetCorrelationId(contextService, BuildEvents(aggregateRoot, partitionKeyValue)), cancellationToken); private static IEnumerable SetCorrelationId( diff --git a/src/Microsoft.Azure.CosmosRepository.AspNetCore/CosmosRepositoryChangeFeedHostedService.cs b/src/Microsoft.Azure.CosmosRepository.AspNetCore/CosmosRepositoryChangeFeedHostedService.cs index c0203a57f..152a13a5d 100644 --- a/src/Microsoft.Azure.CosmosRepository.AspNetCore/CosmosRepositoryChangeFeedHostedService.cs +++ b/src/Microsoft.Azure.CosmosRepository.AspNetCore/CosmosRepositoryChangeFeedHostedService.cs @@ -11,16 +11,12 @@ namespace Microsoft.Azure.CosmosRepository.AspNetCore; /// /// A to start and stop the /// -public class CosmosRepositoryChangeFeedHostedService : BackgroundService +/// +/// Creates an instance of the +/// +/// The to start. +public class CosmosRepositoryChangeFeedHostedService(IChangeFeedService changeFeedService) : BackgroundService { - private readonly IChangeFeedService _changeFeedService; - - /// - /// Creates an instance of the - /// - /// The to start. - public CosmosRepositoryChangeFeedHostedService(IChangeFeedService changeFeedService) => - _changeFeedService = changeFeedService; /// /// Start's the @@ -28,5 +24,5 @@ public class CosmosRepositoryChangeFeedHostedService : BackgroundService /// /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) => - await _changeFeedService.StartAsync(stoppingToken); + await changeFeedService.StartAsync(stoppingToken); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/ContainerAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/ContainerAttribute.cs index b85632852..1c35f6b8c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/ContainerAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/ContainerAttribute.cs @@ -8,18 +8,15 @@ namespace Microsoft.Azure.CosmosRepository.Attributes; /// specify an 's container name. This attribute can only be used when /// is set to true. /// +/// +/// Constructor accepting the of the container for a given . +/// +/// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public sealed class ContainerAttribute : Attribute +public sealed class ContainerAttribute(string name) : Attribute { /// /// Gets the path of the parition key. /// - public string Name { get; } - - /// - /// Constructor accepting the of the container for a given . - /// - /// - public ContainerAttribute(string name) => - Name = name ?? throw new ArgumentNullException(nameof(name), "A name is required."); + public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name), "A name is required."); } diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs index 55e5d101e..97123322e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs @@ -14,18 +14,15 @@ namespace Microsoft.Azure.CosmosRepository.Attributes; /// /// By default, "/id" is used. /// +/// +/// Constructor accepting the of the partition key for a given . +/// +/// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public sealed class PartitionKeyPathAttribute : Attribute +public sealed class PartitionKeyPathAttribute(string path) : Attribute { /// /// Gets the path of the parition key. /// - public string Path { get; } = "/id"; - - /// - /// Constructor accepting the of the partition key for a given . - /// - /// - public PartitionKeyPathAttribute(string path) => - Path = path ?? throw new ArgumentNullException(nameof(path), "A path is required."); + public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path), "A path is required."); } diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/UniqueKeyAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/UniqueKeyAttribute.cs index 6bff7ad69..72961adb7 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/UniqueKeyAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/UniqueKeyAttribute.cs @@ -8,29 +8,23 @@ namespace Microsoft.Azure.CosmosRepository.Attributes; /// specify an 's properties that can contribute to a unique key constraint. /// For more information, see https://docs.microsoft.com/azure/cosmos-db/unique-keys. /// +/// +/// Constructor accepting the for a given 's property. +/// +/// +/// The property path to match for the constraint +/// If the propertyPath is null the name of the property this is defined will be used. [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] -public sealed class UniqueKeyAttribute : Attribute +public sealed class UniqueKeyAttribute(string? keyName = null, string? propertyPath = null) : Attribute { /// /// Gets the key name that represents the unique key. /// /// This is the unique name to match a set of paths on - public string KeyName { get; } + public string KeyName { get; } = keyName ?? "onlyUniqueKey"; /// /// The property path to use for the key /// - public string? PropertyPath { get; set; } - - /// - /// Constructor accepting the for a given 's property. - /// - /// - /// The property path to match for the constraint - /// If the propertyPath is null the name of the property this is defined will be used. - public UniqueKeyAttribute(string? keyName = null, string? propertyPath = null) - { - KeyName = keyName ?? "onlyUniqueKey"; - PropertyPath = propertyPath; - } + public string? PropertyPath { get; set; } = propertyPath; } diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs index 8127a0fee..257162b7b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs @@ -6,19 +6,16 @@ namespace Microsoft.Azure.CosmosRepository.Builders; /// /// Options for a container /// -public class ContainerOptionsBuilder +/// +/// Creates an instance of . +/// +/// The type of the options are for. +public class ContainerOptionsBuilder(Type type) { /// /// The type the container options are for /// - internal Type Type { get; } - - /// - /// Creates an instance of . - /// - /// The type of the options are for. - public ContainerOptionsBuilder(Type type) => - Type = type; + internal Type Type { get; } = type; /// /// Name of the container. diff --git a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/DefaultChangeFeedService.cs b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/DefaultChangeFeedService.cs index c2a195baa..c5cb16ae5 100644 --- a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/DefaultChangeFeedService.cs +++ b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/DefaultChangeFeedService.cs @@ -3,20 +3,13 @@ namespace Microsoft.Azure.CosmosRepository.ChangeFeed; -class DefaultChangeFeedService : IChangeFeedService +class DefaultChangeFeedService(IEnumerable changeFeedContainerProcessorProvider) : IChangeFeedService { - private readonly IEnumerable _changeFeedContainerProcessorProvider; - private IEnumerable _processors; - - public DefaultChangeFeedService(IEnumerable changeFeedContainerProcessorProvider) - { - _processors = new List(); - _changeFeedContainerProcessorProvider = changeFeedContainerProcessorProvider; - } + private IEnumerable _processors = new List(); public Task StartAsync(CancellationToken cancellationToken) { - _processors = _changeFeedContainerProcessorProvider.SelectMany(x => x.GetProcessors()); + _processors = changeFeedContainerProcessorProvider.SelectMany(x => x.GetProcessors()); using CancellationTokenRegistration registration = cancellationToken.Register(() => StopAsync().Wait(TimeSpan.FromSeconds(5))); diff --git a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/InMemory/InMemoryChangeFeed.cs b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/InMemory/InMemoryChangeFeed.cs index 1b8c606e2..ecee759f1 100644 --- a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/InMemory/InMemoryChangeFeed.cs +++ b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/InMemory/InMemoryChangeFeed.cs @@ -6,22 +6,14 @@ namespace Microsoft.Azure.CosmosRepository.ChangeFeed.InMemory; /// /// Represents a change feed for a given /// -public class InMemoryChangeFeed where TItem : class, IItem +/// +/// Creates an instance of an +/// +/// The instance of an in which listen to changes +/// The processor that will be invoked when changes occur +public class InMemoryChangeFeed(IRepository repository, + IEnumerable> changeFeedProcessors) where TItem : class, IItem { - private readonly IRepository _repository; - private readonly IEnumerable> _changeFeedProcessors; - - /// - /// Creates an instance of an - /// - /// The instance of an in which listen to changes - /// The processor that will be invoked when changes occur - public InMemoryChangeFeed(IRepository repository, - IEnumerable> changeFeedProcessors) - { - _repository = repository; - _changeFeedProcessors = changeFeedProcessors; - } /// /// Set's up the to start listen to changes from the given @@ -29,13 +21,13 @@ public class InMemoryChangeFeed where TItem : class, IItem /// Thrown when the registered in DI is not a public void Setup() { - if (_repository is InMemoryRepository inMemoryRepository) + if (repository is InMemoryRepository inMemoryRepository) { inMemoryRepository.Changes += (args) => { foreach (TItem changes in args.ItemChanges) { - foreach (IItemChangeFeedProcessor itemChangeFeedProcessor in _changeFeedProcessors) + foreach (IItemChangeFeedProcessor itemChangeFeedProcessor in changeFeedProcessors) { itemChangeFeedProcessor.HandleAsync(changes, default).AsTask().Wait(); } @@ -45,7 +37,7 @@ public void Setup() else { throw new InvalidOperationException( - $"A repository of type {_repository.GetType().Name} cannot be setup to work with the InMemoryChangeFeed"); + $"A repository of type {repository.GetType().Name} cannot be setup to work with the InMemoryChangeFeed"); } } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedContainerProcessorProvider.cs b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedContainerProcessorProvider.cs index 063c25df7..1374e8ff0 100644 --- a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedContainerProcessorProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedContainerProcessorProvider.cs @@ -3,34 +3,17 @@ namespace Microsoft.Azure.CosmosRepository.ChangeFeed.Providers; -internal class DefaultChangeFeedContainerProcessorProvider : IChangeFeedContainerProcessorProvider +internal class DefaultChangeFeedContainerProcessorProvider( + IOptionsMonitor optionsMonitor, + ICosmosContainerService containerService, + ILeaseContainerProvider leaseContainerProvider, + ILoggerFactory loggerFactory, + IServiceProvider serviceProvider, + IChangeFeedOptionsProvider changeFeedOptionsProvider) : IChangeFeedContainerProcessorProvider { - private readonly IOptionsMonitor _optionsMonitor; - private readonly ICosmosContainerService _containerService; - private readonly ILeaseContainerProvider _leaseContainerProvider; - private readonly ILoggerFactory _loggerFactory; - private readonly IServiceProvider _serviceProvider; - private readonly IChangeFeedOptionsProvider _changeFeedOptionsProvider; - - public DefaultChangeFeedContainerProcessorProvider( - IOptionsMonitor optionsMonitor, - ICosmosContainerService containerService, - ILeaseContainerProvider leaseContainerProvider, - ILoggerFactory loggerFactory, - IServiceProvider serviceProvider, - IChangeFeedOptionsProvider changeFeedOptionsProvider) - { - _optionsMonitor = optionsMonitor; - _containerService = containerService; - _leaseContainerProvider = leaseContainerProvider; - _loggerFactory = loggerFactory; - _serviceProvider = serviceProvider; - _changeFeedOptionsProvider = changeFeedOptionsProvider; - } - public IEnumerable GetProcessors() { - RepositoryOptions repositoryOptions = _optionsMonitor.CurrentValue; + RepositoryOptions repositoryOptions = optionsMonitor.CurrentValue; IEnumerable> containers = repositoryOptions .ContainerBuilder @@ -44,12 +27,12 @@ public IEnumerable GetProcessors() var itemTypes = container.Select(x => x.Type).ToList(); yield return new DefaultContainerChangeFeedProcessor( - _containerService, + containerService, itemTypes, - _leaseContainerProvider, - _changeFeedOptionsProvider.GetOptionsForItems(itemTypes), - _loggerFactory.CreateLogger(), - _serviceProvider); + leaseContainerProvider, + changeFeedOptionsProvider.GetOptionsForItems(itemTypes), + loggerFactory.CreateLogger(), + serviceProvider); } } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedOptionsProvider.cs b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedOptionsProvider.cs index f94878792..fa2dc77ff 100644 --- a/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedOptionsProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/ChangeFeed/Providers/DefaultChangeFeedOptionsProvider.cs @@ -3,12 +3,9 @@ namespace Microsoft.Azure.CosmosRepository.ChangeFeed.Providers; -class DefaultChangeFeedOptionsProvider : IChangeFeedOptionsProvider +class DefaultChangeFeedOptionsProvider(IOptionsMonitor optionsMonitor) : IChangeFeedOptionsProvider { - private readonly RepositoryOptions _repositoryOptions; - - public DefaultChangeFeedOptionsProvider(IOptionsMonitor optionsMonitor) => - _repositoryOptions = optionsMonitor.CurrentValue; + private readonly RepositoryOptions _repositoryOptions = optionsMonitor.CurrentValue; public ChangeFeedOptions GetOptionsForItems(IReadOnlyList items) { diff --git a/src/Microsoft.Azure.CosmosRepository/Exceptions/BatchOperationException.cs b/src/Microsoft.Azure.CosmosRepository/Exceptions/BatchOperationException.cs index 516f06f9d..a08066cd2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Exceptions/BatchOperationException.cs +++ b/src/Microsoft.Azure.CosmosRepository/Exceptions/BatchOperationException.cs @@ -7,26 +7,21 @@ namespace Microsoft.Azure.CosmosRepository.Exceptions; /// Details an error when performing a batch operation for a given TItem /// /// -public class BatchOperationException : Exception +/// +/// Creates +/// +/// +public class BatchOperationException(TransactionalBatchResponse response) : Exception( + $"Failed to execute the batch operation for {typeof(TItem).Name}") where TItem : IItem { /// /// The response from the batch operation. /// - public TransactionalBatchResponse Response { get; } + public TransactionalBatchResponse Response { get; } = response; /// /// The status code return from the /// public HttpStatusCode StatusCode => Response.StatusCode; - - /// - /// Creates - /// - /// - public BatchOperationException(TransactionalBatchResponse response) : base( - $"Failed to execute the batch operation for {typeof(TItem).Name}") - { - Response = response; - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Exceptions/InvalidEtagConfigurationException.cs b/src/Microsoft.Azure.CosmosRepository/Exceptions/InvalidEtagConfigurationException.cs index df4ae339f..58afb16db 100644 --- a/src/Microsoft.Azure.CosmosRepository/Exceptions/InvalidEtagConfigurationException.cs +++ b/src/Microsoft.Azure.CosmosRepository/Exceptions/InvalidEtagConfigurationException.cs @@ -9,13 +9,10 @@ namespace Microsoft.Azure.CosmosRepository.Exceptions; /// Please ensure your Item implementation implements IItemWithEtag. /// /// -public class InvalidEtagConfigurationException : Exception +/// +/// Constructor specifying the message to set in the exception. +/// +/// The message for the exception. +public class InvalidEtagConfigurationException(string message) : Exception(message) { - /// - /// Constructor specifying the message to set in the exception. - /// - /// The message for the exception. - public InvalidEtagConfigurationException(string message) : base(message) - { - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedChangeFeedOptionsException.cs b/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedChangeFeedOptionsException.cs index b3c230597..b3c21903b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedChangeFeedOptionsException.cs +++ b/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedChangeFeedOptionsException.cs @@ -6,20 +6,15 @@ namespace Microsoft.Azure.CosmosRepository.Exceptions; /// /// An exception stating that 's sharing a container have configured different change feed options. /// -public class MissMatchedChangeFeedOptionsException : Exception +/// +/// Creates a +/// +/// The message detailing the miss match. +/// The types of 's that are miss matched. +public class MissMatchedChangeFeedOptionsException(string message, IReadOnlyList itemTypes) : Exception(message) { /// /// The types of 's which are sharing a container. /// - public IReadOnlyList ItemTypes { get; } - - /// - /// Creates a - /// - /// The message detailing the miss match. - /// The types of 's that are miss matched. - public MissMatchedChangeFeedOptionsException(string message, IReadOnlyList itemTypes) : base(message) - { - ItemTypes = itemTypes; - } + public IReadOnlyList ItemTypes { get; } = itemTypes; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedTypeDiscriminatorException.cs b/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedTypeDiscriminatorException.cs index 13e5cf86a..a3ad2a53b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedTypeDiscriminatorException.cs +++ b/src/Microsoft.Azure.CosmosRepository/Exceptions/MissMatchedTypeDiscriminatorException.cs @@ -6,29 +6,23 @@ namespace Microsoft.Azure.CosmosRepository.Exceptions; /// /// This exception is thrown when the type field does not match the 's type. /// -public class MissMatchedTypeDiscriminatorException : Exception +/// +/// Creates a +/// +/// The current type on the +/// The expected type of the +public class MissMatchedTypeDiscriminatorException( + string type, + string expectedType) : Exception( + $"The IItem has the type discriminator of {type} and it expected {expectedType}") { /// /// The current type that was present on the /// - public string Type { get; } + public string Type { get; } = type; /// /// The expected type value /// - public string ExpectedType { get; } - - /// - /// Creates a - /// - /// The current type on the - /// The expected type of the - public MissMatchedTypeDiscriminatorException( - string type, - string expectedType) : base( - $"The IItem has the type discriminator of {type} and it expected {expectedType}") - { - Type = type; - ExpectedType = expectedType; - } + public string ExpectedType { get; } = expectedType; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Factories/DefaultRepositoryFactory.cs b/src/Microsoft.Azure.CosmosRepository/Factories/DefaultRepositoryFactory.cs index da0724698..5918dd122 100644 --- a/src/Microsoft.Azure.CosmosRepository/Factories/DefaultRepositoryFactory.cs +++ b/src/Microsoft.Azure.CosmosRepository/Factories/DefaultRepositoryFactory.cs @@ -4,19 +4,15 @@ namespace Microsoft.Azure.CosmosRepository; /// -class DefaultRepositoryFactory : IRepositoryFactory +/// +/// Constructor for the default repository factory. +/// +/// +class DefaultRepositoryFactory(IServiceProvider serviceProvider) : IRepositoryFactory { - readonly IServiceProvider _serviceProvider; - - /// - /// Constructor for the default repository factory. - /// - /// - public DefaultRepositoryFactory(IServiceProvider serviceProvider) => - _serviceProvider = serviceProvider; /// public IRepository RepositoryOf() where TItem : class, IItem => - _serviceProvider.GetRequiredService>(); + serviceProvider.GetRequiredService>(); } diff --git a/src/Microsoft.Azure.CosmosRepository/Factories/IRepositoryFactory.cs b/src/Microsoft.Azure.CosmosRepository/Factories/IRepositoryFactory.cs index 267fe506a..af3744ee8 100644 --- a/src/Microsoft.Azure.CosmosRepository/Factories/IRepositoryFactory.cs +++ b/src/Microsoft.Azure.CosmosRepository/Factories/IRepositoryFactory.cs @@ -13,7 +13,7 @@ public interface IRepositoryFactory /// Gets an instance for the /// given type. /// - /// The item type that corresponds to the respoitory. + /// The item type that corresponds to the repository. /// An of . IRepository RepositoryOf() where TItem : class, IItem; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs b/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs index d936a63b3..a4a53606b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs +++ b/src/Microsoft.Azure.CosmosRepository/Internals/InternalPatchOperation.cs @@ -3,17 +3,10 @@ namespace Microsoft.Azure.CosmosRepository.Internals; -internal class InternalPatchOperation +internal class InternalPatchOperation(PropertyInfo propertyInfo, object? newValue, PatchOperationType type) { - public PatchOperationType Type { get; } - public PropertyInfo PropertyInfo { get; } + public PatchOperationType Type { get; } = type; + public PropertyInfo PropertyInfo { get; } = propertyInfo; - public object? NewValue { get; } - - public InternalPatchOperation(PropertyInfo propertyInfo, object? newValue, PatchOperationType type) - { - PropertyInfo = propertyInfo; - NewValue = newValue; - Type = type; - } + public object? NewValue { get; } = newValue; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs index af1a9b64d..0b52ec62a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs @@ -3,45 +3,32 @@ namespace Microsoft.Azure.CosmosRepository.Options; -internal class ItemConfiguration +internal class ItemConfiguration( + Type type, + string containerName, + string partitionKeyPath, + UniqueKeyPolicy? uniqueKeyPolicy, + ThroughputProperties? throughputProperties, + int defaultTimeToLive = -1, + bool syncContainerProperties = false, + ChangeFeedOptions? changeFeedOptions = null, + bool useStrictTypeChecking = true) { - public Type Type { get; } + public Type Type { get; } = type; - public string ContainerName { get; } + public string ContainerName { get; } = containerName; - public string PartitionKeyPath { get; } + public string PartitionKeyPath { get; } = partitionKeyPath; - public UniqueKeyPolicy? UniqueKeyPolicy { get; } + public UniqueKeyPolicy? UniqueKeyPolicy { get; } = uniqueKeyPolicy; - public ThroughputProperties? ThroughputProperties { get; } + public ThroughputProperties? ThroughputProperties { get; } = throughputProperties; - public int DefaultTimeToLive { get; } + public int DefaultTimeToLive { get; } = defaultTimeToLive; - public bool SyncContainerProperties { get; } + public bool SyncContainerProperties { get; } = syncContainerProperties; - public ChangeFeedOptions? ChangeFeedOptions { get; } + public ChangeFeedOptions? ChangeFeedOptions { get; } = changeFeedOptions; - public bool UseStrictTypeChecking { get; } - - public ItemConfiguration( - Type type, - string containerName, - string partitionKeyPath, - UniqueKeyPolicy? uniqueKeyPolicy, - ThroughputProperties? throughputProperties, - int defaultTimeToLive = -1, - bool syncContainerProperties = false, - ChangeFeedOptions? changeFeedOptions = null, - bool useStrictTypeChecking = true) - { - Type = type; - ContainerName = containerName; - PartitionKeyPath = partitionKeyPath; - UniqueKeyPolicy = uniqueKeyPolicy; - ThroughputProperties = throughputProperties; - UseStrictTypeChecking = useStrictTypeChecking; - DefaultTimeToLive = defaultTimeToLive; - SyncContainerProperties = syncContainerProperties; - ChangeFeedOptions = changeFeedOptions; - } + public bool UseStrictTypeChecking { get; } = useStrictTypeChecking; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Options/RepositoryOptions.cs b/src/Microsoft.Azure.CosmosRepository/Options/RepositoryOptions.cs index 2bd5e2f9f..758d3ebd3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/RepositoryOptions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/RepositoryOptions.cs @@ -71,7 +71,6 @@ public class RepositoryOptions /// public bool ContainerPerItemType { get; set; } - /// /// Gets or sets whether optimistic batching of service requests occurs. Setting this option might /// impact the latency of the operations. Hence this option is recommended for non-latency diff --git a/src/Microsoft.Azure.CosmosRepository/Options/RepositorySerializationOptions.cs b/src/Microsoft.Azure.CosmosRepository/Options/RepositorySerializationOptions.cs index e7fde1d3d..b32b2c327 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/RepositorySerializationOptions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/RepositorySerializationOptions.cs @@ -27,4 +27,30 @@ public class RepositorySerializationOptions /// /// The default value is . public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } = CosmosPropertyNamingPolicy.CamelCase; + + /// + /// Defines an implicit operator for which allows the to be converted + /// to a . + /// + /// The source options to map from. + public static implicit operator CosmosSerializationOptions(RepositorySerializationOptions? options) => + new() + { + IgnoreNullValues = options?.IgnoreNullValues ?? false, + Indented = options?.Indented ?? false, + PropertyNamingPolicy = options?.PropertyNamingPolicy + ?? CosmosPropertyNamingPolicy.CamelCase + }; + + /// + /// Defines an implicit operator for which allows the to be converted + /// to a . + /// + /// The source options to map from. + public static implicit operator CosmosLinqSerializerOptions(RepositorySerializationOptions? options) => + new() + { + PropertyNamingPolicy = options?.PropertyNamingPolicy + ?? CosmosPropertyNamingPolicy.CamelCase + }; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Processors/DefaultCosmosQueryableProcessor.cs b/src/Microsoft.Azure.CosmosRepository/Processors/DefaultCosmosQueryableProcessor.cs index 35aead604..dc9df8c7d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Processors/DefaultCosmosQueryableProcessor.cs +++ b/src/Microsoft.Azure.CosmosRepository/Processors/DefaultCosmosQueryableProcessor.cs @@ -9,7 +9,7 @@ class DefaultCosmosQueryableProcessor : ICosmosQueryableProcessor { using var iterator = queryable.ToFeedIterator(); - List results = new(); + List results = []; double charge = 0; while (iterator.HasMoreResults) @@ -37,7 +37,7 @@ class DefaultCosmosQueryableProcessor : ICosmosQueryableProcessor { using FeedIterator queryIterator = container.GetItemQueryIterator(queryDefinition); - List results = new(); + List results = []; while (queryIterator.HasMoreResults) { diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultContainerSyncContainerPropertiesProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultContainerSyncContainerPropertiesProvider.cs index 199096d54..6367a7fb3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultContainerSyncContainerPropertiesProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultContainerSyncContainerPropertiesProvider.cs @@ -4,11 +4,8 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultContainerSyncContainerPropertiesProvider : ICosmosContainerSyncContainerPropertiesProvider +class DefaultContainerSyncContainerPropertiesProvider(IOptions options) : ICosmosContainerSyncContainerPropertiesProvider { - private readonly IOptions _options; - - public DefaultContainerSyncContainerPropertiesProvider(IOptions options) => _options = options; /// public bool GetWhetherToSyncContainerProperties() where TItem : IItem => @@ -16,7 +13,7 @@ class DefaultContainerSyncContainerPropertiesProvider : ICosmosContainerSyncCont public bool GetWhetherToSyncContainerProperties(Type itemType) { - RepositoryOptions repositoryOptions = _options.Value; + RepositoryOptions repositoryOptions = options.Value; if (repositoryOptions.SyncAllContainerProperties) { diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosClientOptionsProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosClientOptionsProvider.cs index a319cfd23..e1ed36228 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosClientOptionsProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosClientOptionsProvider.cs @@ -32,13 +32,7 @@ class DefaultCosmosClientOptionsProvider : ICosmosClientOptionsProvider CosmosClientOptions cosmosClientOptions = new() { - SerializerOptions = new() - { - IgnoreNullValues = ro.SerializationOptions?.IgnoreNullValues ?? false, - Indented = ro.SerializationOptions?.Indented ?? false, - PropertyNamingPolicy = ro.SerializationOptions?.PropertyNamingPolicy - ?? CosmosPropertyNamingPolicy.CamelCase - }, + SerializerOptions = ro.SerializationOptions, HttpClientFactory = () => ClientFactory(serviceProvider), AllowBulkExecution = ro.AllowBulkExecution }; diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerDefaultTimeToLiveProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerDefaultTimeToLiveProvider.cs index bd2dd5241..58875b2d5 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerDefaultTimeToLiveProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerDefaultTimeToLiveProvider.cs @@ -4,15 +4,13 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultCosmosContainerDefaultTimeToLiveProvider : ICosmosContainerDefaultTimeToLiveProvider +/// +/// Creates an instance of the . +/// +/// The repository options. +class DefaultCosmosContainerDefaultTimeToLiveProvider(IOptions options) : ICosmosContainerDefaultTimeToLiveProvider { - private readonly IOptions _options; - - /// - /// Creates an instance of the . - /// - /// The repository options. - public DefaultCosmosContainerDefaultTimeToLiveProvider(IOptions options) => _options = options; + private readonly IOptions _options = options; /// public int GetDefaultTimeToLive() where TItem : IItem => diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerNameProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerNameProvider.cs index f6cd6ea13..f32ea190f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerNameProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerNameProvider.cs @@ -4,14 +4,9 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultCosmosContainerNameProvider : ICosmosContainerNameProvider +class DefaultCosmosContainerNameProvider(IOptions options) : ICosmosContainerNameProvider { - private readonly IOptions _options; - - public DefaultCosmosContainerNameProvider(IOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } + private readonly IOptions _options = options ?? throw new ArgumentNullException(nameof(options)); /// public string GetContainerName() where TItem : IItem => diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerProvider.cs index bcd9d182f..37f201427 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosContainerProvider.cs @@ -4,13 +4,10 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultCosmosContainerProvider +class DefaultCosmosContainerProvider(ICosmosContainerService containerService) : ICosmosContainerProvider where TItem : IItem { - readonly Lazy> _lazyContainer; - - public DefaultCosmosContainerProvider(ICosmosContainerService containerService) => - _lazyContainer = new Lazy>(async () => await containerService.GetContainerAsync()); + readonly Lazy> _lazyContainer = new(async () => await containerService.GetContainerAsync()); /// public Task GetContainerAsync() => _lazyContainer.Value; diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs index d9a0e87a5..f2e0f1650 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs @@ -3,36 +3,17 @@ namespace Microsoft.Azure.CosmosRepository.Providers; -class DefaultCosmosItemConfigurationProvider : ICosmosItemConfigurationProvider +class DefaultCosmosItemConfigurationProvider( + ICosmosContainerNameProvider containerNameProvider, + ICosmosPartitionKeyPathProvider cosmosPartitionKeyPathProvider, + ICosmosUniqueKeyPolicyProvider cosmosUniqueKeyPolicyProvider, + ICosmosContainerDefaultTimeToLiveProvider containerDefaultTimeToLiveProvider, + ICosmosContainerSyncContainerPropertiesProvider syncContainerPropertiesProvider, + ICosmosThroughputProvider cosmosThroughputProvider, + ICosmosStrictTypeCheckingProvider cosmosStrictTypeCheckingProvider) : ICosmosItemConfigurationProvider { private static readonly ConcurrentDictionary _itemOptionsMap = new(); - private readonly ICosmosContainerNameProvider _containerNameProvider; - private readonly ICosmosPartitionKeyPathProvider _cosmosPartitionKeyPathProvider; - private readonly ICosmosUniqueKeyPolicyProvider _cosmosUniqueKeyPolicyProvider; - private readonly ICosmosContainerDefaultTimeToLiveProvider _containerDefaultTimeToLiveProvider; - private readonly ICosmosContainerSyncContainerPropertiesProvider _syncContainerPropertiesProvider; - private readonly ICosmosThroughputProvider _cosmosThroughputProvider; - private readonly ICosmosStrictTypeCheckingProvider _cosmosStrictTypeCheckingProvider; - - public DefaultCosmosItemConfigurationProvider( - ICosmosContainerNameProvider containerNameProvider, - ICosmosPartitionKeyPathProvider cosmosPartitionKeyPathProvider, - ICosmosUniqueKeyPolicyProvider cosmosUniqueKeyPolicyProvider, - ICosmosContainerDefaultTimeToLiveProvider containerDefaultTimeToLiveProvider, - ICosmosContainerSyncContainerPropertiesProvider syncContainerPropertiesProvider, - ICosmosThroughputProvider cosmosThroughputProvider, - ICosmosStrictTypeCheckingProvider cosmosStrictTypeCheckingProvider) - { - _containerNameProvider = containerNameProvider; - _cosmosPartitionKeyPathProvider = cosmosPartitionKeyPathProvider; - _cosmosUniqueKeyPolicyProvider = cosmosUniqueKeyPolicyProvider; - _containerDefaultTimeToLiveProvider = containerDefaultTimeToLiveProvider; - _syncContainerPropertiesProvider = syncContainerPropertiesProvider; - _cosmosThroughputProvider = cosmosThroughputProvider; - _cosmosStrictTypeCheckingProvider = cosmosStrictTypeCheckingProvider; - } - public ItemConfiguration GetItemConfiguration() where TItem : IItem => GetItemConfiguration(typeof(TItem)); @@ -44,13 +25,13 @@ private ItemConfiguration AddOptions(Type itemType) { itemType.IsItem(); - var containerName = _containerNameProvider.GetContainerName(itemType); - var partitionKeyPath = _cosmosPartitionKeyPathProvider.GetPartitionKeyPath(itemType); - UniqueKeyPolicy? uniqueKeyPolicy = _cosmosUniqueKeyPolicyProvider.GetUniqueKeyPolicy(itemType); - var timeToLive = _containerDefaultTimeToLiveProvider.GetDefaultTimeToLive(itemType); - var sync = _syncContainerPropertiesProvider.GetWhetherToSyncContainerProperties(itemType); - ThroughputProperties? throughputProperties = _cosmosThroughputProvider.GetThroughputProperties(itemType); - var useStrictTypeChecking = _cosmosStrictTypeCheckingProvider.UseStrictTypeChecking(itemType); + var containerName = containerNameProvider.GetContainerName(itemType); + var partitionKeyPath = cosmosPartitionKeyPathProvider.GetPartitionKeyPath(itemType); + UniqueKeyPolicy? uniqueKeyPolicy = cosmosUniqueKeyPolicyProvider.GetUniqueKeyPolicy(itemType); + var timeToLive = containerDefaultTimeToLiveProvider.GetDefaultTimeToLive(itemType); + var sync = syncContainerPropertiesProvider.GetWhetherToSyncContainerProperties(itemType); + ThroughputProperties? throughputProperties = cosmosThroughputProvider.GetThroughputProperties(itemType); + var useStrictTypeChecking = cosmosStrictTypeCheckingProvider.UseStrictTypeChecking(itemType); return new( itemType, diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs index 2145ab57d..d3e2fca60 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs @@ -4,15 +4,10 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultCosmosPartitionKeyPathProvider : +class DefaultCosmosPartitionKeyPathProvider(IOptions options) : ICosmosPartitionKeyPathProvider { - private readonly IOptions _options; - - public DefaultCosmosPartitionKeyPathProvider(IOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } + private readonly IOptions _options = options ?? throw new ArgumentNullException(nameof(options)); /// public string GetPartitionKeyPath() where TItem : IItem => diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosStrictTypeCheckingProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosStrictTypeCheckingProvider.cs index 760f2c6ac..cb5b58007 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosStrictTypeCheckingProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosStrictTypeCheckingProvider.cs @@ -3,19 +3,14 @@ namespace Microsoft.Azure.CosmosRepository.Providers; -class DefaultCosmosStrictTypeCheckingProvider : ICosmosStrictTypeCheckingProvider +class DefaultCosmosStrictTypeCheckingProvider(IOptions options) : ICosmosStrictTypeCheckingProvider { - private readonly IOptions _options; - - public DefaultCosmosStrictTypeCheckingProvider(IOptions options) => - _options = options; - public bool UseStrictTypeChecking() where TItem : IItem => UseStrictTypeChecking(typeof(TItem)); public bool UseStrictTypeChecking(Type itemType) { - ContainerOptionsBuilder? itemOptions = _options.Value.GetContainerOptions(itemType); + ContainerOptionsBuilder? itemOptions = options.Value.GetContainerOptions(itemType); return itemOptions?.UseStrictTypeChecking ?? true; } diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosThroughputProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosThroughputProvider.cs index f298f94b5..fc030f4cd 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosThroughputProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosThroughputProvider.cs @@ -4,12 +4,8 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// -class DefaultCosmosThroughputProvider : ICosmosThroughputProvider +class DefaultCosmosThroughputProvider(IOptions options) : ICosmosThroughputProvider { - readonly IOptions _options; - - public DefaultCosmosThroughputProvider(IOptions options) => - _options = options; /// public ThroughputProperties? GetThroughputProperties() where TItem : IItem => @@ -17,14 +13,14 @@ class DefaultCosmosThroughputProvider : ICosmosThroughputProvider public ThroughputProperties? GetThroughputProperties(Type itemType) { - ContainerOptionsBuilder? currentItemsOptions = _options.Value.GetContainerOptions(itemType); + ContainerOptionsBuilder? currentItemsOptions = options.Value.GetContainerOptions(itemType); if (currentItemsOptions is null) { return ThroughputProperties.CreateManualThroughput(400); } - foreach (ContainerOptionsBuilder option in _options.Value.GetContainerSharedContainerOptions(itemType)) + foreach (ContainerOptionsBuilder option in options.Value.GetContainerSharedContainerOptions(itemType)) { if (option is { ThroughputProperties: null }) { diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosUniqueKeyPolicyProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosUniqueKeyPolicyProvider.cs index 1cc30a212..e4ba2cd3c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosUniqueKeyPolicyProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosUniqueKeyPolicyProvider.cs @@ -14,7 +14,7 @@ class DefaultCosmosUniqueKeyPolicyProvider : ICosmosUniqueKeyPolicyProvider public UniqueKeyPolicy? GetUniqueKeyPolicy(Type itemType) { - Dictionary> keyNameToPathsMap = new(); + Dictionary> keyNameToPathsMap = []; foreach ((UniqueKeyAttribute? uniqueKey, var propertyName) in itemType.GetProperties() .Where(x => Attribute.IsDefined(x, s_attributeType)) @@ -31,7 +31,7 @@ class DefaultCosmosUniqueKeyPolicyProvider : ICosmosUniqueKeyPolicyProvider continue; } - keyNameToPathsMap[uniqueKey.KeyName] = new List { propertyValue }; + keyNameToPathsMap[uniqueKey.KeyName] = [propertyValue]; } if (!keyNameToPathsMap.Any()) diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultRepositoryExpressionProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultRepositoryExpressionProvider.cs index 622edf005..6f6653d35 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultRepositoryExpressionProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultRepositoryExpressionProvider.cs @@ -3,17 +3,12 @@ namespace Microsoft.Azure.CosmosRepository.Providers; -class DefaultRepositoryExpressionProvider : IRepositoryExpressionProvider +class DefaultRepositoryExpressionProvider(ICosmosItemConfigurationProvider itemConfigurationProvider) : IRepositoryExpressionProvider { - private readonly ICosmosItemConfigurationProvider _itemConfigurationProvider; - - public DefaultRepositoryExpressionProvider(ICosmosItemConfigurationProvider itemConfigurationProvider) => - _itemConfigurationProvider = itemConfigurationProvider; - public Expression> Build(Expression> predicate) where TItem : IItem { - ItemConfiguration options = _itemConfigurationProvider.GetItemConfiguration(); + ItemConfiguration options = itemConfigurationProvider.GetItemConfiguration(); return options.UseStrictTypeChecking ? predicate.Compose(Default(), Expression.AndAlso) : predicate; } @@ -23,7 +18,7 @@ class DefaultRepositoryExpressionProvider : IRepositoryExpressionProvider public TItem CheckItem(TItem item) where TItem : IItem { - ItemConfiguration options = _itemConfigurationProvider.GetItemConfiguration(); + ItemConfiguration options = itemConfigurationProvider.GetItemConfiguration(); if (options.UseStrictTypeChecking) { diff --git a/src/Microsoft.Azure.CosmosRepository/QueryResult.cs b/src/Microsoft.Azure.CosmosRepository/QueryResult.cs index 2d1702c12..afb62f7b7 100644 --- a/src/Microsoft.Azure.CosmosRepository/QueryResult.cs +++ b/src/Microsoft.Azure.CosmosRepository/QueryResult.cs @@ -7,25 +7,20 @@ namespace Microsoft.Azure.CosmosRepository; /// /// /// -public class QueryResult : IQueryResult +/// +/// +/// +/// +/// +public class QueryResult(IReadOnlyList items, double charge) : IQueryResult where T : IItem { /// /// /// - /// - /// - public QueryResult(IReadOnlyList items, double charge) - { - Items = items; - Charge = charge; - } - /// - /// - /// - public IReadOnlyList Items { get; } + public IReadOnlyList Items { get; } = items; /// /// /// - public double Charge { get; } + public double Charge { get; } = charge; } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs index 110f31c5d..467c34ca2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs @@ -15,7 +15,7 @@ internal partial class DefaultRepository var partitionKey = GetPartitionKeyValue(list); - Container container = await _containerProvider.GetContainerAsync(); + Container container = await containerProvider.GetContainerAsync(); TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); @@ -48,7 +48,7 @@ internal partial class DefaultRepository var partitionKey = GetPartitionKeyValue(list); - Container container = await _containerProvider.GetContainerAsync(); + Container container = await containerProvider.GetContainerAsync(); TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); @@ -73,7 +73,7 @@ internal partial class DefaultRepository var partitionKey = GetPartitionKeyValue(list); - Container container = await _containerProvider.GetContainerAsync(); + Container container = await containerProvider.GetContainerAsync(); TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index e93b00d00..74c1b9aef 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -12,14 +12,15 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync() + await containerProvider.GetContainerAsync() .ConfigureAwait(false); - IQueryable query = container.GetItemLinqQueryable(); + IQueryable query = container.GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); - TryLogDebugDetails(_logger, () => $"Read: {query}"); + TryLogDebugDetails(logger, () => $"Read: {query}"); - return await _cosmosQueryableProcessor.CountAsync(query, cancellationToken); + return await cosmosQueryableProcessor.CountAsync(query, cancellationToken); } private async ValueTask> CountAsync( @@ -28,14 +29,15 @@ await _containerProvider.GetContainerAsync() where TResult : IQueryResult { Container container = - await _containerProvider.GetContainerAsync() + await containerProvider.GetContainerAsync() .ConfigureAwait(false); - IQueryable query = container.GetItemLinqQueryable(); + IQueryable query = container.GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); - query = _specificationEvaluator.GetQuery(query, specification, evaluateCriteriaOnly: true); + query = specificationEvaluator.GetQuery(query, specification, evaluateCriteriaOnly: true); - TryLogDebugDetails(_logger, () => $"Read: {query}"); + TryLogDebugDetails(logger, () => $"Read: {query}"); return await query.CountAsync(cancellationToken); } @@ -45,16 +47,17 @@ await _containerProvider.GetContainerAsync() CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync() + await containerProvider.GetContainerAsync() .ConfigureAwait(false); IQueryable query = - container.GetItemLinqQueryable() - .Where(_repositoryExpressionProvider.Build(predicate)); + container.GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider.Build(predicate)); - TryLogDebugDetails(_logger, () => $"Read: {query}"); + TryLogDebugDetails(logger, () => $"Read: {query}"); - return await _cosmosQueryableProcessor.CountAsync( + return await cosmosQueryableProcessor.CountAsync( query, cancellationToken); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs index fda64dde7..3c1e0671d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs @@ -13,7 +13,7 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); if (value is IItemWithTimeStamps { CreatedTimeUtc: null } valueWithTimestamps) { @@ -25,7 +25,7 @@ internal sealed partial class DefaultRepository cancellationToken: cancellationToken) .ConfigureAwait(false); - TryLogDebugDetails(_logger, () => $"Created: {JsonConvert.SerializeObject(value)}"); + TryLogDebugDetails(logger, () => $"Created: {JsonConvert.SerializeObject(value)}"); return response.Resource; } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs index 17eab907b..f5c8bc3bc 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs @@ -27,7 +27,7 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { ItemRequestOptions options = RequestOptions.Options; - Container container = await _containerProvider.GetContainerAsync().ConfigureAwait(false); + Container container = await containerProvider.GetContainerAsync().ConfigureAwait(false); if (partitionKey == default) { @@ -37,6 +37,6 @@ internal sealed partial class DefaultRepository _ = await container.DeleteItemAsync(id, partitionKey, options, cancellationToken) .ConfigureAwait(false); - TryLogDebugDetails(_logger, () => $"Deleted: {id}"); + TryLogDebugDetails(logger, () => $"Deleted: {id}"); } } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index ca8da9bb4..bafcbdbc3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -21,7 +21,7 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); if (partitionKey == default) { @@ -47,15 +47,16 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); IQueryable query = - container.GetItemLinqQueryable() - .Where(_repositoryExpressionProvider.Build(predicate)); + container.GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider.Build(predicate)); - TryLogDebugDetails(_logger, () => $"Read: {query}"); + TryLogDebugDetails(logger, () => $"Read: {query}"); - var count = await _cosmosQueryableProcessor.CountAsync(query, cancellationToken); + var count = await cosmosQueryableProcessor.CountAsync(query, cancellationToken); return count > 0; } } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 9f4db1d73..4bd2a486f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -15,7 +15,7 @@ internal sealed partial class DefaultRepository bool returnTotal = false, CancellationToken cancellationToken = default) { - Container container = await _containerProvider.GetContainerAsync() + Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); QueryRequestOptions options = new() @@ -24,12 +24,15 @@ internal sealed partial class DefaultRepository }; IQueryable query = container - .GetItemLinqQueryable(requestOptions: options, continuationToken: continuationToken) - .Where(_repositoryExpressionProvider.Build( + .GetItemLinqQueryable( + requestOptions: options, + continuationToken: continuationToken, + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider.Build( predicate ?? - _repositoryExpressionProvider.Default())); + repositoryExpressionProvider.Default())); - _logger.LogQueryConstructed(query); + logger.LogQueryConstructed(query); Response? countResponse = null; @@ -38,17 +41,17 @@ internal sealed partial class DefaultRepository countResponse = await query.CountAsync(cancellationToken); } - (List items, var charge, var resultingContinationToken) = + (List items, var charge, var resultingContinuationToken) = await GetAllItemsAsync(query, pageSize, cancellationToken); - _logger.LogQueryExecuted(query, charge); + logger.LogQueryExecuted(query, charge); return new Page( countResponse?.Resource ?? null, pageSize, items.AsReadOnly(), charge + countResponse?.RequestCharge ?? 0, - resultingContinationToken); + resultingContinuationToken); } /// @@ -59,13 +62,14 @@ internal sealed partial class DefaultRepository bool returnTotal = false, CancellationToken cancellationToken = default) { - Container container = await _containerProvider.GetContainerAsync() + Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); IQueryable query = container - .GetItemLinqQueryable() - .Where(_repositoryExpressionProvider - .Build(predicate ?? _repositoryExpressionProvider.Default())); + .GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider + .Build(predicate ?? repositoryExpressionProvider.Default())); Response? countResponse = null; @@ -77,12 +81,12 @@ internal sealed partial class DefaultRepository query = query.Skip(pageSize * (pageNumber - 1)) .Take(pageSize); - _logger.LogQueryConstructed(query); + logger.LogQueryConstructed(query); (List items, var charge, var resultingContinuationToken) = await GetAllItemsAsync(query, pageSize, cancellationToken); - _logger.LogQueryExecuted(query, charge); + logger.LogQueryExecuted(query, charge); return new PageQueryResult( countResponse?.Resource ?? null, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs index 3b9fb33c9..00438f95c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository { + /// public async ValueTask TryGetAsync( string id, string? partitionKeyValue = null, @@ -17,7 +18,7 @@ internal sealed partial class DefaultRepository } catch (CosmosException e) when (e.StatusCode is HttpStatusCode.NotFound) { - _logger.LogItemNotFoundHandled(id, partitionKeyValue ?? id, e); + logger.LogItemNotFoundHandled(id, partitionKeyValue ?? id, e); return default; } } @@ -36,14 +37,14 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); if (partitionKey == default) { partitionKey = new PartitionKey(id); } - _logger.LogPointReadStarted(id, partitionKey.ToString()); + logger.LogPointReadStarted(id, partitionKey.ToString()); ItemResponse response = await container.ReadItemAsync(id, partitionKey, cancellationToken: cancellationToken) @@ -51,10 +52,10 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel TItem item = response.Resource; - _logger.LogPointReadExecuted(response.RequestCharge); - _logger.LogItemRead(item); + logger.LogPointReadExecuted(response.RequestCharge); + logger.LogItemRead(item); - return _repositoryExpressionProvider.CheckItem(item); + return repositoryExpressionProvider.CheckItem(item); } /// @@ -63,18 +64,19 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); IQueryable query = - container.GetItemLinqQueryable() - .Where(_repositoryExpressionProvider.Build(predicate)); + container.GetItemLinqQueryable( + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider.Build(predicate)); - _logger.LogQueryConstructed(query); + logger.LogQueryConstructed(query); (IEnumerable items, var charge) = - await _cosmosQueryableProcessor.IterateAsync(query, cancellationToken); + await cosmosQueryableProcessor.IterateAsync(query, cancellationToken); - _logger.LogQueryExecuted(query, charge); + logger.LogQueryExecuted(query, charge); return items; } @@ -85,12 +87,12 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); - TryLogDebugDetails(_logger, () => $"Read {query}"); + TryLogDebugDetails(logger, () => $"Read {query}"); QueryDefinition queryDefinition = new(query); - return await _cosmosQueryableProcessor.IterateAsync(container, queryDefinition, cancellationToken); + return await cosmosQueryableProcessor.IterateAsync(container, queryDefinition, cancellationToken); } /// @@ -99,10 +101,10 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel CancellationToken cancellationToken = default) { Container container = - await _containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); - TryLogDebugDetails(_logger, () => $"Read {queryDefinition.QueryText}"); + TryLogDebugDetails(logger, () => $"Read {queryDefinition.QueryText}"); - return await _cosmosQueryableProcessor.IterateAsync(container, queryDefinition, cancellationToken); + return await cosmosQueryableProcessor.IterateAsync(container, queryDefinition, cancellationToken); } } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs index 349592d6f..47e5f6857 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs @@ -13,7 +13,7 @@ internal sealed partial class DefaultRepository CancellationToken cancellationToken = default) where TResult : IQueryResult { - Container container = await _containerProvider.GetContainerAsync() + Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); QueryRequestOptions options = new(); @@ -26,18 +26,19 @@ internal sealed partial class DefaultRepository IQueryable query = container .GetItemLinqQueryable( requestOptions: options, - continuationToken: specification.ContinuationToken) - .Where(_repositoryExpressionProvider.Default()); + continuationToken: specification.ContinuationToken, + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + .Where(repositoryExpressionProvider.Default()); - query = _specificationEvaluator.GetQuery(query, specification); + query = specificationEvaluator.GetQuery(query, specification); - _logger.LogQueryConstructed(query); + logger.LogQueryConstructed(query); (List items, var charge, var continuationToken) = await GetAllItemsAsync(query, specification.PageSize, cancellationToken) .ConfigureAwait(false); - _logger.LogQueryExecuted(query, charge); + logger.LogQueryExecuted(query, charge); Response count = await CountAsync(specification, cancellationToken) .ConfigureAwait(false); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index 9e1445fc9..4de7db98f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -14,7 +14,7 @@ internal sealed partial class DefaultRepository { (var optimizeBandwidth, ItemRequestOptions options) = RequestOptions; Container container = - await _containerProvider.GetContainerAsync() + await containerProvider.GetContainerAsync() .ConfigureAwait(false); if (value is IItemWithEtag valueWithEtag && !ignoreEtag) @@ -29,7 +29,7 @@ await _containerProvider.GetContainerAsync() cancellationToken) .ConfigureAwait(false); - TryLogDebugDetails(_logger, () => $"Updated: {JsonConvert.SerializeObject(value)}"); + TryLogDebugDetails(logger, () => $"Updated: {JsonConvert.SerializeObject(value)}"); return optimizeBandwidth ? value : response.Resource; } @@ -56,12 +56,12 @@ await _containerProvider.GetContainerAsync() CancellationToken cancellationToken = default) { CosmosPropertyNamingPolicy? propertyNamingPolicy = - _optionsMonitor.CurrentValue.SerializationOptions?.PropertyNamingPolicy; + optionsMonitor.CurrentValue.SerializationOptions?.PropertyNamingPolicy; IPatchOperationBuilder patchOperationBuilder = new PatchOperationBuilder(propertyNamingPolicy); builder(patchOperationBuilder); - Container container = await _containerProvider.GetContainerAsync(); + Container container = await containerProvider.GetContainerAsync(); partitionKeyValue ??= id; diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 14e2b3333..ca09bc816 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -5,39 +5,22 @@ namespace Microsoft.Azure.CosmosRepository; /// -internal sealed partial class DefaultRepository : IRepository +internal sealed partial class DefaultRepository( + IOptionsMonitor optionsMonitor, + ICosmosContainerProvider containerProvider, + ILogger> logger, + ICosmosQueryableProcessor cosmosQueryableProcessor, + IRepositoryExpressionProvider repositoryExpressionProvider, + ISpecificationEvaluator specificationEvaluator) : IRepository where TItem : IItem { - readonly ICosmosContainerProvider _containerProvider; - readonly IOptionsMonitor _optionsMonitor; - readonly ILogger> _logger; - readonly ICosmosQueryableProcessor _cosmosQueryableProcessor; - readonly IRepositoryExpressionProvider _repositoryExpressionProvider; - readonly ISpecificationEvaluator _specificationEvaluator; - - (bool OptimizeBandwidth, ItemRequestOptions Options) RequestOptions => - (_optionsMonitor.CurrentValue.OptimizeBandwidth, new ItemRequestOptions + private (bool OptimizeBandwidth, ItemRequestOptions Options) RequestOptions => + (optionsMonitor.CurrentValue.OptimizeBandwidth, new ItemRequestOptions { - EnableContentResponseOnWrite = !_optionsMonitor.CurrentValue.OptimizeBandwidth + EnableContentResponseOnWrite = !optionsMonitor.CurrentValue.OptimizeBandwidth }); - public DefaultRepository( - IOptionsMonitor optionsMonitor, - ICosmosContainerProvider containerProvider, - ILogger> logger, - ICosmosQueryableProcessor cosmosQueryableProcessor, - IRepositoryExpressionProvider repositoryExpressionProvider, - ISpecificationEvaluator specificationEvaluator) - { - _optionsMonitor = optionsMonitor; - _containerProvider = containerProvider; - _logger = logger; - _cosmosQueryableProcessor = cosmosQueryableProcessor; - _repositoryExpressionProvider = repositoryExpressionProvider; - _specificationEvaluator = specificationEvaluator; - } - - static void TryLogDebugDetails(ILogger logger, Func getMessage) + private static void TryLogDebugDetails(ILogger logger, Func getMessage) { // ReSharper disable once ConstantConditionalAccessQualifier if (logger?.IsEnabled(LogLevel.Debug) ?? false) @@ -46,13 +29,13 @@ static void TryLogDebugDetails(ILogger logger, Func getMessage) } } - static async Task<(List items, double charge, string? continuationToken)> GetAllItemsAsync( + private static async Task<(List items, double charge, string? continuationToken)> GetAllItemsAsync( IQueryable query, int pageSize, CancellationToken cancellationToken = default) { string? continuationToken = null; - List results = new(); + List results = []; var readItemsCount = 0; double charge = 0; using var iterator = query.ToFeedIterator(); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Create.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Create.cs index 743785fef..f2d32c7cc 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Create.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Create.cs @@ -17,7 +17,7 @@ internal partial class InMemoryRepository { IEnumerable enumerable = values.ToList(); - List results = new(); + List results = []; foreach (TItem value in enumerable) { diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs index 0dbf68732..7787c1e31 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs @@ -53,7 +53,7 @@ internal partial class InMemoryRepository { IEnumerable enumerable = values.ToList(); - List results = new(); + List results = []; foreach (TItem value in enumerable) { diff --git a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerSyncService.cs b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerSyncService.cs index 8d46c18c9..a211a48b3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerSyncService.cs +++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerSyncService.cs @@ -4,13 +4,8 @@ namespace Microsoft.Azure.CosmosRepository.Services; /// -class DefaultCosmosContainerSyncService : ICosmosContainerSyncService +class DefaultCosmosContainerSyncService(ICosmosContainerService containerService) : ICosmosContainerSyncService { - readonly ICosmosContainerService _containerService; - - public DefaultCosmosContainerSyncService(ICosmosContainerService containerService) => - _containerService = containerService; - public Task SyncContainerPropertiesAsync() where TItem : IItem => - _containerService.GetContainerAsync(true); + containerService.GetContainerAsync(true); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs index 41d417811..1579bedca 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs @@ -3,14 +3,11 @@ namespace Microsoft.Azure.CosmosRepository.Specification.Builder; -internal class SpecificationBuilder : ISpecificationBuilder +internal class SpecificationBuilder(BaseSpecification specification) : ISpecificationBuilder where TItem : IItem where TResult : IQueryResult { - public BaseSpecification Specification { get; } - - public SpecificationBuilder(BaseSpecification specification) => - Specification = specification; + public BaseSpecification Specification { get; } = specification; /// public ISpecificationBuilder Where(Expression> expression) diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/WhereExpressionInfo.cs b/src/Microsoft.Azure.CosmosRepository/Specification/WhereExpressionInfo.cs index 3eb3cf0d4..f7ac7c6b8 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/WhereExpressionInfo.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/WhereExpressionInfo.cs @@ -7,17 +7,15 @@ namespace Microsoft.Azure.CosmosRepository.Specification; /// Container class for a where predicate /// /// -public class WhereExpressionInfo +/// +/// Constructor for creating a where expression +/// +/// +public class WhereExpressionInfo(Expression> filter) { - /// - /// Constructor for creating a where expression - /// - /// - public WhereExpressionInfo(Expression> filter) => - Filter = filter; /// /// A predicate that is used for filtering. Given an item of a function evalut /// - public Expression> Filter { get; } + public Expression> Filter { get; } = filter; } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Validators/DefaultRepositoryOptionsValidator.cs b/src/Microsoft.Azure.CosmosRepository/Validators/DefaultRepositoryOptionsValidator.cs index 34e1638d6..aecc3699c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Validators/DefaultRepositoryOptionsValidator.cs +++ b/src/Microsoft.Azure.CosmosRepository/Validators/DefaultRepositoryOptionsValidator.cs @@ -17,7 +17,7 @@ public void ValidateForContainerCreation(IOptions options) throw new ArgumentNullException(nameof(options), "Repository option are required"); } - List exceptionsEncountered = new(); + List exceptionsEncountered = []; (var connectionStringIsNull, var accountEndpointIsNull, var tokenCredentialIsNull, var databaseIdIsNull, var containerIdIsNull, var containerPerType) = ( current.CosmosConnectionString is null, diff --git a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/AcceptanceTests.Execute.cs b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/AcceptanceTests.Execute.cs index afbdc69e3..ab16ace46 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/AcceptanceTests.Execute.cs +++ b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/AcceptanceTests.Execute.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Azure.Cosmos; using Microsoft.Azure.CosmosEventSourcing.Events; using Microsoft.Azure.CosmosEventSourcingAcceptanceTests.Aggregates; using Microsoft.Azure.CosmosEventSourcingAcceptanceTests.Items; @@ -24,16 +25,18 @@ public partial class AcceptanceTests private readonly List _atomicEventIds = new(); private readonly AsyncPolicy _defaultPolicy = Policy - .Handle() + .Handle() .WaitAndRetryAsync(20, i => TimeSpan.FromSeconds(i * 2)); - private async Task Execute() { await CreateAndVerifyTodoItemLists(); await AddItemToAllListsAndVerify("Task 1"); await CompleteTaskForItems(1); await _defaultPolicy.ExecuteAsync(CheckTodoListsProjectionBuilder); + + await Task.Delay(TimeSpan.FromSeconds(5)); + await _defaultPolicy.ExecuteAsync(() => CheckTodoItemsProjectionBuilders("Task 1")); } @@ -130,12 +133,15 @@ private async Task CheckTodoItemsProjectionBuilders(string taskTitle) foreach (var name in _names) { IEnumerable items = - await _todoItemsRepository.GetAsync(x => x.PartitionKey == name).ToListAsync(); + await _todoItemsRepository.GetAsync(x => x.PartitionKey == name); + items.Should().Contain(x => x.Title == taskTitle); items.Should().Contain(x => x.IsComplete); items.Should().HaveCount(1); } - CompletedProjections.Invocations.Should().Be(3); + // It's ok to have more invocations, but we need at least 3. + // We can end up with more when Polly retries. + CompletedProjections.Invocations.Should().BeGreaterThanOrEqualTo(3); } } \ No newline at end of file diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs index f748e3896..6b5a5bae7 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs @@ -13,6 +13,7 @@ public class DefaultRepositoryTests readonly Mock _container = new(); readonly IRepositoryExpressionProvider _expressionProvider = new MockExpressionProvider(); readonly ISpecificationEvaluator _specificationEvaluator = new SpecificationEvaluator(); + public DefaultRepositoryTests() { _options.Setup(o => o.CurrentValue).Returns(_repositoryOptions); @@ -53,7 +54,8 @@ public async Task GetAsyncGivenExpressionQueriesContainerCorrectly() _containerProviderForTestItemWithETag.Setup(o => o.GetContainerAsync()).ReturnsAsync(_container.Object); _container - .Setup(o => o.GetItemLinqQueryable(false, null, null, null)) + .Setup(o => o.GetItemLinqQueryable( + false, null, null, It.IsAny())) .Returns(queryable); _queryableProcessor.Setup(o => o.IterateAsync(queryable, It.IsAny())) @@ -86,7 +88,8 @@ public async Task ExistsAsyncGivenExpressionQueriesContainerCorrectly() _containerProviderForTestItemWithETag.Setup(o => o.GetContainerAsync()).ReturnsAsync(_container.Object); _container - .Setup(o => o.GetItemLinqQueryable(false, null, null, null)) + .Setup(o => o.GetItemLinqQueryable( + false, null, null, It.IsAny())) .Returns(queryable); _queryableProcessor.Setup(o => o.CountAsync(queryable, It.IsAny()))