diff --git a/src/Simple.OData.Client.UnitTests/Core/TypedDataAggregationTests.cs b/src/Simple.OData.Client.UnitTests/Core/TypedDataAggregationTests.cs index 47b0dada..2e45690f 100644 --- a/src/Simple.OData.Client.UnitTests/Core/TypedDataAggregationTests.cs +++ b/src/Simple.OData.Client.UnitTests/Core/TypedDataAggregationTests.cs @@ -1,310 +1,342 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Simple.OData.Client.Tests.Core; -using Simple.OData.Client.V4.Adapter.Extensions; -using Xunit; - -namespace Simple.OData.Client.Tests.FluentApi; - -public class TypedDataAggregationTests : CoreTestBase -{ - public override string MetadataFile => "Northwind4.xml"; - public override IFormatSettings FormatSettings => new ODataV4Format(); - - [Fact] - public async Task Filter() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Filter(x => x.ProductName.Contains("ai")).Filter(x => x.ProductName.StartsWith("Ch"))); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=filter%28contains%28ProductName%2C%27ai%27%29%20and%20startswith%28ProductName%2C%27Ch%27%29%29", commandText); - } - - [Fact] - public async Task AggregateWithAverage() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { AverageUnitPrice = a.Average(x.UnitPrice) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29", commandText); - } - - [Fact] - public async Task AggregateWithAverageAsConcreteDestinationType() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new ProductGroupedByCategoryNameWithAggregatedProperties - { - AverageUnitPrice = a.Average(x.UnitPrice) - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29", commandText); - } - - [Fact] - public async Task AggregateWithSum() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { Total = a.Sum(x.UnitPrice) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20sum%20as%20Total%29", commandText); - } - - [Fact] - public async Task AggregateWithMin() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { MinPrice = a.Min(x.UnitPrice) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20min%20as%20MinPrice%29", commandText); - } - - [Fact] - public async Task AggregateWithMax() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { MaxPrice = a.Max(x.UnitPrice) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20max%20as%20MaxPrice%29", commandText); - } - - [Fact] - public async Task AggregateWithDistinctCount() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { Count = a.CountDistinct(x.ProductName) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28ProductName%20with%20countdistinct%20as%20Count%29", commandText); - } - - [Fact] - public async Task AggregateWithCount() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Aggregate((x, a) => new { Count = a.Count() })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=aggregate%28%24count%20as%20Count%29", commandText); - } - - [Fact] - public async Task SimpleGroupBy() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, _) => x.Category.CategoryName)); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%29", commandText); - } - - [Fact] - public async Task SimpleGroupByWithMultipleProperties() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, _) => new - { - x.Category.CategoryName, - x.ProductName - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%2CProductName%29%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregation() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice) - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationAndNestedAssignments() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, a) => new - { - x.ProductName, - Category = new Category - { - CategoryName = x.Category.CategoryName - }, - AverageUnitPrice = a.Average(x.UnitPrice) - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28ProductName%2CCategory%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationOfMultipleProperties() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice), - Count = a.Count() - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%2C%24count%20as%20Count%29%29", commandText); - } - - [Fact] - public async Task FilterThenGroupByWithAggregationAsConcreteDestinationType() - { - var categories = new List { "Beverage", "Food" }; - var command = _client - .WithExtensions() - .For() - .Apply(b => b - .Filter(x => categories.Contains(x.Category.CategoryName)) - .GroupBy((x, a) => new ProductGroupedByCategoryNameWithAggregatedProperties - { - Category = new Category { CategoryName = x.Category.CategoryName }, - AverageUnitPrice = a.Average(x.UnitPrice) - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=filter%28%28Category%2FCategoryName%20in%20%28%27Beverage%27%2C%27Food%27%29%29%29%2Fgroupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationThenFilter() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b - .GroupBy((x, a) => new - { - x.Category.CategoryName, - MaxUnitPrice = a.Max(x.UnitPrice) - }) - .Filter(x => x.MaxUnitPrice > 100)); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20max%20as%20MaxUnitPrice%29%29%2Ffilter%28MaxUnitPrice%20gt%20100%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationThenAggregate() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b - .GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice) - }) - .Aggregate((x, a) => new { MaxPrice = a.Max(x.AverageUnitPrice) })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Faggregate%28AverageUnitPrice%20with%20max%20as%20MaxPrice%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationThenGroupByWithAggregation() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b - .GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice) - }) - .GroupBy((x, a) => new - { - x.AverageUnitPrice, - CategoriesCount = a.Count() - })); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Fgroupby%28%28AverageUnitPrice%29%2Caggregate%28%24count%20as%20CategoriesCount%29%29", commandText); - } - - [Fact] - public async Task GroupByWithAggregationThenOrderBy() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice) - })) - .OrderBy(x => x.AverageUnitPrice); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29&$orderby=AverageUnitPrice", commandText); - } - - [Fact] - public async Task MultipleApplies() - { - var command = _client - .WithExtensions() - .For() - .Apply(b => b.Filter(x => x.Category.CategoryName.Contains("ab"))) - .Apply(b => b.GroupBy((x, a) => new - { - x.Category.CategoryName, - AverageUnitPrice = a.Average(x.UnitPrice) - })) - .Apply(b => b.Filter(x => x.AverageUnitPrice > 100)); - - var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); - Assert.Equal("Products?$apply=filter%28contains%28Category%2FCategoryName%2C%27ab%27%29%29%2Fgroupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Ffilter%28AverageUnitPrice%20gt%20100%29", commandText); - } -} - -internal class ProductGroupedByCategoryNameWithAggregatedProperties : Product -{ - public decimal AverageUnitPrice { get; set; } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using Simple.OData.Client.Tests.Core; +using Simple.OData.Client.V4.Adapter.Extensions; +using Xunit; + +namespace Simple.OData.Client.Tests.FluentApi; + +public class TypedDataAggregationTests : CoreTestBase +{ + public override string MetadataFile => "Northwind4.xml"; + public override IFormatSettings FormatSettings => new ODataV4Format(); + + [Fact] + public async Task Filter() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Filter(x => x.ProductName.Contains("ai")).Filter(x => x.ProductName.StartsWith("Ch"))); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=filter%28contains%28ProductName%2C%27ai%27%29%20and%20startswith%28ProductName%2C%27Ch%27%29%29", commandText); + } + + [Fact] + public async Task AggregateWithAverage() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { AverageUnitPrice = a.Average(x.UnitPrice) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29", commandText); + } + + [Fact] + public async Task AggregateWithAverageAsConcreteDestinationType() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new ProductGroupedByCategoryNameWithAggregatedProperties + { + AverageUnitPrice = a.Average(x.UnitPrice) + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29", commandText); + } + + [Fact] + public async Task AggregateWithSum() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { Total = a.Sum(x.UnitPrice) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20sum%20as%20Total%29", commandText); + } + + [Fact] + public async Task AggregateWithMin() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { MinPrice = a.Min(x.UnitPrice) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20min%20as%20MinPrice%29", commandText); + } + + [Fact] + public async Task AggregateWithMax() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { MaxPrice = a.Max(x.UnitPrice) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28UnitPrice%20with%20max%20as%20MaxPrice%29", commandText); + } + + [Fact] + public async Task AggregateWithDistinctCount() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { Count = a.CountDistinct(x.ProductName) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28ProductName%20with%20countdistinct%20as%20Count%29", commandText); + } + + [Fact] + public async Task AggregateWithCount() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Aggregate((x, a) => new { Count = a.Count() })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=aggregate%28%24count%20as%20Count%29", commandText); + } + + [Fact] + public async Task SimpleGroupBy() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, _) => x.Category.CategoryName)); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%29", commandText); + } + + [Fact] + public async Task SimpleGroupByWithMultipleProperties() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, _) => new + { + x.Category.CategoryName, + x.ProductName + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%2CProductName%29%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregation() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice) + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationAndNestedAssignments() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, a) => new + { + x.ProductName, + Category = new Category + { + CategoryName = x.Category.CategoryName + }, + AverageUnitPrice = a.Average(x.UnitPrice) + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28ProductName%2CCategory%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationOfMultipleProperties() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice), + Count = a.Count() + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%2C%24count%20as%20Count%29%29", commandText); + } + + [Fact] + public async Task FilterThenGroupByWithAggregationAsConcreteDestinationType() + { + var categories = new List { "Beverage", "Food" }; + var command = _client + .WithExtensions() + .For() + .Apply(b => b + .Filter(x => categories.Contains(x.Category.CategoryName)) + .GroupBy((x, a) => new ProductGroupedByCategoryNameWithAggregatedProperties + { + Category = new Category { CategoryName = x.Category.CategoryName }, + AverageUnitPrice = a.Average(x.UnitPrice) + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=filter%28%28Category%2FCategoryName%20in%20%28%27Beverage%27%2C%27Food%27%29%29%29%2Fgroupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationThenFilter() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b + .GroupBy((x, a) => new + { + x.Category.CategoryName, + MaxUnitPrice = a.Max(x.UnitPrice) + }) + .Filter(x => x.MaxUnitPrice > 100)); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20max%20as%20MaxUnitPrice%29%29%2Ffilter%28MaxUnitPrice%20gt%20100%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationThenAggregate() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b + .GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice) + }) + .Aggregate((x, a) => new { MaxPrice = a.Max(x.AverageUnitPrice) })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Faggregate%28AverageUnitPrice%20with%20max%20as%20MaxPrice%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationThenGroupByWithAggregation() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b + .GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice) + }) + .GroupBy((x, a) => new + { + x.AverageUnitPrice, + CategoriesCount = a.Count() + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Fgroupby%28%28AverageUnitPrice%29%2Caggregate%28%24count%20as%20CategoriesCount%29%29", commandText); + } + + [Fact] + public async Task GroupByWithAggregationThenOrderBy() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice) + })) + .OrderBy(x => x.AverageUnitPrice); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29&$orderby=AverageUnitPrice", commandText); + } + + [Fact] + public async Task MultipleApplies() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.Filter(x => x.Category.CategoryName.Contains("ab"))) + .Apply(b => b.GroupBy((x, a) => new + { + x.Category.CategoryName, + AverageUnitPrice = a.Average(x.UnitPrice) + })) + .Apply(b => b.Filter(x => x.AverageUnitPrice > 100)); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=filter%28contains%28Category%2FCategoryName%2C%27ab%27%29%29%2Fgroupby%28%28Category%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29%2Ffilter%28AverageUnitPrice%20gt%20100%29", commandText); + } + + [Fact] + public async Task GroupByNullablePropertyWithAggregation() + { + var command = _client + .WithExtensions() + .For() + .Apply(b => b.GroupBy((x, a) => new ProductGroupedByNullableCategoryIdWithAggregatedProperties + { + Category = new CategoryWithNullableId + { + CategoryID = x.Category.CategoryID, + CategoryName = x.Category.CategoryName + }, + AverageUnitPrice = a.Average(x.UnitPrice) + })); + + var commandText = await command.GetCommandTextAsync().ConfigureAwait(false); + Assert.Equal("Products?$apply=groupby%28%28Category%2FCategoryID%2CCategory%2FCategoryName%29%2Caggregate%28UnitPrice%20with%20average%20as%20AverageUnitPrice%29%29", commandText); + } +} + +internal class ProductGroupedByCategoryNameWithAggregatedProperties : Product +{ + public decimal AverageUnitPrice { get; set; } +} + +internal class ProductGroupedByNullableCategoryIdWithAggregatedProperties : Product +{ + public CategoryWithNullableId Category { get; set; } + public decimal AverageUnitPrice { get; set; } +} + +internal class CategoryWithNullableId +{ + public int? CategoryID { get; set; } + public string CategoryName { get; set; } +} diff --git a/src/Simple.OData.Client.V4.Adapter/Extensions/DataAggregationBuilder.cs b/src/Simple.OData.Client.V4.Adapter/Extensions/DataAggregationBuilder.cs index eec000c5..c6726fad 100644 --- a/src/Simple.OData.Client.V4.Adapter/Extensions/DataAggregationBuilder.cs +++ b/src/Simple.OData.Client.V4.Adapter/Extensions/DataAggregationBuilder.cs @@ -1,230 +1,233 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace Simple.OData.Client.V4.Adapter.Extensions -{ - internal abstract class DataAggregationBuilder - { - protected readonly List DataAggregationClauses; - private DataAggregationBuilder? _nextDataAggregationBuilder; - - protected DataAggregationBuilder() - { - DataAggregationClauses = new List(); - } - - internal string Build(ResolvedCommand command, ISession session) - { - var context = new ExpressionContext(session, null, null, command.DynamicPropertiesContainerName); - var commandText = string.Empty; - foreach (var applyClause in DataAggregationClauses) - { - var formattedApplyClause = applyClause.Format(context); - if (string.IsNullOrEmpty(formattedApplyClause)) - { - continue; - } - - if (commandText.Length > 0) - { - commandText += "/"; - } - - commandText += formattedApplyClause; - } - - return AddNextCommand(commandText, command, session); - } - - internal void Append(DataAggregationBuilder nextDataAggregationBuilder) - { - if (_nextDataAggregationBuilder != null) - { - _nextDataAggregationBuilder.Append(nextDataAggregationBuilder); - return; - } - - _nextDataAggregationBuilder = nextDataAggregationBuilder; - } - - private string AddNextCommand(string commandText, ResolvedCommand command, ISession session) - { - if (_nextDataAggregationBuilder == null) - { - return commandText; - } - - var nestedCommand = _nextDataAggregationBuilder.Build(command, session); - if (string.IsNullOrEmpty(nestedCommand)) - { - return commandText; - } - - if (commandText.Length > 0) - { - commandText += "/"; - } - - commandText += nestedCommand; - - return commandText; - } - } - - /// - internal class DataAggregationBuilder : DataAggregationBuilder, IDataAggregation - where T : class - { - private readonly ISession _session; - - internal DataAggregationBuilder(ISession session) : base() - { - _session = session; - } - - public IDataAggregation Filter(Expression> filter) - { - if (DataAggregationClauses.LastOrDefault() is FilterClause filterClause) - { - filterClause.Append(ODataExpression.FromLinqExpression(filter)); - } - else - { - filterClause = new FilterClause(ODataExpression.FromLinqExpression(filter)); - DataAggregationClauses.Add(filterClause); - } - - return this; - } - - public IDataAggregation Aggregate(Expression, TR>> aggregation) where TR : class - { - var aggregationClauses = ExtractAggregationClauses(aggregation); - DataAggregationClauses.Add(aggregationClauses); - var nextDataAggregationBuilder = new DataAggregationBuilder(_session); - Append(nextDataAggregationBuilder); - return nextDataAggregationBuilder; - } - - private static AggregationClauseCollection ExtractAggregationClauses(Expression, TR>> expression) where TR : class - { - var aggregationClauses = new AggregationClauseCollection(); - switch (expression.Body) - { - case NewExpression newExpression: - { - var membersCount = Math.Min(newExpression.Members.Count, newExpression.Arguments.Count); - for (var index = 0; index < membersCount; index++) - { - if (newExpression.Arguments[index] is MethodCallExpression methodCallExpression && methodCallExpression.Method.DeclaringType == typeof(IAggregationFunction)) - { - aggregationClauses.Add(new AggregationClause(newExpression.Members[index].Name, newExpression.Arguments[index])); - } - } - - break; - } - case MemberInitExpression memberInitExpression: - { - foreach (var assignment in memberInitExpression.Bindings.OfType()) - { - if (assignment.Expression is MethodCallExpression methodCallExpression && methodCallExpression.Method.DeclaringType == typeof(IAggregationFunction)) - { - aggregationClauses.Add(new AggregationClause(assignment.Member.Name, assignment.Expression)); - } - } - - break; - } - default: - throw new AggregateException("Expression should be a NewExpression or MemberInitExpression"); - } - - return aggregationClauses; - } - - public IDataAggregation GroupBy(Expression, TR>> groupBy) where TR : class - { - var groupByColumns = new List(); - AggregationClauseCollection? aggregationClauses = null; - if (groupBy.Body is MemberExpression memberExpression) - { - groupByColumns.Add(memberExpression.ExtractColumnName(_session.TypeCache)); - } - else - { - aggregationClauses = ExtractAggregationClauses(groupBy); - groupByColumns.AddRange(ExtractGroupByColumns(groupBy)); - } - - var groupByClause = new GroupByClause(groupByColumns, aggregationClauses); - DataAggregationClauses.Add(groupByClause); - var nextDataAggregationBuilder = new DataAggregationBuilder(_session); - Append(nextDataAggregationBuilder); - return nextDataAggregationBuilder; - } - - private IEnumerable ExtractGroupByColumns(Expression, TR>> expression) where TR : class - { - switch (expression.Body) - { - case NewExpression newExpression: - { - var membersCount = Math.Min(newExpression.Members.Count, newExpression.Arguments.Count); - for (var index = 0; index < membersCount; index++) - { - switch (newExpression.Arguments[index]) - { - case MemberExpression _: - yield return newExpression.Arguments[index].ExtractColumnName(_session.TypeCache); - break; - case MemberInitExpression memberInitExpression: - { - foreach (var columnName in ExtractColumnNames(memberInitExpression)) - { - yield return columnName; - } - - break; - } - } - } - - break; - } - case MemberInitExpression memberInitExpression: - { - foreach (var columnName in ExtractColumnNames(memberInitExpression)) - { - yield return columnName; - } - - break; - } - default: - throw new AggregateException("Expression should be a NewExpression or MemberInitExpression"); - } - } - - private IEnumerable ExtractColumnNames(MemberInitExpression expression) - { - var columnNames = new List(); - foreach (var assignment in expression.Bindings.OfType()) - { - switch (assignment.Expression) - { - case MemberExpression _: - columnNames.Add(assignment.Expression.ExtractColumnName(_session.TypeCache)); - break; - case MemberInitExpression memberInitExpression: - columnNames.AddRange(ExtractColumnNames(memberInitExpression)); - break; - } - } - - return columnNames; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Simple.OData.Client.V4.Adapter.Extensions +{ + internal abstract class DataAggregationBuilder + { + protected readonly List DataAggregationClauses; + private DataAggregationBuilder? _nextDataAggregationBuilder; + + protected DataAggregationBuilder() + { + DataAggregationClauses = new List(); + } + + internal string Build(ResolvedCommand command, ISession session) + { + var context = new ExpressionContext(session, null, null, command.DynamicPropertiesContainerName); + var commandText = string.Empty; + foreach (var applyClause in DataAggregationClauses) + { + var formattedApplyClause = applyClause.Format(context); + if (string.IsNullOrEmpty(formattedApplyClause)) + { + continue; + } + + if (commandText.Length > 0) + { + commandText += "/"; + } + + commandText += formattedApplyClause; + } + + return AddNextCommand(commandText, command, session); + } + + internal void Append(DataAggregationBuilder nextDataAggregationBuilder) + { + if (_nextDataAggregationBuilder != null) + { + _nextDataAggregationBuilder.Append(nextDataAggregationBuilder); + return; + } + + _nextDataAggregationBuilder = nextDataAggregationBuilder; + } + + private string AddNextCommand(string commandText, ResolvedCommand command, ISession session) + { + if (_nextDataAggregationBuilder == null) + { + return commandText; + } + + var nestedCommand = _nextDataAggregationBuilder.Build(command, session); + if (string.IsNullOrEmpty(nestedCommand)) + { + return commandText; + } + + if (commandText.Length > 0) + { + commandText += "/"; + } + + commandText += nestedCommand; + + return commandText; + } + } + + /// + internal class DataAggregationBuilder : DataAggregationBuilder, IDataAggregation + where T : class + { + private readonly ISession _session; + + internal DataAggregationBuilder(ISession session) : base() + { + _session = session; + } + + public IDataAggregation Filter(Expression> filter) + { + if (DataAggregationClauses.LastOrDefault() is FilterClause filterClause) + { + filterClause.Append(ODataExpression.FromLinqExpression(filter)); + } + else + { + filterClause = new FilterClause(ODataExpression.FromLinqExpression(filter)); + DataAggregationClauses.Add(filterClause); + } + + return this; + } + + public IDataAggregation Aggregate(Expression, TR>> aggregation) where TR : class + { + var aggregationClauses = ExtractAggregationClauses(aggregation); + DataAggregationClauses.Add(aggregationClauses); + var nextDataAggregationBuilder = new DataAggregationBuilder(_session); + Append(nextDataAggregationBuilder); + return nextDataAggregationBuilder; + } + + private static AggregationClauseCollection ExtractAggregationClauses(Expression, TR>> expression) where TR : class + { + var aggregationClauses = new AggregationClauseCollection(); + switch (expression.Body) + { + case NewExpression newExpression: + { + var membersCount = Math.Min(newExpression.Members.Count, newExpression.Arguments.Count); + for (var index = 0; index < membersCount; index++) + { + if (newExpression.Arguments[index] is MethodCallExpression methodCallExpression && methodCallExpression.Method.DeclaringType == typeof(IAggregationFunction)) + { + aggregationClauses.Add(new AggregationClause(newExpression.Members[index].Name, newExpression.Arguments[index])); + } + } + + break; + } + case MemberInitExpression memberInitExpression: + { + foreach (var assignment in memberInitExpression.Bindings.OfType()) + { + if (assignment.Expression is MethodCallExpression methodCallExpression && methodCallExpression.Method.DeclaringType == typeof(IAggregationFunction)) + { + aggregationClauses.Add(new AggregationClause(assignment.Member.Name, assignment.Expression)); + } + } + + break; + } + default: + throw new AggregateException("Expression should be a NewExpression or MemberInitExpression"); + } + + return aggregationClauses; + } + + public IDataAggregation GroupBy(Expression, TR>> groupBy) where TR : class + { + var groupByColumns = new List(); + AggregationClauseCollection? aggregationClauses = null; + if (groupBy.Body is MemberExpression memberExpression) + { + groupByColumns.Add(memberExpression.ExtractColumnName(_session.TypeCache)); + } + else + { + aggregationClauses = ExtractAggregationClauses(groupBy); + groupByColumns.AddRange(ExtractGroupByColumns(groupBy)); + } + + var groupByClause = new GroupByClause(groupByColumns, aggregationClauses); + DataAggregationClauses.Add(groupByClause); + var nextDataAggregationBuilder = new DataAggregationBuilder(_session); + Append(nextDataAggregationBuilder); + return nextDataAggregationBuilder; + } + + private IEnumerable ExtractGroupByColumns(Expression, TR>> expression) where TR : class + { + switch (expression.Body) + { + case NewExpression newExpression: + { + var membersCount = Math.Min(newExpression.Members.Count, newExpression.Arguments.Count); + for (var index = 0; index < membersCount; index++) + { + switch (newExpression.Arguments[index]) + { + case MemberExpression _: + yield return newExpression.Arguments[index].ExtractColumnName(_session.TypeCache); + break; + case MemberInitExpression memberInitExpression: + { + foreach (var columnName in ExtractColumnNames(memberInitExpression)) + { + yield return columnName; + } + + break; + } + } + } + + break; + } + case MemberInitExpression memberInitExpression: + { + foreach (var columnName in ExtractColumnNames(memberInitExpression)) + { + yield return columnName; + } + + break; + } + default: + throw new AggregateException("Expression should be a NewExpression or MemberInitExpression"); + } + } + + private IEnumerable ExtractColumnNames(MemberInitExpression expression) + { + var columnNames = new List(); + foreach (var assignment in expression.Bindings.OfType()) + { + switch (assignment.Expression) + { + case MemberExpression _: + columnNames.Add(assignment.Expression.ExtractColumnName(_session.TypeCache)); + break; + case MemberInitExpression memberInitExpression: + columnNames.AddRange(ExtractColumnNames(memberInitExpression)); + break; + case UnaryExpression unaryExpression when unaryExpression.NodeType == ExpressionType.Convert: + columnNames.Add(unaryExpression.Operand.ExtractColumnName(_session.TypeCache)); + break; + } + } + + return columnNames; + } + } } \ No newline at end of file