diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 838af5d..ef0b2e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,12 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: 10.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 647bba8..f72f416 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,12 +10,12 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: 10.x diff --git a/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj b/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj index a5e4b80..9b2373e 100644 --- a/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj +++ b/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj @@ -6,7 +6,7 @@ AutoMapper.AspNetCore.OData.EF6 Creates LINQ expressions from ODataQueryOptions and executes the query. false - Supporting AutoMapper v16 (EF Core only). + Marking obsolete methods (EF Core only). linq expressions odata efcore icon.png https://github.com/AutoMapper/AutoMapper.Extensions.OData diff --git a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj index 6f299d5..566a04c 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj +++ b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj @@ -6,7 +6,7 @@ AutoMapper.AspNetCore.OData.EFCore Creates LINQ expressions from ODataQueryOptions and executes the query. false - Supporting AutoMapper v16 (EF Core only). + Marking obsolete methods (EF Core only). linq expressions odata efcore icon.png https://github.com/AutoMapper/AutoMapper.Extensions.OData @@ -30,7 +30,7 @@ - + diff --git a/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs b/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs index 665fc35..1219c1c 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs @@ -13,6 +13,7 @@ namespace AutoMapper.AspNet.OData { public static class QueryableExtensions { + [Obsolete("Use \"Task> GetQueryAsync(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null)\" or \"IQueryable GetQuery(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null)\" instead.")] public static ICollection Get(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null) where TModel : class { @@ -31,6 +32,7 @@ public static ICollection Get(this IQueryable quer ); } + [Obsolete("Use \"Task> GetQueryAsync(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null)\" or \"IQueryable GetQuery(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null)\" instead.")] public static async Task> GetAsync(this IQueryable query, IMapper mapper, ODataQueryOptions options, QuerySettings querySettings = null) where TModel : class { @@ -72,6 +74,7 @@ public static IQueryable GetQuery(this IQueryable return query.GetQueryable(mapper, options, querySettings, filter); } + [Obsolete("This method was meant for internal use. The equivalent GetQueryable methods are private.")] public static ICollection Get(this IQueryable query, IMapper mapper, Expression> filter = null, Expression, IQueryable>> queryFunc = null, @@ -81,7 +84,7 @@ public static ICollection Get(this IQueryable quer query.GetDataQuery(mapper, filter, queryFunc, includeProperties).ToList() ).ToList(); - + [Obsolete("This method was meant for internal use. The equivalent GetQueryable methods are private.")] public static async Task> GetAsync(this IQueryable query, IMapper mapper, Expression> filter = null, Expression, IQueryable>> queryFunc = null, diff --git a/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs b/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs index 8cf4519..9e78c26 100644 --- a/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs +++ b/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs @@ -218,7 +218,7 @@ public ExpansionTestsFixture() ), ServiceLifetime.Transient ) - .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly), new NullLoggerFactory())) + .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetQueryTests).Assembly), new NullLoggerFactory())) .AddTransient(sp => new Mapper(sp.GetRequiredService(), sp.GetService)) .AddTransient(sp => new ApplicationBuilder(sp)) .AddRouting() diff --git a/AutoMapper.OData.EFCore.Tests/GetQuerySelectTests.cs b/AutoMapper.OData.EFCore.Tests/GetQuerySelectTests.cs index ba9c456..998e593 100644 --- a/AutoMapper.OData.EFCore.Tests/GetQuerySelectTests.cs +++ b/AutoMapper.OData.EFCore.Tests/GetQuerySelectTests.cs @@ -214,7 +214,7 @@ public GetQuerySelectTestsFixture() ), ServiceLifetime.Transient ) - .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly), new NullLoggerFactory())) + .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetQueryTests).Assembly), new NullLoggerFactory())) .AddTransient(sp => new Mapper(sp.GetRequiredService(), sp.GetService)) .AddTransient(sp => new ApplicationBuilder(sp)) .AddRouting() diff --git a/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs b/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs index e241efd..9c39033 100644 --- a/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs +++ b/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs @@ -1,15 +1,22 @@ using AutoMapper.AspNet.OData; +using AutoMapper.OData.EFCore.Tests.Binders; using AutoMapper.OData.EFCore.Tests.Data; using AutoMapper.OData.EFCore.Tests.Model; using DAL.EFCore; using Domain.OData; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.OData.Extensions; using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Query.Expressions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Microsoft.OData.UriParser; using System; using System.Collections.Generic; using System.Linq; @@ -2000,7 +2007,7 @@ public GetQueryTestsFixture() ), ServiceLifetime.Transient ) - .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly), new NullLoggerFactory())) + .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetQueryTests).Assembly), new NullLoggerFactory())) .AddTransient(sp => new Mapper(sp.GetRequiredService(), sp.GetService)) .AddTransient(sp => new ApplicationBuilder(sp)) .AddRouting() @@ -2016,4 +2023,82 @@ public GetQueryTestsFixture() internal IServiceProvider ServiceProvider; } + + public static class ODataHelpers + { + public static ODataQueryOptions GetODataQueryOptions(string queryString, IServiceProvider serviceProvider, string customNamespace = null, bool enableLowerCamelCase = false) where T : class + { + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + if (customNamespace != null) + builder.Namespace = customNamespace; + + if (enableLowerCamelCase) + builder.EnableLowerCamelCase(); + + builder.EntitySet(typeof(T).Name); + IEdmModel model = builder.GetEdmModel(); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet(typeof(T).Name); + ODataPath path = new ODataPath(new EntitySetSegment(entitySet)); + + var oDataQueryOptions = new ODataQueryOptions + ( + new ODataQueryContext(model, typeof(T), path), + BuildRequest(serviceProvider, model, new Uri(BASEADDRESS + queryString)) + ); + + return oDataQueryOptions; + } + + public static ODataQueryOptions GetODataQueryOptionsWithDuplicateEntityName(string queryString, IServiceProvider serviceProvider, string customNamespace = null) + { + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + if (customNamespace != null) + builder.Namespace = customNamespace; + + builder.EnableLowerCamelCase(); + + builder.EntitySet(typeof(X.CategoryModel).Name + "X"); + builder.EntitySet(nameof(Model.CategoryModel)); + IEdmModel model = builder.GetEdmModel(); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet(typeof(Model.CategoryModel).Name); + ODataPath path = new ODataPath(new EntitySetSegment(entitySet)); + + var oDataQueryOptions = new ODataQueryOptions + ( + new ODataQueryContext(model, typeof(Model.CategoryModel), path), + BuildRequest(serviceProvider, model, new Uri(BASEADDRESS + queryString)) + ); + + return oDataQueryOptions; + } + + static HttpRequest BuildRequest(IServiceProvider serviceProvider, IEdmModel model, Uri uri) + { + var request = new DefaultHttpContext() + { + RequestServices = serviceProvider + }.Request; + + var oDataOptions = new ODataOptions().AddRouteComponents("key", model, + x => x.AddSingleton()); + var (_, routeProvider) = oDataOptions.RouteComponents["key"]; + + request.ODataFeature().Services = routeProvider; + + request.Method = "GET"; + request.Host = new HostString(uri.Host, uri.Port); + request.Path = uri.LocalPath; + request.QueryString = new QueryString(uri.Query); + + return request; + } + + static readonly string BASEADDRESS = "http://localhost:16324"; + } + + internal class TestMvcCoreBuilder : IMvcCoreBuilder + { + public ApplicationPartManager PartManager { get; set; } + public IServiceCollection Services { get; set; } + } } diff --git a/AutoMapper.OData.EFCore.Tests/GetTests.cs b/AutoMapper.OData.EFCore.Tests/GetTests.cs deleted file mode 100644 index c13c361..0000000 --- a/AutoMapper.OData.EFCore.Tests/GetTests.cs +++ /dev/null @@ -1,796 +0,0 @@ -using AutoMapper.AspNet.OData; -using AutoMapper.OData.EFCore.Tests.Binders; -using AutoMapper.OData.EFCore.Tests.Data; -using DAL.EFCore; -using Domain.OData; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.OData; -using Microsoft.AspNetCore.OData.Extensions; -using Microsoft.AspNetCore.OData.Query; -using Microsoft.AspNetCore.OData.Query.Expressions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.OData.Edm; -using Microsoft.OData.ModelBuilder; -using Microsoft.OData.UriParser; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace AutoMapper.OData.EFCore.Tests -{ - public class GetTests : IClassFixture - { - private readonly GetTestsFixture _fixture; - - public GetTests(GetTestsFixture fixture) - { - _fixture = fixture; - serviceProvider = _fixture.ServiceProvider; - } - - #region Fields - private readonly IServiceProvider serviceProvider; - #endregion Fields - - [Fact] - public async Task OpsTenantCreatedOnFilterServerUTCTimeZone() - { - var querySettings = new QuerySettings - { - ODataSettings = new ODataSettings - { - HandleNullPropagation = HandleNullPropagationOption.False, - TimeZone = TimeZoneInfo.Utc - } - }; - - string query = "/opstenant?$filter=CreatedDate eq 2012-12-12T00:00:00Z"; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - query = "/opstenant?$filter=CreatedDate eq 2012-12-11T19:00:00-05:00"; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - } - } - - [Fact] - public async Task OpsTenantCreatedOnFilterServerESTTimeZone() - { - var querySettings = new QuerySettings - { - ODataSettings = new ODataSettings - { - HandleNullPropagation = HandleNullPropagationOption.False, - TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time") - } - }; - - string query = "/opstenant?$filter=CreatedDate eq 2012-12-12T05:00:00Z"; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - query = "/opstenant?$filter=CreatedDate eq 2012-12-12T00:00:00-05:00"; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - } - } - - [Fact] - public async Task OpsTenantExpandBuildingsFilterEqAndOrderBy() - { - string query = "/opstenant?$top=5&$expand=Buildings&$filter=Name eq 'One'&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Equal(2, collection.First().Buildings.Count); - Assert.Equal("One", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantExpandBuildingsFilterNeAndOrderBy() - { - string query = "/opstenant?$top=5&$expand=Buildings&$filter=Name ne 'One'&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Equal(3, collection.First().Buildings.Count); - Assert.Equal("Two", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantFilterEqNoExpand() - { - string query = "/opstenant?$filter=Name eq 'One'"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Empty(collection.First().Buildings); - Assert.Equal("One", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantExpandBuildingsNoFilterAndOrderBy() - { - string query = "/opstenant?$top=5&$expand=Buildings&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - Assert.Equal(3, collection.First().Buildings.Count); - Assert.Equal("Two", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantNoExpandNoFilterAndOrderBy() - { - string query = "/opstenant?$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - Assert.Empty(collection.First().Buildings); - Assert.Equal("Two", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantNoExpandFilterEqAndOrderBy() - { - string query = "/opstenant?$top=5&$filter=Name eq 'One'&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Empty(collection.First().Buildings); - Assert.Equal("One", collection.First().Name); - } - } - - [Fact] - public async Task OpsTenantExpandBuildingsExpandBuilderExpandCityFilterNeAndOrderBy() - { - string query = "/opstenant?$top=5&$expand=Buildings($expand=Builder($expand=City))&$filter=Name ne 'One'&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Equal(3, collection.First().Buildings.Count); - Assert.NotNull(collection.First().Buildings.First().Builder); - Assert.NotNull(collection.First().Buildings.First().Builder.City); - Assert.Equal("Two", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantFilterEqAndOrderBy() - { - string query = "/corebuilding?$top=5&$expand=Builder,Tenant&$filter=Name eq 'One L1'"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Single(collection); - Assert.Equal("Sam", collection.First().Builder.Name); - Assert.Equal("One", collection.First().Tenant.Name); - Assert.Equal("One L1", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantFilterOnNestedPropertyAndOrderBy() - { - string query = "/corebuilding?$top=5&$expand=Builder,Tenant&$filter=Builder/Name eq 'Sam'&$orderby=Name asc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - Assert.Equal("Sam", collection.First().Builder.Name); - Assert.Equal("One", collection.First().Tenant.Name); - Assert.Equal("One L1", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityFilterOnPropertyAndOrderBy() - { - string query = "/corebuilding?$top=5&$expand=Builder($expand=City),Tenant&$filter=Name ne 'One L2'&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(4, collection.Count); - Assert.NotNull(collection.First().Builder.City); - Assert.Equal("Two L3", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityFilterOnNestedNestedPropertyWithCount() - { - string query = "/corebuilding?$top=5&$expand=Builder($expand=City),Tenant&$filter=Builder/City/Name eq 'Leeds'&$count=true"; - ODataQueryOptions options = ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider - ); - Test(Get(query, options)); - Test(await GetAsync(query, options)); - Test - ( - await GetUsingCustomNameSpace - ( - query, - ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider, - "com.FooBar" - ) - ) - ); - - void Test(ICollection collection) - { - Assert.Equal(2, options.Request.ODataFeature().TotalCount); - Assert.Equal(2, collection.Count); - Assert.Equal("Leeds", collection.First().Builder.City.Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityOrderByName() - { - string query = "/corebuilding?$top=5&$expand=Builder($expand=City),Tenant&$orderby=Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - Assert.Equal("Leeds", collection.First().Builder.City.Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityOrderByNameThenByIdentity() - { - string query = "/corebuilding?$top=5&$expand=Builder($expand=City),Tenant&$orderby=Name desc,Identity"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - Assert.Equal("Leeds", collection.First().Builder.City.Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityOrderByBuilderName() - { - string query = "/corebuilding?$top=5&$expand=Builder($expand=City),Tenant&$orderby=Builder/Name"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - Assert.Equal("London", collection.First().Builder.City.Name); - Assert.Equal("Two L1", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityOrderByBuilderNameSkip3Take1WithCount() - { - string query = "/corebuilding?$skip=4&$top=1&$expand=Builder($expand=City),Tenant&$orderby=Name desc,Identity&$count=true"; - ODataQueryOptions options = ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider - ); - Test(Get(query, options)); - Test(await GetAsync(query, options)); - Test(await GetUsingCustomNameSpace(query, options)); - - void Test(ICollection collection) - { - Assert.Equal(5, options.Request.ODataFeature().TotalCount); - Assert.Single(collection); - Assert.Equal("London", collection.First().Builder.City.Name); - Assert.Equal("One L1", collection.First().Name); - } - } - - [Fact] - public async Task BuildingExpandBuilderTenantExpandCityOrderByBuilderNameSkip3Take1NoCount() - { - string query = "/corebuilding?$skip=4&$top=1&$expand=Builder($expand=City),Tenant&$orderby=Name desc,Identity"; - ODataQueryOptions options = ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider - ); - - Test(Get(query, options)); - Test(await GetAsync(query, options)); - Test(await GetUsingCustomNameSpace(query, options)); - - void Test(ICollection collection) - { - Assert.Null(options.Request.ODataFeature().TotalCount); - Assert.Single(collection); - Assert.Equal("London", collection.First().Builder.City.Name); - Assert.Equal("One L1", collection.First().Name); - } - } - - [Fact] - public async Task BuildingSelectNameWithoutOrderWithoutTop() - { - string query = "/corebuilding?$select=Name"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - } - } - - [Fact] - public async Task BuildingWithoutTopAndPageSize() - { - string query = "/corebuilding"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - } - } - - [Fact] - public async Task BuildingWithTopOnly() - { - string query = "/corebuilding?$top=3"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(3, collection.Count); - } - } - - [Fact] - public async Task BuildingWithPageSizeOnly() - { - string query = "/corebuilding"; - var querySettings = new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False, PageSize = 2 } }; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - } - } - - [Fact] - public async Task BuildingWithTopAndSmallerPageSize() - { - string query = "/corebuilding?$top=3"; - var querySettings = new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False, PageSize = 2 } }; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - } - } - - [Fact] - public async Task BuildingWithTopAndLargerPageSize() - { - string query = "/corebuilding?$top=3"; - var querySettings = new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False, PageSize = 4 } }; - Test(Get(query, querySettings: querySettings)); - Test(await GetAsync(query, querySettings: querySettings)); - Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); - - void Test(ICollection collection) - { - Assert.Equal(3, collection.Count); - } - } - - [Fact] - public async Task BuildingWithTopAndSmallerPageSizeNextLink() - { - int pageSize = 2; - string query = "/corebuilding?$top=3"; - var querySettings = new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False, PageSize = pageSize } }; - ODataQueryOptions options = ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider - ); - - Test(Get(query, options, querySettings)); - Test(await GetAsync(query, options, querySettings)); - Test - ( - await GetUsingCustomNameSpace - ( - query, - ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider, - "com.FooBar" - ), - querySettings - ) - ); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - - Uri nextPageLink = options.Request.ODataFeature().NextLink; - Assert.NotNull(nextPageLink); - Assert.Equal("localhost:16324/corebuilding?$top=1&$skip=2", nextPageLink.AbsoluteUri); - Assert.Contains("$top=1", nextPageLink.Query); - Assert.Contains("$skip=2", nextPageLink.Query); - } - } - - [Fact] - public async Task BuildingWithTopAndLargerPageSizeNextLink() - { - int pageSize = 4; - string query = "/corebuilding?$top=3"; - var querySettings = new QuerySettings { ODataSettings = new ODataSettings { HandleNullPropagation = HandleNullPropagationOption.False, PageSize = pageSize } }; - ODataQueryOptions options = ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider - ); - - Test(Get(query, options, querySettings)); - Test(await GetAsync(query, options, querySettings)); - Test - ( - await GetUsingCustomNameSpace - ( - query, - ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider, - "com.FooBar" - ), - querySettings - ) - ); - - void Test(ICollection collection) - { - Assert.Equal(3, collection.Count); - Assert.Null(options.Request.ODataFeature().NextLink); - } - } - - [Fact] - public async Task OpsTenantOrderByCountOfReference() - { - string query = "/opstenant?$expand=Buildings&$orderby=Buildings/$count desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - Assert.NotNull(collection.First().Buildings); - Assert.Equal("Two", collection.First().Name); - Assert.Equal(3, collection.First().Buildings.Count); - Assert.Equal(2, collection.Last().Buildings.Count); - } - } - - [Fact] - public async Task OpsTenantOrderByFilteredCount() - { - string query = "/opstenant?$expand=Buildings&$orderby=Buildings/$count($filter=Name eq 'One L1') desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(2, collection.Count); - Assert.NotNull(collection.First().Buildings); - Assert.Equal("One", collection.First().Name); - Assert.Equal(2, collection.First().Buildings.Count); - Assert.Equal(3, collection.Last().Buildings.Count); - } - } - - //Exception: 'The Include path 'Mandator->Buildings' results in a cycle. - //Cycles are not allowed in no-tracking queries. Either use a tracking query or remove the cycle.' - [Fact] - public async Task CoreBuildingOrderByCountOfChildReferenceOfReference() - { - string query = "/corebuilding?$expand=Tenant($expand=Buildings)&$orderby=Tenant/Buildings/$count desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - Assert.NotNull(collection.First().Tenant.Buildings); - Assert.Equal(3, collection.First().Tenant.Buildings.Count); - Assert.Equal(2, collection.Last().Tenant.Buildings.Count); - } - } - - [Fact] - public async Task CoreBuildingOrderByPropertyOfChildReferenceOfReference() - { - string query = "/corebuilding?$expand=Builder($expand=City)&$orderby=Builder/City/Name desc"; - Test(Get(query)); - Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); - - void Test(ICollection collection) - { - Assert.Equal(5, collection.Count); - Assert.NotNull(collection.First().Builder.City); - Assert.Equal("London", collection.First().Builder.City.Name); - Assert.Equal("Leeds", collection.Last().Builder.City.Name); - } - } - - [Fact] - public async Task CancellationThrowsException() - { - var cancelledToken = new CancellationTokenSource(TimeSpan.Zero).Token; - await Assert.ThrowsAnyAsync(() => GetAsync("/corebuilding", querySettings: new QuerySettings { AsyncSettings = new AsyncSettings { CancellationToken = cancelledToken } })); - } - - private ICollection Get(string query, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class - { - return DoGet - ( - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService() - ); - - ICollection DoGet(IMapper mapper, MyDbContext context) - { - return context.Set().Get - ( - mapper, - options ?? GetODataQueryOptions(query, customNamespace), - querySettings - ); - } - } - - private async Task> GetAsync(string query, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class - { - return await DoGet - ( - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService() - ); - - async Task> DoGet(IMapper mapper, MyDbContext context) - { - return await context.Set().GetAsync - ( - mapper, - options ?? GetODataQueryOptions(query, customNamespace), - querySettings - ); - } - } - - private Task> GetUsingCustomNameSpace(string query, - ODataQueryOptions options = null, QuerySettings querySettings = null) where TModel : class where TData : class - => GetAsync(query, options, querySettings, "com.FooBar"); - - private ODataQueryOptions GetODataQueryOptions(string query, string customNamespace = null) where TModel : class - { - return ODataHelpers.GetODataQueryOptions - ( - query, - serviceProvider, - customNamespace - ); - } - } - - public static class ODataHelpers - { - public static ODataQueryOptions GetODataQueryOptions(string queryString, IServiceProvider serviceProvider, string customNamespace = null, bool enableLowerCamelCase = false) where T : class - { - ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); - if (customNamespace != null) - builder.Namespace = customNamespace; - - if (enableLowerCamelCase) - builder.EnableLowerCamelCase(); - - builder.EntitySet(typeof(T).Name); - IEdmModel model = builder.GetEdmModel(); - IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet(typeof(T).Name); - ODataPath path = new ODataPath(new EntitySetSegment(entitySet)); - - var oDataQueryOptions = new ODataQueryOptions - ( - new ODataQueryContext(model, typeof(T), path), - BuildRequest(serviceProvider, model, new Uri(BASEADDRESS + queryString)) - ); - - return oDataQueryOptions; - } - - public static ODataQueryOptions GetODataQueryOptionsWithDuplicateEntityName(string queryString, IServiceProvider serviceProvider, string customNamespace = null) - { - ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); - if (customNamespace != null) - builder.Namespace = customNamespace; - - builder.EnableLowerCamelCase(); - - builder.EntitySet(typeof(X.CategoryModel).Name + "X"); - builder.EntitySet(nameof(Model.CategoryModel)); - IEdmModel model = builder.GetEdmModel(); - IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet(typeof(Model.CategoryModel).Name); - ODataPath path = new ODataPath(new EntitySetSegment(entitySet)); - - var oDataQueryOptions = new ODataQueryOptions - ( - new ODataQueryContext(model, typeof(Model.CategoryModel), path), - BuildRequest(serviceProvider, model, new Uri(BASEADDRESS + queryString)) - ); - - return oDataQueryOptions; - } - - static HttpRequest BuildRequest(IServiceProvider serviceProvider, IEdmModel model, Uri uri) - { - var request = new DefaultHttpContext() - { - RequestServices = serviceProvider - }.Request; - - var oDataOptions = new ODataOptions().AddRouteComponents("key", model, - x => x.AddSingleton()); - var (_, routeProvider) = oDataOptions.RouteComponents["key"]; - - request.ODataFeature().Services = routeProvider; - - request.Method = "GET"; - request.Host = new HostString(uri.Host, uri.Port); - request.Path = uri.LocalPath; - request.QueryString = new QueryString(uri.Query); - - return request; - } - - static readonly string BASEADDRESS = "http://localhost:16324"; - } - - internal class TestMvcCoreBuilder : IMvcCoreBuilder - { - public ApplicationPartManager PartManager { get; set; } - public IServiceCollection Services { get; set; } - } - - public class GetTestsFixture - { - public GetTestsFixture() - { - IServiceCollection services = new ServiceCollection(); - IMvcCoreBuilder builder = new TestMvcCoreBuilder - { - Services = services - }; - - builder.AddOData(); - services.AddDbContext - ( - options => options.UseSqlServer - ( - @"Server=(localdb)\mssqllocaldb;Database=GetTestsDatabase;ConnectRetryCount=0", - options => options.EnableRetryOnFailure() - ), - ServiceLifetime.Transient - ) - .AddSingleton(new MapperConfiguration(cfg => cfg.AddMaps(typeof(GetTests).Assembly), new NullLoggerFactory())) - .AddTransient(sp => new Mapper(sp.GetRequiredService(), sp.GetService)) - .AddTransient(sp => new ApplicationBuilder(sp)) - .AddRouting() - .AddLogging(); - - ServiceProvider = services.BuildServiceProvider(); - - MyDbContext context = ServiceProvider.GetRequiredService(); - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - DatabaseInitializer.SeedDatabase(context); - } - - internal IServiceProvider ServiceProvider; - } -}