From ca1b7f87b83def0f00fd4656ceb041054a9b4d49 Mon Sep 17 00:00:00 2001 From: Viliam Vadocz Date: Thu, 19 Aug 2021 14:22:29 +0200 Subject: [PATCH 1/3] Add floors to buildings for aggregation tests --- AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs | 10 +++++----- .../Mappings/CoreBuildingMappings.cs | 1 + .../Data/DatabaseInitializer.cs | 10 +++++----- .../Mappings/CoreBuildingMappings.cs | 1 + DAL.EF6/TBuilding.cs | 2 ++ DAL.EFCore/TBuilding.cs | 2 ++ DAL/TBuilding.cs | 1 + Domain.OData/CoreBuilding.cs | 1 + Domain/CoreBuilding.cs | 1 + SeedDatabase/Program.cs | 10 +++++----- 10 files changed, 24 insertions(+), 15 deletions(-) diff --git a/AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs b/AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs index 17f8cef..7a03440 100644 --- a/AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs +++ b/AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs @@ -28,8 +28,8 @@ protected override void Seed(TestDbContext context) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 4 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 5 } } }); context.MandatorSet.Add(new TMandator @@ -39,9 +39,9 @@ protected override void Seed(TestDbContext context) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id, FloorAmount = 1 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 2 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 3 } } }); context.SaveChanges(); diff --git a/AutoMapper.OData.EF6.Tests/Mappings/CoreBuildingMappings.cs b/AutoMapper.OData.EF6.Tests/Mappings/CoreBuildingMappings.cs index d5cf0ed..8366f2b 100644 --- a/AutoMapper.OData.EF6.Tests/Mappings/CoreBuildingMappings.cs +++ b/AutoMapper.OData.EF6.Tests/Mappings/CoreBuildingMappings.cs @@ -15,6 +15,7 @@ public CoreBuildingMappings() .ForMember(d => d.Name, o => o.MapFrom(s => s.LongName)) .ForMember(d => d.Tenant, o => o.MapFrom(s => s.Mandator)) .ForMember(d => d.Parameter, o => o.MapFrom(s => buildingParameter)) + .ForMember(d => d.Floors, o => o.MapFrom(s => s.FloorAmount)) .ForAllMembers(o => o.ExplicitExpansion()); CreateMap() diff --git a/AutoMapper.OData.EFCore.Tests/Data/DatabaseInitializer.cs b/AutoMapper.OData.EFCore.Tests/Data/DatabaseInitializer.cs index ca99be6..7279453 100644 --- a/AutoMapper.OData.EFCore.Tests/Data/DatabaseInitializer.cs +++ b/AutoMapper.OData.EFCore.Tests/Data/DatabaseInitializer.cs @@ -28,8 +28,8 @@ public static void SeedDatabase(MyDbContext context) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 4 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 5 } } }); context.MandatorSet.Add(new TMandator @@ -39,9 +39,9 @@ public static void SeedDatabase(MyDbContext context) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id, FloorAmount = 1 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 2 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 3 } } }); context.SaveChanges(); diff --git a/AutoMapper.OData.EFCore.Tests/Mappings/CoreBuildingMappings.cs b/AutoMapper.OData.EFCore.Tests/Mappings/CoreBuildingMappings.cs index a2ab50d..1ef3cac 100644 --- a/AutoMapper.OData.EFCore.Tests/Mappings/CoreBuildingMappings.cs +++ b/AutoMapper.OData.EFCore.Tests/Mappings/CoreBuildingMappings.cs @@ -15,6 +15,7 @@ public CoreBuildingMappings() .ForMember(d => d.Name, o => o.MapFrom(s => s.LongName)) .ForMember(d => d.Tenant, o => o.MapFrom(s => s.Mandator)) .ForMember(d => d.Parameter, o => o.MapFrom(s => buildingParameter)) + .ForMember(d => d.Floors, o => o.MapFrom(s => s.FloorAmount)) .ForAllMembers(o => o.ExplicitExpansion()); CreateMap() diff --git a/DAL.EF6/TBuilding.cs b/DAL.EF6/TBuilding.cs index a2c1a05..943fae7 100644 --- a/DAL.EF6/TBuilding.cs +++ b/DAL.EF6/TBuilding.cs @@ -28,5 +28,7 @@ public class TBuilding [ForeignKey("Mandator")] [Column("fkMandatorID")] public Int32 MandatorId { get; set; } + + public Int32 FloorAmount { get; set; } } } diff --git a/DAL.EFCore/TBuilding.cs b/DAL.EFCore/TBuilding.cs index abbbb52..3b5bc22 100644 --- a/DAL.EFCore/TBuilding.cs +++ b/DAL.EFCore/TBuilding.cs @@ -30,5 +30,7 @@ public class TBuilding [Column("fkMandatorID")] public Int32 MandatorId { get; set; } + public Int32 FloorAmount { get; set; } + } } diff --git a/DAL/TBuilding.cs b/DAL/TBuilding.cs index b774507..80e5ca4 100644 --- a/DAL/TBuilding.cs +++ b/DAL/TBuilding.cs @@ -29,5 +29,6 @@ public class TBuilding [Column("fkMandatorID")] public Int32 MandatorId { get; set; } + public Int32 FloorAmount { get; set; } } } diff --git a/Domain.OData/CoreBuilding.cs b/Domain.OData/CoreBuilding.cs index a89b9b4..ede1808 100644 --- a/Domain.OData/CoreBuilding.cs +++ b/Domain.OData/CoreBuilding.cs @@ -11,5 +11,6 @@ public class CoreBuilding public OpsBuilder Builder { get; set; } public OpsTenant Tenant { get; set; } public string Parameter { get; set; } + public Int32 Floors { get; set; } } } diff --git a/Domain/CoreBuilding.cs b/Domain/CoreBuilding.cs index ae2047c..6341d36 100644 --- a/Domain/CoreBuilding.cs +++ b/Domain/CoreBuilding.cs @@ -10,5 +10,6 @@ public class CoreBuilding public string Name { get; set; } public OpsBuilder Builder { get; set; } public OpsTenant Tenant { get; set; } + public Int32 Floors { get; set; } } } diff --git a/SeedDatabase/Program.cs b/SeedDatabase/Program.cs index 682e46e..cc68989 100644 --- a/SeedDatabase/Program.cs +++ b/SeedDatabase/Program.cs @@ -45,8 +45,8 @@ static void Main(string[] args) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L1", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 4 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "One L2", BuilderId = builders.First(b => b.Name == "Sam").Id, FloorAmount = 5 } } }); context.MandatorSet.Add(new TMandator @@ -56,9 +56,9 @@ static void Main(string[] args) CreatedDate = new DateTime(2012, 12, 12), Buildings = new List { - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id }, - new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id } + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L1", BuilderId = builders.First(b => b.Name == "John").Id, FloorAmount = 1 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L2", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 2 }, + new TBuilding { Identity = Guid.NewGuid(), LongName = "Two L3", BuilderId = builders.First(b => b.Name == "Mark").Id, FloorAmount = 3 } } }); context.SaveChanges(); From 87c6559d6eed08a089b50a74a82eee2265c0f556 Mon Sep 17 00:00:00 2001 From: Viliam Vadocz Date: Thu, 19 Aug 2021 14:22:53 +0200 Subject: [PATCH 2/3] Add failing tests --- .../GetApplyTests.cs | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 AutoMapper.OData.EFCore.Tests/GetApplyTests.cs diff --git a/AutoMapper.OData.EFCore.Tests/GetApplyTests.cs b/AutoMapper.OData.EFCore.Tests/GetApplyTests.cs new file mode 100644 index 0000000..d20615f --- /dev/null +++ b/AutoMapper.OData.EFCore.Tests/GetApplyTests.cs @@ -0,0 +1,173 @@ +using AutoMapper.AspNet.OData; +using AutoMapper.OData.EFCore.Tests.Data; +using DAL.EFCore; +using Domain.OData; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.OData; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace AutoMapper.OData.EFCore.Tests +{ + public class GetApplyTests + { + public GetApplyTests() + { + Initialize(); + } + + #region Fields + private IServiceProvider serviceProvider; + #endregion Fields + + private void Initialize() + { + IServiceCollection services = new ServiceCollection(); + IMvcCoreBuilder builder = new TestMvcCoreBuilder + { + Services = services + }; + + builder.AddOData(); + services.AddDbContext + ( + options => + { + options.UseInMemoryDatabase("MyDbContext"); + options.UseInternalServiceProvider(new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider()); + }, + ServiceLifetime.Transient + ) + .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly))) + .AddTransient(sp => new Mapper(sp.GetRequiredService(), sp.GetService)) + .AddTransient(sp => new ApplicationBuilder(sp)) + .AddRouting() + .AddLogging(); + + serviceProvider = services.BuildServiceProvider(); + + MyDbContext context = serviceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + DatabaseInitializer.SeedDatabase(context); + } + + [Fact] + public async void OpsTenantGroupbyNameCreatedDate() + { + Test(Get("/opstenant?$apply=groupby((Name, CreatedDate))")); + Test(await GetAsync("/opstenant?$apply=groupby((Name, CreatedDate))")); + + void Test(ICollection collection) + { + Assert.Equal(2, collection.Count); + Assert.Equal("One", collection.First().Name); + Assert.Equal("Two", collection.Last().Name); + Assert.Equal(2, collection.First().Buildings.Count); + Assert.Equal(3, collection.Last().Buildings.Count); + } + } + + [Fact] + public async void OpsTenantFilterName() + { + Test(Get("/opstenant?$apply=filter(Name eq 'One')")); + Test(await GetAsync("/opstenant?$apply=filter(Name eq 'One')")); + + void Test(ICollection collection) + { + Assert.Equal(1, collection.Count); + Assert.Equal("One", collection.First().Name); + } + } + + [Fact] + public async void BuildingGroupbyTenantAggregateFloorTotal() + { + Test(Get("/corebuilding?$apply=groupby((Builder), aggregate(FloorAmount with sum as FloorTotal))&$orderby=FloorTotal")); + Test(await GetAsync("/corebuilding?$apply=groupby((Builder), aggregate(FloorAmount with sum as FloorTotal))&$orderby=FloorTotal")); + + void Test(ICollection collection) + { + Assert.Equal(3, collection.Count); + Assert.Equal("Sam", collection.First().Builder.Name); + // CoreBuilding does not have .FloorTotal + // Assert.Equal(9, collection.First().FloorTotal); + } + } + + private ICollection Get(string query, ODataQueryOptions options = null) where TModel : class where TData : class + { + return + ( + DoGet + ( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService() + ) + ).ToList(); + + IQueryable DoGet(IMapper mapper, MyDbContext context) + { + return context.Set().GetQuery + ( + mapper, + options ?? GetODataQueryOptions(query), + new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False } } + ); + } + } + + private async Task> GetAsync(string query, IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null) where TModel : class where TData : class + { + return + ( + await DoGet + ( + serviceProvider.GetRequiredService() + ) + ).ToList(); + + async Task> DoGet(IMapper mapper) + { + return await dataQueryable.GetQueryAsync + ( + mapper, + options ?? GetODataQueryOptions(query), + querySettings + ); + } + } + + private async Task> GetAsync(string query, ODataQueryOptions options = null, QuerySettings querySettings = null) where TModel : class where TData : class + { + return await GetAsync + ( + query, + serviceProvider.GetRequiredService().Set(), + options, + querySettings + ); + } + + private ODataQueryOptions _oDataQueryOptions; + private ODataQueryOptions GetODataQueryOptions(string query) where TModel : class + { + if (_oDataQueryOptions == null) + { + _oDataQueryOptions = ODataHelpers.GetODataQueryOptions + ( + query, + serviceProvider + ); + } + + return (ODataQueryOptions)_oDataQueryOptions; + } + } +} From f6663379563ffcfd2fd11e4a30c91e3757e77e32 Mon Sep 17 00:00:00 2001 From: Viliam Vadocz Date: Thu, 19 Aug 2021 14:25:43 +0200 Subject: [PATCH 3/3] Add commented-out apply experiments --- .../QueryableExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs b/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs index 6552bd4..1e378dc 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs @@ -69,6 +69,12 @@ public static async Task> GetQueryAsync(this I public static IQueryable GetQuery(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null) where TModel : class { + // This gives InMemoryProjectionBindingExpressionVisitor error. + // var output = options.ApplyTo(mapper.ProjectTo(query)); + + // This can be used to generate an Expression, but what to do afterwards? + // var queryable = options.Apply.ApplyTo(Enumerable.Empty().AsQueryable(), new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.Default }); + Expression> filter = options.Filter.ToFilterExpression(querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False); query.ApplyOptions(mapper, filter, options, querySettings); return query.GetQueryable(mapper, options, querySettings, filter); @@ -176,6 +182,7 @@ private static IQueryable GetQueryable(this IQueryable> filter) where TModel : class { + // var transformations = options.Apply.ApplyClause.Transformations; // TODO How to use? var expansions = options.SelectExpand.GetExpansions(typeof(TModel)); return query.GetQuery @@ -201,6 +208,9 @@ private static IQueryable GetQuery(this IQueryable Expression> f = mapper.MapExpression>>(filter); Func, IQueryable> mappedQueryFunc = mapper.MapExpression, IQueryable>>>(queryFunc)?.Compile(); + // TODO + // Apply + if (filter != null) query = query.Where(f); @@ -227,6 +237,7 @@ private static async Task ApplyOptionsAsync(this IQueryable(ODataQueryOptions options, QuerySettings querySettings) { + options.AddApplyOptionsResult(); options.AddExpandOptionsResult(); if (querySettings?.ODataSettings?.PageSize.HasValue == true) options.AddNextLinkOptionsResult(querySettings.ODataSettings.PageSize.Value);