Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT PR] Add support for $apply #108

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public static async Task<IQueryable<TModel>> GetQueryAsync<TModel, TData>(this I
public static IQueryable<TModel> GetQuery<TModel, TData>(this IQueryable<TData> query, IMapper mapper, ODataQueryOptions<TModel> options, QuerySettings querySettings = null)
where TModel : class
{
// This gives InMemoryProjectionBindingExpressionVisitor error.
// var output = options.ApplyTo(mapper.ProjectTo<TModel>(query));

// This can be used to generate an Expression, but what to do afterwards?
// var queryable = options.Apply.ApplyTo(Enumerable.Empty<T>().AsQueryable(), new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.Default });

Expression<Func<TModel, bool>> filter = options.Filter.ToFilterExpression<TModel>(querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False);
query.ApplyOptions(mapper, filter, options, querySettings);
return query.GetQueryable(mapper, options, querySettings, filter);
Expand Down Expand Up @@ -176,6 +182,7 @@ private static IQueryable<TModel> GetQueryable<TModel, TData>(this IQueryable<TD
Expression<Func<TModel, bool>> filter)
where TModel : class
{
// var transformations = options.Apply.ApplyClause.Transformations; // TODO How to use?
var expansions = options.SelectExpand.GetExpansions(typeof(TModel));

return query.GetQuery
Expand All @@ -201,6 +208,9 @@ private static IQueryable<TModel> GetQuery<TModel, TData>(this IQueryable<TData>
Expression<Func<TData, bool>> f = mapper.MapExpression<Expression<Func<TData, bool>>>(filter);
Func<IQueryable<TData>, IQueryable<TData>> mappedQueryFunc = mapper.MapExpression<Expression<Func<IQueryable<TData>, IQueryable<TData>>>>(queryFunc)?.Compile();

// TODO
// Apply

if (filter != null)
query = query.Where(f);

Expand All @@ -227,6 +237,7 @@ private static async Task ApplyOptionsAsync<TModel, TData>(this IQueryable<TData

private static void ApplyOptions<TModel>(ODataQueryOptions<TModel> options, QuerySettings querySettings)
{
options.AddApplyOptionsResult();
options.AddExpandOptionsResult();
if (querySettings?.ODataSettings?.PageSize.HasValue == true)
options.AddNextLinkOptionsResult(querySettings.ODataSettings.PageSize.Value);
Expand Down
10 changes: 5 additions & 5 deletions AutoMapper.OData.EF6.Tests/Data/DatabaseInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ protected override void Seed(TestDbContext context)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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
Expand All @@ -39,9 +39,9 @@ protected override void Seed(TestDbContext context)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TBuilder, OpsBuilder>()
Expand Down
10 changes: 5 additions & 5 deletions AutoMapper.OData.EFCore.Tests/Data/DatabaseInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public static void SeedDatabase(MyDbContext context)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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
Expand All @@ -39,9 +39,9 @@ public static void SeedDatabase(MyDbContext context)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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();
Expand Down
173 changes: 173 additions & 0 deletions AutoMapper.OData.EFCore.Tests/GetApplyTests.cs
Original file line number Diff line number Diff line change
@@ -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<MyDbContext>
(
options =>
{
options.UseInMemoryDatabase("MyDbContext");
options.UseInternalServiceProvider(new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider());
},
ServiceLifetime.Transient
)
.AddSingleton<IConfigurationProvider>(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly)))
.AddTransient<IMapper>(sp => new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService))
.AddTransient<IApplicationBuilder>(sp => new ApplicationBuilder(sp))
.AddRouting()
.AddLogging();

serviceProvider = services.BuildServiceProvider();

MyDbContext context = serviceProvider.GetRequiredService<MyDbContext>();
context.Database.EnsureCreated();
DatabaseInitializer.SeedDatabase(context);
}

[Fact]
public async void OpsTenantGroupbyNameCreatedDate()
{
Test(Get<OpsTenant, TMandator>("/opstenant?$apply=groupby((Name, CreatedDate))"));
Test(await GetAsync<OpsTenant, TMandator>("/opstenant?$apply=groupby((Name, CreatedDate))"));

void Test(ICollection<OpsTenant> 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, TMandator>("/opstenant?$apply=filter(Name eq 'One')"));
Test(await GetAsync<OpsTenant, TMandator>("/opstenant?$apply=filter(Name eq 'One')"));

void Test(ICollection<OpsTenant> collection)
{
Assert.Equal(1, collection.Count);
Assert.Equal("One", collection.First().Name);
}
}

[Fact]
public async void BuildingGroupbyTenantAggregateFloorTotal()
{
Test(Get<CoreBuilding, TBuilding>("/corebuilding?$apply=groupby((Builder), aggregate(FloorAmount with sum as FloorTotal))&$orderby=FloorTotal"));
Test(await GetAsync<CoreBuilding, TBuilding>("/corebuilding?$apply=groupby((Builder), aggregate(FloorAmount with sum as FloorTotal))&$orderby=FloorTotal"));

void Test(ICollection<CoreBuilding> 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<TModel> Get<TModel, TData>(string query, ODataQueryOptions<TModel> options = null) where TModel : class where TData : class
{
return
(
DoGet
(
serviceProvider.GetRequiredService<IMapper>(),
serviceProvider.GetRequiredService<MyDbContext>()
)
).ToList();

IQueryable<TModel> DoGet(IMapper mapper, MyDbContext context)
{
return context.Set<TData>().GetQuery
(
mapper,
options ?? GetODataQueryOptions<TModel>(query),
new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False } }
);
}
}

private async Task<ICollection<TModel>> GetAsync<TModel, TData>(string query, IQueryable<TData> dataQueryable, ODataQueryOptions<TModel> options = null, QuerySettings querySettings = null) where TModel : class where TData : class
{
return
(
await DoGet
(
serviceProvider.GetRequiredService<IMapper>()
)
).ToList();

async Task<IQueryable<TModel>> DoGet(IMapper mapper)
{
return await dataQueryable.GetQueryAsync
(
mapper,
options ?? GetODataQueryOptions<TModel>(query),
querySettings
);
}
}

private async Task<ICollection<TModel>> GetAsync<TModel, TData>(string query, ODataQueryOptions<TModel> options = null, QuerySettings querySettings = null) where TModel : class where TData : class
{
return await GetAsync
(
query,
serviceProvider.GetRequiredService<MyDbContext>().Set<TData>(),
options,
querySettings
);
}

private ODataQueryOptions _oDataQueryOptions;
private ODataQueryOptions<TModel> GetODataQueryOptions<TModel>(string query) where TModel : class
{
if (_oDataQueryOptions == null)
{
_oDataQueryOptions = ODataHelpers.GetODataQueryOptions<TModel>
(
query,
serviceProvider
);
}

return (ODataQueryOptions<TModel>)_oDataQueryOptions;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TBuilder, OpsBuilder>()
Expand Down
2 changes: 2 additions & 0 deletions DAL.EF6/TBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ public class TBuilding
[ForeignKey("Mandator")]
[Column("fkMandatorID")]
public Int32 MandatorId { get; set; }

public Int32 FloorAmount { get; set; }
}
}
2 changes: 2 additions & 0 deletions DAL.EFCore/TBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public class TBuilding
[Column("fkMandatorID")]
public Int32 MandatorId { get; set; }

public Int32 FloorAmount { get; set; }

}
}
1 change: 1 addition & 0 deletions DAL/TBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ public class TBuilding
[Column("fkMandatorID")]
public Int32 MandatorId { get; set; }

public Int32 FloorAmount { get; set; }
}
}
1 change: 1 addition & 0 deletions Domain.OData/CoreBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
1 change: 1 addition & 0 deletions Domain/CoreBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
10 changes: 5 additions & 5 deletions SeedDatabase/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ static void Main(string[] args)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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
Expand All @@ -56,9 +56,9 @@ static void Main(string[] args)
CreatedDate = new DateTime(2012, 12, 12),
Buildings = new List<TBuilding>
{
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();
Expand Down