From d7e9658a74a39aec903b18bc3c863af72e51b810 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Sat, 15 Feb 2025 11:07:02 -0500 Subject: [PATCH 1/4] 1) Supporting AutoMapper v14. 2) Fixes Issue #235 --- .../AutoMapper.AspNetCore.OData.EFCore.csproj | 2 +- .../Visitors/ProjectionVisitor.cs | 7 +- .../GetQueryTests.cs | 186 +++++++++++++++++- AutoMapper.OData.EFCore.Tests/GetTests.cs | 5 +- .../Mappings/ObjectMappings.cs | 7 + .../Model/ModelClasses.cs | 45 +++++ 6 files changed, 240 insertions(+), 12 deletions(-) diff --git a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj index 58c775a..0565b10 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj +++ b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj @@ -28,7 +28,7 @@ - + diff --git a/AutoMapper.AspNetCore.OData.EFCore/Visitors/ProjectionVisitor.cs b/AutoMapper.AspNetCore.OData.EFCore/Visitors/ProjectionVisitor.cs index e536d13..733a54d 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/Visitors/ProjectionVisitor.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/Visitors/ProjectionVisitor.cs @@ -36,7 +36,7 @@ protected override Expression VisitMemberInit(MemberInitExpression node) List AddBinding(List list, MemberAssignment binding) { - if (ListTypesAreEquivalent(binding.Member.GetMemberType(), expansion.MemberType) + if (TypesAreEquivalent(binding.Member.GetMemberType(), expansion.MemberType) && string.Compare(binding.Member.Name, expansion.MemberName, true) == 0)//found the expansion { if (foundExpansions.Count > 0) @@ -61,8 +61,11 @@ void AddBindingExpression(Expression bindingExpression) protected abstract Expression GetBindingExpression(MemberAssignment binding, ODataExpansionOptions expansion); - protected static bool ListTypesAreEquivalent(Type bindingType, Type expansionType) + protected static bool TypesAreEquivalent(Type bindingType, Type expansionType) { + if (!bindingType.IsList() && !expansionType.IsList()) + return bindingType == expansionType; + if (!bindingType.IsList() || !expansionType.IsList()) return false; diff --git a/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs b/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs index f62e8f5..39e1a92 100644 --- a/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs +++ b/AutoMapper.OData.EFCore.Tests/GetQueryTests.cs @@ -44,6 +44,7 @@ public async Task OpsTenantSearch() string query = "/opstenant?$search=One"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -59,6 +60,7 @@ public async Task OpsTenantSearchAndFilter() string query = "/opstenant?$search=One&$filter=Name eq 'Two'"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -82,6 +84,7 @@ public async Task OpsTenantCreatedOnFilterServerUTCTimeZone() string query = "/opstenant?$filter=CreatedDate eq 2012-12-12T00:00:00Z"; Test(Get(query, querySettings: querySettings)); Test(await GetAsync(query, querySettings: querySettings)); + Test(await GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); query = "/opstenant?$filter=CreatedDate eq 2012-12-11T19:00:00-05:00"; @@ -110,11 +113,13 @@ public async Task OpsTenantCreatedOnFilterServerESTTimeZone() string query = "/opstenant?$filter=CreatedDate eq 2012-12-12T05:00:00Z"; Test(Get(query, querySettings: querySettings)); Test(await GetAsync(query, querySettings: querySettings)); + Test(await GetAsyncUsingLowerCamelCase(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 GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); void Test(ICollection collection) @@ -129,6 +134,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -145,6 +151,7 @@ public async Task OpsTenantExpandBuildingsNestedFilterEqUsingThisParameterWithNo const string query = "/opstenant?$expand=Buildings($filter=$this/Parameter eq 'FakeParameter')"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); static void Test(ICollection collection) @@ -161,6 +168,7 @@ public async Task OpsTenantExpandBuildingsNestedFilterEqUsingThisParameterWithMa const string query = "/opstenant?$expand=Buildings($filter=$this/Name eq 'Two L1')&$orderby=Name desc"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); static void Test(ICollection collection) @@ -178,6 +186,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -194,6 +203,7 @@ public async Task OpsTenantFilterEqNoExpand() string query = "/opstenant?$filter=Name eq 'One'"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -210,6 +220,7 @@ public async Task OpsTenantFilterGtDateNoExpand() string query = "/opstenant?$filter=CreatedDate gt 2012-11-11T12:00:00.00Z"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -226,6 +237,7 @@ public async Task OpsTenantFilterLtDateNoExpand() string query = "/opstenant?$filter=CreatedDate lt 2012-11-11T12:00:00.00Z"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -240,6 +252,7 @@ public async Task OpsTenantExpandBuildingsNoFilterAndOrderBy() string query = "/opstenant?$top=5&$expand=Buildings&$orderby=Name desc"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -256,6 +269,7 @@ public async Task OpsTenantNoExpandNoFilterAndOrderBy() string query = "/opstenant?$orderby=Name desc"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -280,6 +294,7 @@ public async Task OpsTenantNoExpandNoFilterNoOrderByShouldApplyByPk(bool alwaysS Test(Get(query, GetMandators(), querySettings: querySettings)); Test(await GetAsync(query, GetMandators(), querySettings: querySettings)); + Test(await GetAsync(query, GetMandators(), querySettings: querySettings, customNamespace: null, enableLowerCamelCase : true)); Test(await GetUsingCustomNameSpace(query, GetMandators(), querySettings: querySettings)); return; @@ -319,6 +334,7 @@ public async Task OpsTenantNoExpandNoFilterWithOrderByShouldApplyByPk(bool alway // Test multiple scenarios Test(Get(query, GetMandators(), querySettings: querySettings)); Test(await GetAsync(query, GetMandators(), querySettings: querySettings)); + Test(await GetAsyncUsingLowerCamelCase(query, GetMandators(), querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, GetMandators(), querySettings: querySettings)); return; @@ -350,6 +366,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -366,6 +383,7 @@ public async Task OpsTenantExpandBuildingsSelectNameAndBuilderExpandBuilderExpan string query = "/opstenant?$top=5&$select=Name&$expand=Buildings($select=Name,Builder;$expand=Builder($select=Name,City;$expand=City))&$filter=Name ne 'One'&$orderby=Name desc"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -385,6 +403,7 @@ public async Task OpsTenantExpandBuildingsExpandBuilderExpandCityFilterNeAndOrde 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -438,6 +457,19 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + null, + new QuerySettings + { + ProjectionSettings = projectionSettings, + ODataSettings = odataSettings + } + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -467,6 +499,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -484,7 +517,7 @@ public async Task BuildingExpandBuilderSelectNameExpandTenantFilterEqAndOrderBy( string query = "/corebuilding?$top=5&$expand=Builder($select=Name),Tenant&$filter=Name eq 'One L1'"; Test(Get(query)); Test(await GetAsync(query)); - Test(await GetUsingCustomNameSpace(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -502,6 +535,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -519,6 +553,7 @@ public async Task BuildingExpandBuilderTenantExpandCityFilterOnPropertyAndOrderB 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -540,6 +575,7 @@ public async Task BuildingExpandBuilderTenantExpandCityFilterOnNestedNestedPrope ); Test(Get(query, options)); Test(await GetAsync(query, options)); + Test(await GetAsyncUsingLowerCamelCase(query, options)); Test ( await GetUsingCustomNameSpace @@ -568,6 +604,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -583,6 +620,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -598,6 +636,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -619,6 +658,7 @@ public async Task BuildingExpandBuilderTenantExpandCityOrderByBuilderNameSkip3Ta ); Test(Get(query, options)); Test(await GetAsync(query, options)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query, options)); void Test(ICollection collection) @@ -642,6 +682,7 @@ public async Task BuildingExpandBuilderTenantExpandCityOrderByBuilderNameSkip3Ta Test(Get(query, options)); Test(await GetAsync(query, options)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query, options)); void Test(ICollection collection) @@ -659,6 +700,7 @@ public async Task BuildingSelectName_WithoutOrder_WithoutTop() string query = "/corebuilding?$select=Name"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -673,6 +715,7 @@ public async Task BuildingWithoutTopAndPageSize() string query = "/corebuilding"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -687,6 +730,7 @@ public async Task BuildingWithTopOnly() string query = "/corebuilding?$top=3"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -702,6 +746,7 @@ public async Task BuildingWithPageSizeOnly() 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 GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); void Test(ICollection collection) @@ -717,6 +762,7 @@ public async Task BuildingWithTopAndSmallerPageSize() 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 GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); void Test(ICollection collection) @@ -732,6 +778,7 @@ public async Task BuildingWithTopAndLargerPageSize() 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 GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test(await GetUsingCustomNameSpace(query, querySettings: querySettings)); void Test(ICollection collection) @@ -754,6 +801,8 @@ public async Task BuildingWithTopAndSmallerPageSizeNextLink() Test(Get(query, options, querySettings)); Test(await GetAsync(query, options, querySettings)); + Test(await GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); + Test(await GetAsyncUsingLowerCamelCase(query, options, querySettings)); Test ( await GetUsingCustomNameSpace @@ -795,6 +844,7 @@ public async Task BuildingWithTopAndLargerPageSizeNextLink() Test(Get(query, options, querySettings)); Test(await GetAsync(query, options, querySettings)); + Test(await GetAsyncUsingLowerCamelCase(query, querySettings: querySettings)); Test ( await GetUsingCustomNameSpace @@ -837,7 +887,6 @@ public void BuildingsFilterNameEnableConstantParameterization() { string query = "/corebuilding?$filter=contains(Name, 'Two L2')"; Test(GetQuery(query, querySettings: new() { ODataSettings = new() { EnableConstantParameterization = true } })); - void Test(IQueryable queryable) { string sqlQuery = queryable.ToQueryString(); @@ -853,6 +902,7 @@ public async Task OpsTenantOrderByCountOfReference() string query = "/opstenant?$expand=Buildings&$orderby=Buildings/$count desc"; Test(Get(query)); Test(await GetAsync(query)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -871,7 +921,7 @@ 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)); + Test(await GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -915,6 +965,7 @@ 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 GetAsyncUsingLowerCamelCase(query)); Test(await GetUsingCustomNameSpace(query)); void Test(ICollection collection) @@ -1055,6 +1106,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1091,6 +1150,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1127,6 +1194,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1163,6 +1238,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1200,6 +1283,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1237,6 +1328,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1274,6 +1373,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1311,6 +1418,14 @@ await GetAsync ) ); Test + ( + await GetAsyncUsingLowerCamelCase + ( + query, + GetCategories() + ) + ); + Test ( await GetUsingCustomNameSpace ( @@ -1332,6 +1447,7 @@ public async Task SkipOnRootNoOrderByThenExpandAndSkipOnChildCollectionNoOrderBy { const string query = "/CategoryModel?$skip=1&$expand=Products($skip=1;$expand=AlternateAddresses($skip=1;$top=3;$orderby=AddressID desc))"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1352,6 +1468,7 @@ public async Task SkipOnRootNoOrderByThenExpandAndSkipOnChildCollectionNoOrderBy { const string query = "/CategoryModel?$skip=1&$expand=Products($skip=1;$expand=AlternateAddresses($skip=1;$top=3))"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1372,6 +1489,7 @@ public async Task ExpandChildCollectionWithTopAndSkipNoOrderBy() { const string query = "/CategoryModel?$expand=Products($skip=1;$top=2)"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1389,6 +1507,7 @@ public async Task ExpandChildCollectionSkipBeyondAllElementsNoOrderBy() { const string query = "/CategoryModel?$expand=Products($skip=3)"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1404,6 +1523,7 @@ public async Task ExpandChildCollectionWithSkipNoOrderByModelHasCompositeKey() { const string query = "/CategoryModel?$expand=CompositeKeys($skip=1)"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1430,6 +1550,7 @@ public async Task ExpandChildCollectionWithSkipAndTopNoOrderByModelHasCompositeK { const string query = "/CategoryModel?$expand=CompositeKeys($skip=1;$top=2)"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1452,6 +1573,7 @@ public async Task SkipOnRootNoOrderBy() { const string query = "/CategoryModel?$skip=1"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1493,6 +1615,7 @@ public async Task SkipBeyondAllElementsOnRootNoOrderBy() { const string query = "/CategoryModel?$skip=2"; Test(await GetAsync(query, GetCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetCategories())); Test(await GetUsingCustomNameSpace(query, GetCategories())); Test(Get(query, GetCategories())); @@ -1522,6 +1645,7 @@ public async Task ExpandChildCollectionWithNoDefaultConstructorNoFilter() { const string query = "/SuperCategoryModel?$expand=Categories"; Test(await GetAsync(query, GetSuperCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetSuperCategories())); Test(await GetUsingCustomNameSpace(query, GetSuperCategories())); Test(Get(query, GetSuperCategories())); @@ -1537,6 +1661,7 @@ public async Task ExpandChildCollectionWithNoDefaultConstructorNestedFilter() { const string query = "/SuperCategoryModel?$expand=Categories($filter=CategoryName eq 'CategoryOne')"; Test(await GetAsync(query, GetSuperCategories())); + Test(await GetAsyncUsingLowerCamelCase(query, GetSuperCategories())); Test(await GetUsingCustomNameSpace(query, GetSuperCategories())); Test(Get(query, GetSuperCategories())); @@ -1554,6 +1679,33 @@ public async Task CancellationThrowsException() await Assert.ThrowsAnyAsync(() => GetAsync("/corebuilding?$count=true", querySettings: new QuerySettings { AsyncSettings = new AsyncSettings { CancellationToken = cancelledToken } })); } + [Fact] + public async Task OrganizationIdentitiesWithNestedOrganizationNames() + { + string odataQuery = "/OrganizationIdentity?$select=Id&$expand=Organization($select=Id;$expand=Names($select=Id,LangId,Value;$filter=LangId eq 1))"; + var query = GetOrganizationIdentitiesWithNestedOrganizationNames(); + + Assert.Equal(2, query.First().Organization.Names.Count); + Assert.Single(query.First().Organization.Names, n => n.LangId == 1); + + var result = (await GetAsync(odataQuery, query)).ToList(); + Assert.Single(result.First().Organization.Names); + } + + private IQueryable GetOrganizationIdentitiesWithNestedOrganizationNames() + { + var organization1 = new Organization() { Id = 1 }; + var name1 = new OrganizationName() { Id = 1, LangId = 1, Value = "Value (lang1)" }; + var name2 = new OrganizationName() { Id = 2, LangId = 2, Value = "Value (lang2)" }; + organization1.Names = new[] { name1, name2 }; + + var identity1 = new OrganizationIdentity() { Id = 1, OrganizationId = organization1.Id, Organization = organization1, Value = "id1", TypeId = 1 }; + var identity2 = new OrganizationIdentity() { Id = 2, OrganizationId = organization1.Id, Organization = organization1, Value = "id2", TypeId = 2 }; + organization1.Identities = new[] { identity2 }; + + return new[] { identity1, identity2 }.AsQueryable(); + } + private ICollection Get(string query, IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null) where TModel : class where TData : class { return @@ -1599,7 +1751,7 @@ private ICollection Get(string query, ODataQueryOptions> GetAsync(string query, - IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class + IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null, bool enableLowerCamelCase = false) where TModel : class where TData : class { return ( @@ -1614,12 +1766,18 @@ async Task> DoGet(IMapper mapper) return await dataQueryable.GetQueryAsync ( mapper, - options ?? GetODataQueryOptions(query, customNamespace), + options ?? GetODataQueryOptions(query, customNamespace, enableLowerCamelCase: enableLowerCamelCase), querySettings ); } } + private Task> GetAsyncUsingLowerCamelCase(string query, + IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class + { + return GetAsync(query, dataQueryable, options, querySettings, customNamespace, true); + } + private async Task> GetAsync(string query, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class { return await GetAsync @@ -1632,6 +1790,19 @@ private async Task> GetAsync(string query, OD ); } + private async Task> GetAsyncUsingLowerCamelCase(string query, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) where TModel : class where TData : class + { + return await GetAsync + ( + query, + serviceProvider.GetRequiredService().Set(), + options, + querySettings, + customNamespace, + enableLowerCamelCase: true + ); + } + private async Task> GetAsyncDuplicateEntityName(string query, IQueryable dataQueryable, ODataQueryOptions options = null, QuerySettings querySettings = null, string customNamespace = null) { @@ -1662,13 +1833,14 @@ private Task> GetUsingCustomNameSpace(string 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 + private ODataQueryOptions GetODataQueryOptions(string query, string customNamespace = null, bool enableLowerCamelCase = false) where TModel : class { return ODataHelpers.GetODataQueryOptions ( query, serviceProvider, - customNamespace + customNamespace, + enableLowerCamelCase ); } } diff --git a/AutoMapper.OData.EFCore.Tests/GetTests.cs b/AutoMapper.OData.EFCore.Tests/GetTests.cs index 15067a2..12d891b 100644 --- a/AutoMapper.OData.EFCore.Tests/GetTests.cs +++ b/AutoMapper.OData.EFCore.Tests/GetTests.cs @@ -680,13 +680,14 @@ private ODataQueryOptions GetODataQueryOptions(string query, str public static class ODataHelpers { - public static ODataQueryOptions GetODataQueryOptions(string queryString, IServiceProvider serviceProvider, string customNamespace = null) where T : class + 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; - builder.EnableLowerCamelCase(); + if (enableLowerCamelCase) + builder.EnableLowerCamelCase(); builder.EntitySet(typeof(T).Name); IEdmModel model = builder.GetEdmModel(); diff --git a/AutoMapper.OData.EFCore.Tests/Mappings/ObjectMappings.cs b/AutoMapper.OData.EFCore.Tests/Mappings/ObjectMappings.cs index 155eb66..c0ebc53 100644 --- a/AutoMapper.OData.EFCore.Tests/Mappings/ObjectMappings.cs +++ b/AutoMapper.OData.EFCore.Tests/Mappings/ObjectMappings.cs @@ -28,6 +28,13 @@ public ObjectMappings() CreateMap() .ForAllMembers(o => o.ExplicitExpansion()); CreateMap(); + + CreateMap() + .ForAllMembers(o => o.ExplicitExpansion()); + CreateMap() + .ForAllMembers(o => o.ExplicitExpansion()); + CreateMap() + .ForAllMembers(o => o.ExplicitExpansion()); } } } diff --git a/AutoMapper.OData.EFCore.Tests/Model/ModelClasses.cs b/AutoMapper.OData.EFCore.Tests/Model/ModelClasses.cs index 0496f88..891a609 100644 --- a/AutoMapper.OData.EFCore.Tests/Model/ModelClasses.cs +++ b/AutoMapper.OData.EFCore.Tests/Model/ModelClasses.cs @@ -192,6 +192,51 @@ public enum LongEnumModel : long ThirdLong, FourthLong } + + public class Organization + { + public int Id { get; set; } + public virtual ICollection Identities { get; set; } + public virtual ICollection Names { get; set; } + } + + public class OrganizationName + { + public int Id { get; set; } + public int LangId { get; set; } + public string Value { get; set; } + } + + public class OrganizationIdentity + { + public int Id { get; set; } + public string Value { get; set; } + public int TypeId { get; set; } + public int OrganizationId { get; set; } + public Organization Organization { get; set; } + } + + public class OrganizationDto + { + public int Id { get; set; } + public virtual ICollection Identities { get; set; } + public virtual ICollection Names { get; set; } + } + + public class OrganizationNameDto + { + public int Id { get; set; } + public int LangId { get; set; } + public string Value { get; set; } + } + + public class OrganizationIdentityDto + { + public int Id { get; set; } + public string Value { get; set; } + public int TypeId { get; set; } + public OrganizationDto Organization { get; set; } + } } namespace X From 1b1d17076ce1cec7fccf3947099fda3906c0c0e6 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Sat, 15 Feb 2025 11:11:21 -0500 Subject: [PATCH 2/4] README.md --- .../AutoMapper.AspNetCore.OData.EFCore.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj index 0565b10..34e20ab 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj +++ b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj @@ -12,6 +12,7 @@ https://github.com/AutoMapper/AutoMapper.Extensions.OData MIT https://github.com/AutoMapper/AutoMapper.Extensions.OData + README.md ..\AutoMapper.snk true v @@ -25,6 +26,7 @@ + From cca72e7b904b5dd65c243232d6b21194d3a92f1d Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Sat, 15 Feb 2025 11:12:11 -0500 Subject: [PATCH 3/4] README.md --- .../AutoMapper.AspNetCore.OData.EFCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj index 34e20ab..37f0fc4 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj +++ b/AutoMapper.AspNetCore.OData.EFCore/AutoMapper.AspNetCore.OData.EFCore.csproj @@ -26,7 +26,7 @@ - + From e5aa57e442421f8b3a92850123b7fd9fd3f36f78 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Sat, 15 Feb 2025 11:26:34 -0500 Subject: [PATCH 4/4] Fixing package release notes. --- .../AutoMapper.AspNetCore.OData.EF6.csproj | 2 +- .../AutoMapper.AspNetCore.OData.EFCore.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj b/AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj index eac52a8..1cfef47 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 v13 (EF Core only). + Supporting AutoMapper v14 (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 37f0fc4..8193d76 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 v13 (EF Core only). + Supporting AutoMapper v14 (EF Core only). linq expressions odata efcore icon.png https://github.com/AutoMapper/AutoMapper.Extensions.OData