├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── AutoMapper.Extensions.ExpressionMapping.sln ├── AutoMapper.snk ├── Directory.Build.props ├── LICENSE ├── Pack_Push.ps1 ├── README.md ├── icon.png ├── src └── AutoMapper.Extensions.ExpressionMapping │ ├── AnonymousTypeFactory.cs │ ├── AutoMapper.Extensions.ExpressionMapping.csproj │ ├── ConfigurationExtensions.cs │ ├── ElementTypeHelper.cs │ ├── ExpressionExtensions.cs │ ├── ExpressionMapper.cs │ ├── Extensions │ └── VisitorExtensions.cs │ ├── FindMemberExpressionsVisitor.cs │ ├── Impl │ ├── ISourceInjectedQueryable.cs │ ├── QueryDataSourceInjection.cs │ ├── SourceInjectedQuery.cs │ ├── SourceInjectedQueryInspector.cs │ └── SourceInjectedQueryProvider.cs │ ├── LockingConcurrentDictionary.cs │ ├── MapIncludesVisitor.cs │ ├── MapperExtensions.cs │ ├── MapperInfoDictionary.cs │ ├── MemberVisitor.cs │ ├── NullsafeQueryRewriter.cs │ ├── ParameterExpressionEqualityComparer.cs │ ├── PrependParentNameVisitor.cs │ ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx │ ├── QueryableExtensions.cs │ ├── ReflectionExtensions.cs │ ├── ReplaceExpressionVisitor.cs │ ├── Structures │ ├── DeclaringMemberKey.cs │ ├── MapperInfo.cs │ ├── MemberAssignmentInfo.cs │ ├── MemberBindingGroup.cs │ └── PropertyMapInfo.cs │ ├── TypeExtensions.cs │ ├── TypeMapHelper.cs │ └── XpressionMapperVisitor.cs └── tests └── AutoMapper.Extensions.ExpressionMapping.UnitTests ├── AssertionExtensions.cs ├── AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj ├── AutoMapperSpecBase.cs ├── CanMapExpressionWithListConstants.cs ├── CanMapExpressionWithLocalExpressionConstant.cs ├── CanMapMemberFromTypeBinaryExpression.cs ├── CanMapMismatchedLiteralMemberExpressionsWithoutCustomExpressions.cs ├── CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs ├── CanMapParameterBodyWithoutMemberExpression.cs ├── EF.cs ├── EnumerableDotContainsWorksOnLocalVariables.cs ├── ExplicitExpansionAsDataSource.cs ├── ExpressionConversion.cs ├── ExpressionMapping.cs ├── ExpressionMappingEnumToNumericOrString.cs ├── ExpressionMappingPropertyFromBaseClass.cs ├── ExpressionMappingPropertyFromDerviedType.cs ├── ExpressionMappingWithUseAsDataSource.cs ├── GenericTestExtensionMethods.cs ├── Impl └── SourceInjectedQuery.cs ├── MappingMemberInitWithCustomExpressions.cs ├── MappingMemberInitWithPropertiesInBaseClass.cs ├── MappingWithIncludeMembersConfigurations.cs ├── MemberMappingsOfLiteralParentTypesMustMatch.cs ├── ParameterizedQueriesTestsAsDataSource.cs ├── ShouldOnlyMapExistingTypeMaps.cs ├── ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs ├── ShouldUseDeclaringTypeForInstanceMethodCalls.cs ├── XpressionMapper.ForPath.Tests.cs ├── XpressionMapper.Structs.Tests.cs └── XpressionMapperTests.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | core.eol crlf 17 | 18 | *.cs diff=csharp 19 | 20 | *.csproj merge=union 21 | *.vbproj merge=union 22 | *.fsproj merge=union 23 | *.dbproj merge=union 24 | *.sln merge=union 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Add AutoMapper Myget Source 20 | run: dotnet nuget add source https://www.myget.org/F/automapperdev/api/v3/index.json -n automappermyget 21 | 22 | - name: Test 23 | run: dotnet test --configuration Release --verbosity normal 24 | 25 | - name: Pack and push 26 | env: 27 | PROJECT_NAME: AutoMapper.Extensions.ExpressionMapping 28 | DEPLOY_PACKAGE_URL: https://www.myget.org/F/automapperdev/api/v3/index.json 29 | DEPLOY_PACKAGE_API_KEY: ${{ secrets.MYGET_CI_API_KEY }} 30 | REPO: ${{ github.repository }} 31 | REPO_OWNER: ${{ github.repository_owner }} 32 | run: ./Pack_Push.ps1 33 | shell: pwsh 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Test 18 | run: dotnet test --configuration Release --verbosity normal 19 | 20 | - name: Pack and push 21 | env: 22 | PROJECT_NAME: AutoMapper.Extensions.ExpressionMapping 23 | DEPLOY_PACKAGE_URL: https://api.nuget.org/v3/index.json 24 | DEPLOY_PACKAGE_API_KEY: ${{ secrets.NUGET_API_KEY }} 25 | REPO: ${{ github.repository }} 26 | REPO_OWNER: ${{ github.repository_owner }} 27 | run: ./Pack_Push.ps1 28 | shell: pwsh 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | artifacts/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | *.ncrunchsolution 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Windows Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Windows Store app package directory 160 | AppPackages/ 161 | 162 | # Visual Studio cache files 163 | # files ending in .cache can be ignored 164 | *.[Cc]ache 165 | # but keep track of directories ending in .cache 166 | !*.[Cc]ache/ 167 | 168 | # Others 169 | ClientBin/ 170 | [Ss]tyle[Cc]op.* 171 | ~$* 172 | *~ 173 | *.dbmdl 174 | *.dbproj.schemaview 175 | *.pfx 176 | *.publishsettings 177 | node_modules/ 178 | orleans.codegen.cs 179 | 180 | # RIA/Silverlight projects 181 | Generated_Code/ 182 | 183 | # Backup & report files from converting an old project file 184 | # to a newer Visual Studio version. Backup files are not needed, 185 | # because we have git ;-) 186 | _UpgradeReport_Files/ 187 | Backup*/ 188 | UpgradeLog*.XML 189 | UpgradeLog*.htm 190 | 191 | # SQL Server files 192 | *.mdf 193 | *.ldf 194 | 195 | # Business Intelligence projects 196 | *.rdl.data 197 | *.bim.layout 198 | *.bim_*.settings 199 | 200 | # Microsoft Fakes 201 | FakesAssemblies/ 202 | 203 | # Node.js Tools for Visual Studio 204 | .ntvs_analysis.dat 205 | 206 | # Visual Studio 6 build log 207 | *.plg 208 | 209 | # Visual Studio 6 workspace options file 210 | *.opt 211 | 212 | # Visual Studio LightSwitch build output 213 | **/*.HTMLClient/GeneratedArtifacts 214 | **/*.DesktopClient/GeneratedArtifacts 215 | **/*.DesktopClient/ModelManifest.xml 216 | **/*.Server/GeneratedArtifacts 217 | **/*.Server/ModelManifest.xml 218 | _Pvt_Extensions 219 | 220 | # Project Rider 221 | *.iml 222 | .idea -------------------------------------------------------------------------------- /AutoMapper.Extensions.ExpressionMapping.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29001.49 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Extensions.ExpressionMapping", "src\AutoMapper.Extensions.ExpressionMapping\AutoMapper.Extensions.ExpressionMapping.csproj", "{24DF305C-EE59-460A-BA97-4B7CD5505434}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Extensions.ExpressionMapping.UnitTests", "tests\AutoMapper.Extensions.ExpressionMapping.UnitTests\AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj", "{31D058FF-FD83-4DB3-9C32-1D3599687A8E}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{792DF9DF-A7ED-449E-B358-57953FC62B1D}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitattributes = .gitattributes 13 | .gitignore = .gitignore 14 | .github\workflows\ci.yml = .github\workflows\ci.yml 15 | Directory.Build.props = Directory.Build.props 16 | LICENSE = LICENSE 17 | Pack_Push.ps1 = Pack_Push.ps1 18 | README.md = README.md 19 | .github\workflows\release.yml = .github\workflows\release.yml 20 | EndProjectSection 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|x64 = Debug|x64 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|x64 = Release|x64 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|x64.Build.0 = Debug|Any CPU 36 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Debug|x86.Build.0 = Debug|Any CPU 38 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|x64.ActiveCfg = Release|Any CPU 41 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|x64.Build.0 = Release|Any CPU 42 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|x86.ActiveCfg = Release|Any CPU 43 | {24DF305C-EE59-460A-BA97-4B7CD5505434}.Release|x86.Build.0 = Release|Any CPU 44 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|x64.ActiveCfg = Debug|Any CPU 47 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|x64.Build.0 = Debug|Any CPU 48 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Debug|x86.Build.0 = Debug|Any CPU 50 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|x64.ActiveCfg = Release|Any CPU 53 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|x64.Build.0 = Release|Any CPU 54 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|x86.ActiveCfg = Release|Any CPU 55 | {31D058FF-FD83-4DB3-9C32-1D3599687A8E}.Release|x86.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {7464B224-B1DB-4F63-89AE-34DDBF794E15} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /AutoMapper.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/b8b4900d4331d2bb42487484621b42649094fcc4/AutoMapper.snk -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Jimmy Bogard 4 | latest 5 | true 6 | $(NoWarn);1701;1702;1591 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Jimmy Bogard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Pack_Push.ps1: -------------------------------------------------------------------------------- 1 | $scriptName = $MyInvocation.MyCommand.Name 2 | 3 | Write-Host "Owner ${Env:REPO_OWNER}" 4 | Write-Host "Repository ${Env:REPO}" 5 | 6 | $PROJECT_PATH = ".\src\$($Env:PROJECT_NAME)\$($Env:PROJECT_NAME).csproj" 7 | $NUGET_PACKAGE_PATH = ".\artifacts\$($Env:PROJECT_NAME).*.nupkg" 8 | 9 | Write-Host "Project Path ${PROJECT_PATH}" 10 | Write-Host "Package Path ${NUGET_PACKAGE_PATH}" 11 | 12 | if ([string]::IsNullOrEmpty($Env:DEPLOY_PACKAGE_API_KEY)) { 13 | Write-Host "${scriptName}: Only creates packages on AutoMapper repositories." 14 | } else { 15 | dotnet pack $PROJECT_PATH -c Release -o .\artifacts --no-build 16 | dotnet nuget push $NUGET_PACKAGE_PATH --skip-duplicate --source $Env:DEPLOY_PACKAGE_URL --api-key $Env:DEPLOY_PACKAGE_API_KEY 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OData 2 | AutoMapper extentions for mapping expressions (OData) 3 | 4 | [![NuGet](http://img.shields.io/nuget/v/AutoMapper.Extensions.ExpressionMapping.svg)](https://www.nuget.org/packages/AutoMapper.Extensions.ExpressionMapping/) 5 | 6 | To use, configure using the configuration helper method: 7 | 8 | ```c# 9 | var mapper = new Mapper(new MapperConfiguration(cfg => { 10 | cfg.AddExpressionMapping(); 11 | // Rest of your configuration 12 | })); 13 | 14 | // or if using the MS Ext DI: 15 | 16 | services.AddAutoMapper(cfg => { 17 | cfg.AddExpressionMapping(); 18 | }, /* assemblies with profiles */); 19 | ``` 20 | 21 | ## DTO Queries 22 | Expression Mapping also supports writing queries against the mapped objects. Take the following source and destination types: 23 | ```csharp 24 | public class User 25 | { 26 | public int Id { get; set; } 27 | public string Name { get; set; } 28 | } 29 | 30 | public class Request 31 | { 32 | public int Id { get; set; } 33 | public int AssigneeId { get; set; } 34 | public User Assignee { get; set; } 35 | } 36 | 37 | public class UserDTO 38 | { 39 | public int Id { get; set; } 40 | public string Name { get; set; } 41 | } 42 | 43 | public class RequestDTO 44 | { 45 | public int Id { get; set; } 46 | public UserDTO Assignee { get; set; } 47 | } 48 | ``` 49 | 50 | We can write LINQ expressions against the DTO collections. 51 | ```csharp 52 | ICollection requests = await context.Request.GetItemsAsync(mapper, r => r.Id > 0 && r.Id < 3, null, new List, IIncludableQueryable>>>() { item => item.Include(s => s.Assignee) }); 53 | ICollection users = await context.User.GetItemsAsync(mapper, u => u.Id > 0 && u.Id < 4, q => q.OrderBy(u => u.Name)); 54 | int count = await context.Request.Query(mapper, q => q.Count(r => r.Id > 1)); 55 | ``` 56 | The methods below map the DTO query expresions to the equivalent data query expressions. The call to IMapper.Map converts the data query results back to the DTO (or model) object types. 57 | ```csharp 58 | static class Extensions 59 | { 60 | internal static async Task Query(this IQueryable query, IMapper mapper, 61 | Expression, TModelResult>> queryFunc) where TData : class 62 | { 63 | //Map the expressions 64 | Func, TDataResult> mappedQueryFunc = mapper.MapExpression, TDataResult>>>(queryFunc).Compile(); 65 | 66 | //execute the query 67 | return mapper.Map(mappedQueryFunc(query)); 68 | } 69 | 70 | internal static async Task> GetItemsAsync(this IQueryable query, IMapper mapper, 71 | Expression> filter = null, 72 | Expression, IQueryable>> queryFunc = null, 73 | ICollection, IIncludableQueryable>>> includeProperties = null) 74 | { 75 | //Map the expressions 76 | Expression> f = mapper.MapExpression>>(filter); 77 | Func, IQueryable> mappedQueryFunc = mapper.MapExpression, IQueryable>>>(queryFunc)?.Compile(); 78 | ICollection, IIncludableQueryable>>> includes = mapper.MapIncludesList, IIncludableQueryable>>>(includeProperties); 79 | 80 | if (f != null) 81 | query = query.Where(f); 82 | 83 | if (includes != null) 84 | query = includes.Select(i => i.Compile()).Aggregate(query, (list, next) => query = next(query)); 85 | 86 | //Call the store 87 | ICollection result = mappedQueryFunc != null ? await mappedQueryFunc(query).ToListAsync() : await query.ToListAsync(); 88 | 89 | //Map and return the data 90 | return mapper.Map, IEnumerable>(result).ToList(); 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/b8b4900d4331d2bb42487484621b42649094fcc4/icon.png -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping 8 | { 9 | internal static class AnonymousTypeFactory 10 | { 11 | private static int classCount; 12 | 13 | public static Type CreateAnonymousType(IEnumerable memberDetails) 14 | => CreateAnonymousType(memberDetails.ToDictionary(key => key.Name, element => element.GetMemberType())); 15 | 16 | public static Type CreateAnonymousType(IDictionary memberDetails) 17 | { 18 | AssemblyName dynamicAssemblyName = new AssemblyName("TempAssembly.AutoMapper.Extensions.ExpressionMapping"); 19 | AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run); 20 | ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("TempAssembly.AutoMapper.Extensions.ExpressionMapping"); 21 | TypeBuilder typeBuilder = dynamicModule.DefineType(GetAnonymousTypeName(), TypeAttributes.Public); 22 | MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; 23 | 24 | var builders = memberDetails.Select 25 | ( 26 | info => 27 | { 28 | Type memberType = info.Value; 29 | string memberName = info.Key; 30 | return new 31 | { 32 | FieldBuilder = typeBuilder.DefineField(string.Concat("_", memberName), memberType, FieldAttributes.Private), 33 | PropertyBuilder = typeBuilder.DefineProperty(memberName, PropertyAttributes.HasDefault, memberType, null), 34 | GetMethodBuilder = typeBuilder.DefineMethod(string.Concat("get_", memberName), getSetAttr, memberType, Type.EmptyTypes), 35 | SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, new Type[] { memberType }) 36 | }; 37 | } 38 | ); 39 | 40 | builders.ToList().ForEach(builder => 41 | { 42 | ILGenerator getMethodIL = builder.GetMethodBuilder.GetILGenerator(); 43 | getMethodIL.Emit(OpCodes.Ldarg_0); 44 | getMethodIL.Emit(OpCodes.Ldfld, builder.FieldBuilder); 45 | getMethodIL.Emit(OpCodes.Ret); 46 | 47 | ILGenerator setMethodIL = builder.SetMethodBuilder.GetILGenerator(); 48 | setMethodIL.Emit(OpCodes.Ldarg_0); 49 | setMethodIL.Emit(OpCodes.Ldarg_1); 50 | setMethodIL.Emit(OpCodes.Stfld, builder.FieldBuilder); 51 | setMethodIL.Emit(OpCodes.Ret); 52 | 53 | builder.PropertyBuilder.SetGetMethod(builder.GetMethodBuilder); 54 | builder.PropertyBuilder.SetSetMethod(builder.SetMethodBuilder); 55 | }); 56 | 57 | return typeBuilder.CreateTypeInfo().AsType(); 58 | } 59 | 60 | private static string GetAnonymousTypeName() 61 | => $"AnonymousType{++classCount}"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Expression mapping (OData) extensions for AutoMapper 5 | Expression mapping (OData) extensions for AutoMapper 6 | net8.0 7 | true 8 | ..\..\AutoMapper.snk 9 | true 10 | true 11 | AutoMapper.Extensions.ExpressionMapping 12 | icon.png 13 | https://automapper.org 14 | MIT 15 | git 16 | https://github.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping 17 | v 18 | true 19 | true 20 | snupkg 21 | true 22 | true 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | 40 | 41 | 42 | True 43 | True 44 | Resources.resx 45 | 46 | 47 | 48 | 49 | 50 | ResXFileCodeGenerator 51 | Resources.Designer.cs 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper.Internal; 2 | using AutoMapper.Mappers; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping 5 | { 6 | public static class ConfigurationExtensions 7 | { 8 | public static IMapperConfigurationExpression AddExpressionMapping(this IMapperConfigurationExpression config) 9 | { 10 | config.Internal().Mappers.Insert(0, new ExpressionMapper()); 11 | return config; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ElementTypeHelper.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping 8 | { 9 | public static class ElementTypeHelper 10 | { 11 | public static Type GetElementType(Type enumerableType) => GetElementTypes(enumerableType, null)[0]; 12 | 13 | public static Type[] GetElementTypes(Type enumerableType, ElementTypeFlags flags = ElementTypeFlags.None) => 14 | GetElementTypes(enumerableType, null, flags); 15 | 16 | public static Type[] GetElementTypes(Type enumerableType, System.Collections.IEnumerable enumerable, 17 | ElementTypeFlags flags = ElementTypeFlags.None) 18 | { 19 | if (enumerableType.HasElementType) 20 | { 21 | return new[] { enumerableType.GetElementType() }; 22 | } 23 | 24 | var iDictionaryType = enumerableType.GetDictionaryType(); 25 | if (iDictionaryType != null && flags.HasFlag(ElementTypeFlags.BreakKeyValuePair)) 26 | { 27 | return iDictionaryType.GetTypeInfo().GenericTypeArguments; 28 | } 29 | 30 | var iReadOnlyDictionaryType = enumerableType.GetReadOnlyDictionaryType(); 31 | if (iReadOnlyDictionaryType != null && flags.HasFlag(ElementTypeFlags.BreakKeyValuePair)) 32 | { 33 | return iReadOnlyDictionaryType.GetTypeInfo().GenericTypeArguments; 34 | } 35 | 36 | var iEnumerableType = enumerableType.GetIEnumerableType(); 37 | if (iEnumerableType != null) 38 | { 39 | return iEnumerableType.GetTypeInfo().GenericTypeArguments; 40 | } 41 | 42 | if (typeof(System.Collections.IEnumerable).IsAssignableFrom(enumerableType)) 43 | { 44 | var first = enumerable?.Cast().FirstOrDefault(); 45 | 46 | return new[] { first?.GetType() ?? typeof(object) }; 47 | } 48 | 49 | throw new ArgumentException($"Unable to find the element type for type '{enumerableType}'.", 50 | nameof(enumerableType)); 51 | } 52 | 53 | public static Type GetReadOnlyDictionaryType(this Type type) => type.GetGenericInterface(typeof(IReadOnlyDictionary<,>)); 54 | public static Type GetDictionaryType(this Type type) => type.GetGenericInterface(typeof(IDictionary<,>)); 55 | 56 | public static System.Linq.Expressions.Expression ToType(System.Linq.Expressions.Expression expression, Type type) => expression.Type == type ? expression : System.Linq.Expressions.Expression.Convert(expression, type); 57 | } 58 | public enum ElementTypeFlags 59 | { 60 | None = 0, 61 | BreakKeyValuePair = 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using AutoMapper.Internal; 7 | 8 | namespace AutoMapper 9 | { 10 | using AutoMapper.Extensions.ExpressionMapping; 11 | using static Expression; 12 | 13 | internal static class ExpressionExtensions 14 | { 15 | public static Expression MemberAccesses(this IEnumerable members, Expression obj) => 16 | members.Aggregate(obj, (expression, member) => MakeMemberAccess(expression, member)); 17 | 18 | public static IEnumerable GetMembers(this Expression expression) 19 | { 20 | var memberExpression = expression as MemberExpression; 21 | if(memberExpression == null) 22 | { 23 | return new MemberExpression[0]; 24 | } 25 | return memberExpression.GetMembers(); 26 | } 27 | 28 | public static IEnumerable GetMembers(this MemberExpression expression) 29 | { 30 | while(expression != null) 31 | { 32 | yield return expression; 33 | expression = expression.Expression as MemberExpression; 34 | } 35 | } 36 | 37 | public static bool IsMemberPath(this LambdaExpression exp) 38 | { 39 | return exp.Body.GetMembers().LastOrDefault()?.Expression == exp.Parameters.First(); 40 | } 41 | } 42 | 43 | internal static class ExpressionHelpers 44 | { 45 | public static MemberExpression MemberAccesses(string members, Expression obj) => 46 | (MemberExpression)GetMemberPath(obj.Type, members).MemberAccesses(obj); 47 | 48 | public static Expression ReplaceParameters(this LambdaExpression exp, params Expression[] replace) 49 | { 50 | var replaceExp = exp.Body; 51 | for (var i = 0; i < Math.Min(replace.Length, exp.Parameters.Count); i++) 52 | replaceExp = Replace(replaceExp, exp.Parameters[i], replace[i]); 53 | return replaceExp; 54 | } 55 | 56 | public static Expression Replace(this Expression exp, Expression old, Expression replace) => new ReplaceExpressionVisitor(old, replace).Visit(exp); 57 | 58 | private static IEnumerable GetMemberPath(Type type, string fullMemberName) 59 | { 60 | MemberInfo property = null; 61 | foreach (var memberName in fullMemberName.Split('.')) 62 | { 63 | var currentType = GetCurrentType(property, type); 64 | yield return property = currentType.GetFieldOrProperty(memberName); 65 | } 66 | } 67 | 68 | private static Type GetCurrentType(MemberInfo member, Type type) 69 | => member?.GetMemberType() ?? type; 70 | } 71 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using AutoMapper.Extensions.ExpressionMapping.Structures; 8 | using AutoMapper.Internal; 9 | 10 | namespace AutoMapper.Extensions.ExpressionMapping.Extensions 11 | { 12 | internal static class VisitorExtensions 13 | { 14 | /// 15 | /// Returns true if the expression is a direct or descendant member expression of the parameter. 16 | /// 17 | /// 18 | /// 19 | public static bool IsMemberExpression(this Expression expression) 20 | { 21 | if (expression.NodeType == ExpressionType.MemberAccess) 22 | { 23 | var memberExpression = (MemberExpression)expression; 24 | return IsMemberOrParameterExpression(memberExpression.Expression); 25 | } 26 | 27 | return false; 28 | } 29 | 30 | private static bool IsMemberOrParameterExpression(Expression expression) 31 | { 32 | //the node represents parameter of the expression 33 | switch (expression.NodeType) 34 | { 35 | case ExpressionType.Parameter: 36 | return true; 37 | case ExpressionType.MemberAccess: 38 | var memberExpression = (MemberExpression)expression; 39 | return IsMemberOrParameterExpression(memberExpression.Expression); 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /// 46 | /// Returns the fully qualified name of the member starting with the immediate child member of the parameter 47 | /// 48 | /// 49 | /// 50 | public static string GetPropertyFullName(this Expression expression) 51 | { 52 | if (expression == null) 53 | return string.Empty; 54 | 55 | const string period = "."; 56 | 57 | //the node represents parameter of the expression 58 | switch (expression.NodeType) 59 | { 60 | case ExpressionType.MemberAccess: 61 | var memberExpression = (MemberExpression)expression; 62 | var parentFullName = memberExpression.Expression.GetPropertyFullName(); 63 | return string.IsNullOrEmpty(parentFullName) 64 | ? memberExpression.Member.Name 65 | : string.Concat(memberExpression.Expression.GetPropertyFullName(), period, memberExpression.Member.Name); 66 | default: 67 | return string.Empty; 68 | } 69 | } 70 | 71 | public static Expression GetUnconvertedExpression(this Expression expression) 72 | { 73 | switch (expression.NodeType) 74 | { 75 | case ExpressionType.Convert: 76 | case ExpressionType.ConvertChecked: 77 | case ExpressionType.TypeAs: 78 | return ((UnaryExpression)expression).Operand.GetUnconvertedExpression(); 79 | default: 80 | return expression; 81 | } 82 | } 83 | 84 | public static Expression ConvertTypeIfNecessary(this Expression expression, Type memberType) 85 | { 86 | if (memberType == expression.Type) 87 | return expression; 88 | 89 | expression = expression.GetUnconvertedExpression(); 90 | if (memberType != expression.Type) 91 | return Expression.Convert(expression, memberType); 92 | 93 | return expression; 94 | } 95 | 96 | public static MemberExpression GetMemberExpression(this LambdaExpression expr) 97 | => expr.Body.GetUnconvertedExpression() as MemberExpression; 98 | 99 | public static MemberExpression GetMemberExpression(this Expression expr) 100 | => expr.GetUnconvertedExpression() as MemberExpression; 101 | 102 | /// 103 | /// Returns the ParameterExpression for the LINQ parameter. 104 | /// 105 | /// 106 | /// 107 | public static ParameterExpression GetParameterExpression(this Expression expression) 108 | { 109 | if (expression == null) 110 | return null; 111 | 112 | //the node represents parameter of the expression 113 | switch (expression.NodeType) 114 | { 115 | case ExpressionType.Parameter: 116 | return (ParameterExpression)expression; 117 | case ExpressionType.Quote: 118 | return GetParameterExpression(((UnaryExpression)expression).Operand); 119 | case ExpressionType.Lambda: 120 | return GetParameterExpression(((LambdaExpression)expression).Body); 121 | case ExpressionType.ConvertChecked: 122 | case ExpressionType.Convert: 123 | var ue = expression as UnaryExpression; 124 | return GetParameterExpression(ue?.Operand); 125 | case ExpressionType.TypeAs: 126 | return ((UnaryExpression)expression).Operand.GetParameterExpression(); 127 | case ExpressionType.TypeIs: 128 | return ((TypeBinaryExpression)expression).Expression.GetParameterExpression(); 129 | case ExpressionType.MemberAccess: 130 | return GetParameterExpression(((MemberExpression)expression).Expression); 131 | case ExpressionType.Call: 132 | var methodExpression = expression as MethodCallExpression; 133 | var parentExpression = methodExpression?.Object;//Method is an instance method 134 | 135 | var isExtension = methodExpression != null && methodExpression.Method.IsDefined(typeof(ExtensionAttribute), true); 136 | if (isExtension && parentExpression == null && methodExpression.Arguments.Count > 0) 137 | parentExpression = methodExpression.Arguments[0];//Method is an extension method based on the type of methodExpression.Arguments[0]. 138 | 139 | return isExtension && parentExpression == null && methodExpression.Arguments.Count > 0 140 | ? GetParameterExpression(methodExpression.Arguments[0]) 141 | : GetParameterExpression(parentExpression); 142 | } 143 | 144 | return null; 145 | } 146 | 147 | /// 148 | /// Returns the first ancestor node that is not a MemberExpression. 149 | /// 150 | /// 151 | /// 152 | public static Expression GetBaseOfMemberExpression(this MemberExpression expression) 153 | { 154 | if (expression.Expression == null) 155 | return null; 156 | 157 | return expression.Expression.NodeType == ExpressionType.MemberAccess 158 | ? GetBaseOfMemberExpression((MemberExpression)expression.Expression) 159 | : expression.Expression; 160 | } 161 | 162 | /// 163 | /// Adds member expressions to an existing expression. 164 | /// 165 | /// 166 | /// 167 | /// 168 | public static MemberExpression MemberAccesses(this Expression exp, List list) => 169 | (MemberExpression) list.SelectMany(propertyMapInfo => propertyMapInfo.DestinationPropertyInfos).MemberAccesses(exp); 170 | 171 | /// 172 | /// For the given a Lambda Expression, returns the fully qualified name of the member starting with the immediate child member of the parameter 173 | /// 174 | /// 175 | /// 176 | public static string GetMemberFullName(this LambdaExpression expr) 177 | { 178 | if (expr.Body.NodeType == ExpressionType.Parameter) 179 | return string.Empty; 180 | 181 | MemberExpression me; 182 | switch (expr.Body.NodeType) 183 | { 184 | case ExpressionType.Convert: 185 | case ExpressionType.ConvertChecked: 186 | case ExpressionType.TypeAs: 187 | me = expr.Body.GetUnconvertedExpression() as MemberExpression; 188 | break; 189 | default: 190 | me = expr.Body as MemberExpression; 191 | break; 192 | } 193 | 194 | return me.GetPropertyFullName(); 195 | } 196 | 197 | /// 198 | /// Returns the underlying type typeof(T) when the type implements IEnumerable. 199 | /// 200 | /// 201 | /// 202 | public static List GetUnderlyingGenericTypes(this Type type) => 203 | type == null || !type.GetTypeInfo().IsGenericType 204 | ? new List() 205 | : type.GetGenericArguments().ToList(); 206 | 207 | public static bool IsEnumType(this Type type) 208 | { 209 | if (type.IsNullableType()) 210 | type = Nullable.GetUnderlyingType(type); 211 | 212 | return type.IsEnum(); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using AutoMapper.Internal; 7 | using AutoMapper.Extensions.ExpressionMapping.Extensions; 8 | 9 | namespace AutoMapper.Extensions.ExpressionMapping 10 | { 11 | internal class FindMemberExpressionsVisitor : ExpressionVisitor 12 | { 13 | internal FindMemberExpressionsVisitor(Expression newParentExpression) => _newParentExpression = newParentExpression; 14 | 15 | private readonly Expression _newParentExpression; 16 | private readonly List _memberExpressions = new List(); 17 | 18 | public MemberExpression Result 19 | { 20 | get 21 | { 22 | const string period = "."; 23 | var fullNamesGrouped = _memberExpressions.Select(m => m.GetPropertyFullName()) 24 | .GroupBy(n => n) 25 | .Select(grp => grp.Key) 26 | .OrderBy(a => a.Length).ToList(); 27 | 28 | var member = fullNamesGrouped.Aggregate(string.Empty, (result, next) => 29 | { 30 | if (string.IsNullOrEmpty(result) || next.Contains(result)) 31 | result = next; 32 | else throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, 33 | Properties.Resources.includeExpressionTooComplex, 34 | string.Concat(_newParentExpression.Type.Name, period, result), 35 | string.Concat(_newParentExpression.Type.Name, period, next))); 36 | 37 | return result; 38 | }); 39 | 40 | return ExpressionHelpers.MemberAccesses(member, _newParentExpression); 41 | } 42 | } 43 | 44 | protected override Expression VisitMember(MemberExpression node) 45 | { 46 | var parameterExpression = node.GetParameterExpression(); 47 | var sType = parameterExpression?.Type; 48 | if (sType != null && _newParentExpression.Type == sType && node.IsMemberExpression()) 49 | { 50 | if (node.Expression.NodeType == ExpressionType.MemberAccess && node.Type.IsLiteralType()) 51 | _memberExpressions.Add((MemberExpression)node.Expression); 52 | else if (node.Expression.NodeType == ExpressionType.Parameter && node.Type.IsLiteralType()) 53 | throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.mappedMemberIsChildOfTheParameterFormat, node.GetPropertyFullName(), node.Type.FullName, sType.FullName)); 54 | else 55 | _memberExpressions.Add(node); 56 | } 57 | 58 | return base.VisitMember(node); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Impl/ISourceInjectedQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace AutoMapper.Extensions.ExpressionMapping.Impl 6 | { 7 | public interface ISourceInjectedQueryable : IQueryable 8 | { 9 | /// 10 | /// Called when [enumerated]. 11 | /// 12 | /// The enumeration handler. 13 | IQueryable OnEnumerated(Action> enumerationHandler); 14 | 15 | /// 16 | /// Casts itself to IQueryable<T> so no explicit casting is necessary 17 | /// 18 | /// A 19 | IQueryable AsQueryable(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using AutoMapper.QueryableExtensions; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping.Impl 9 | { 10 | using MemberPaths = IEnumerable>; 11 | using IObjectDictionary = IDictionary; 12 | 13 | public interface IQueryDataSourceInjection 14 | { 15 | /// 16 | /// Creates the mapped query with an optional inspector 17 | /// 18 | /// The type of the destination. 19 | /// 20 | ISourceInjectedQueryable For(); 21 | ISourceInjectedQueryable For(object parameters, params Expression>[] membersToExpand); 22 | ISourceInjectedQueryable For(params Expression>[] membersToExpand); 23 | ISourceInjectedQueryable For(IObjectDictionary parameters, params string[] membersToExpand); 24 | 25 | IQueryDataSourceInjection UsingInspector(SourceInjectedQueryInspector inspector); 26 | 27 | /// 28 | /// ExpressionVisitors called before MappingVisitor itself is executed 29 | /// 30 | /// The visitors. 31 | /// 32 | IQueryDataSourceInjection BeforeProjection(params ExpressionVisitor[] visitors); 33 | 34 | /// 35 | /// ExpressionVisitors called after the MappingVisitor itself is executed 36 | /// 37 | /// The visitors. 38 | /// 39 | IQueryDataSourceInjection AfterProjection(params ExpressionVisitor[] visitors); 40 | 41 | /// 42 | /// Allows specifying a handler that will be called when the underlying QueryProvider encounters an exception. 43 | /// This is especially useful if you expose the resulting IQueryable in e.g. a WebApi controller where 44 | /// you do not call "ToList" yourself and therefore cannot catch exceptions 45 | /// 46 | /// The exception handler. 47 | /// 48 | IQueryDataSourceInjection OnError(Action exceptionHandler); 49 | } 50 | 51 | public class QueryDataSourceInjection : IQueryDataSourceInjection 52 | { 53 | private readonly IQueryable _dataSource; 54 | private readonly IMapper _mapper; 55 | private readonly List _beforeMappingVisitors = new List(); 56 | private readonly List _afterMappingVisitors = new List(); 57 | private readonly ExpressionVisitor _sourceExpressionTracer = null; 58 | private readonly ExpressionVisitor _destinationExpressionTracer = null; 59 | private Action _exceptionHandler = x => { }; 60 | private MemberPaths _membersToExpand; 61 | private IObjectDictionary _parameters; 62 | private SourceInjectedQueryInspector _inspector; 63 | 64 | public QueryDataSourceInjection(IQueryable dataSource, IMapper mapper) 65 | { 66 | _dataSource = dataSource; 67 | _mapper = mapper; 68 | } 69 | 70 | public ISourceInjectedQueryable For() => CreateQueryable(); 71 | 72 | public ISourceInjectedQueryable For(object parameters, params Expression>[] membersToExpand) 73 | { 74 | _parameters = GetParameters(parameters); 75 | _membersToExpand = ReflectionExtensions.GetMemberPaths(membersToExpand); 76 | return CreateQueryable(); 77 | } 78 | 79 | public ISourceInjectedQueryable For(params Expression>[] membersToExpand) 80 | { 81 | _membersToExpand = ReflectionExtensions.GetMemberPaths(membersToExpand); 82 | return CreateQueryable(); 83 | } 84 | 85 | public ISourceInjectedQueryable For(IObjectDictionary parameters, params string[] membersToExpand) 86 | { 87 | _parameters = parameters; 88 | _membersToExpand = ReflectionExtensions.GetMemberPaths(typeof(TDestination), membersToExpand); 89 | return CreateQueryable(); 90 | } 91 | 92 | public IQueryDataSourceInjection UsingInspector(SourceInjectedQueryInspector inspector) 93 | { 94 | _inspector = inspector; 95 | 96 | if (_sourceExpressionTracer != null) 97 | _beforeMappingVisitors.Insert(0, _sourceExpressionTracer); 98 | if (_destinationExpressionTracer != null) 99 | _afterMappingVisitors.Add(_destinationExpressionTracer); 100 | 101 | return this; 102 | } 103 | 104 | /// 105 | /// ExpressionVisitors called before MappingVisitor itself is executed 106 | /// 107 | /// The visitors. 108 | /// 109 | public IQueryDataSourceInjection BeforeProjection(params ExpressionVisitor[] visitors) 110 | { 111 | foreach (var visitor in visitors.Where(visitor => !_beforeMappingVisitors.Contains(visitor))) 112 | { 113 | _beforeMappingVisitors.Add(visitor); 114 | } 115 | return this; 116 | } 117 | 118 | /// 119 | /// ExpressionVisitors called after the MappingVisitor itself is executed 120 | /// 121 | /// The visitors. 122 | /// 123 | public IQueryDataSourceInjection AfterProjection(params ExpressionVisitor[] visitors) 124 | { 125 | foreach (var visitor in visitors.Where(visitor => !_afterMappingVisitors.Contains(visitor))) 126 | { 127 | _afterMappingVisitors.Add(visitor); 128 | } 129 | return this; 130 | } 131 | 132 | /// 133 | /// Allows specifying a handler that will be called when the underlying QueryProvider encounters an exception. 134 | /// This is especially useful if you expose the resulting IQueryable in e.g. a WebApi controller where 135 | /// you do not call "ToList" yourself and therefore cannot catch exceptions 136 | /// 137 | /// The exception handler. 138 | /// 139 | public IQueryDataSourceInjection OnError(Action exceptionHandler) 140 | { 141 | _exceptionHandler = exceptionHandler; 142 | return this; 143 | } 144 | 145 | private ISourceInjectedQueryable CreateQueryable() => 146 | new SourceSourceInjectedQuery(_dataSource, 147 | new TDestination[0].AsQueryable(), 148 | _mapper, 149 | _beforeMappingVisitors, 150 | _afterMappingVisitors, 151 | _exceptionHandler, 152 | _parameters, 153 | _membersToExpand, 154 | _inspector); 155 | 156 | private static IObjectDictionary GetParameters(object parameters) 157 | { 158 | return (parameters ?? new object()).GetType() 159 | .GetDeclaredProperties() 160 | .ToDictionary(pi => pi.Name, pi => pi.GetValue(parameters, null)); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping.Impl 9 | { 10 | using IObjectDictionary = IDictionary; 11 | using MemberPaths = IEnumerable>; 12 | 13 | public class SourceSourceInjectedQuery : IOrderedQueryable, ISourceInjectedQueryable 14 | { 15 | private readonly Action _exceptionHandler; 16 | 17 | public SourceSourceInjectedQuery(IQueryable dataSource, 18 | IQueryable destQuery, 19 | IMapper mapper, 20 | IEnumerable beforeVisitors, 21 | IEnumerable afterVisitors, 22 | Action exceptionHandler, 23 | IObjectDictionary parameters, 24 | MemberPaths membersToExpand, 25 | SourceInjectedQueryInspector inspector) 26 | { 27 | Parameters = parameters; 28 | EnumerationHandler = (x => { }); 29 | Expression = destQuery.Expression; 30 | ElementType = typeof(TDestination); 31 | Provider = new SourceInjectedQueryProvider(mapper, dataSource, destQuery, beforeVisitors, afterVisitors, exceptionHandler, parameters, membersToExpand) 32 | { 33 | Inspector = inspector ?? new SourceInjectedQueryInspector() 34 | }; 35 | _exceptionHandler = exceptionHandler ?? (x => { }); 36 | } 37 | 38 | internal SourceSourceInjectedQuery(IQueryProvider provider, Expression expression, Action> enumerationHandler, Action exceptionHandler) 39 | { 40 | _exceptionHandler = exceptionHandler ?? (x => { }); 41 | Provider = provider; 42 | Expression = expression; 43 | EnumerationHandler = enumerationHandler ?? (x => { }); 44 | ElementType = typeof(TDestination); 45 | } 46 | 47 | public IQueryable OnEnumerated(Action> enumerationHandler) 48 | { 49 | EnumerationHandler = enumerationHandler ?? (x => { }); 50 | ((SourceInjectedQueryProvider)Provider).EnumerationHandler = EnumerationHandler; 51 | return this; 52 | } 53 | 54 | public IQueryable AsQueryable() => this; 55 | 56 | internal Action> EnumerationHandler { get; set; } 57 | internal IObjectDictionary Parameters { get; set; } 58 | 59 | public IEnumerator GetEnumerator() 60 | { 61 | try 62 | { 63 | var results = Provider.Execute>(Expression).Cast().ToArray(); 64 | EnumerationHandler(results); 65 | return results.Cast().GetEnumerator(); 66 | } 67 | catch (Exception x) 68 | { 69 | _exceptionHandler(x); 70 | throw; 71 | } 72 | } 73 | 74 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 75 | 76 | public Type ElementType { get; } 77 | public Expression Expression { get; } 78 | public IQueryProvider Provider { get; } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryInspector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping.Impl 5 | { 6 | public class SourceInjectedQueryInspector 7 | { 8 | public SourceInjectedQueryInspector() 9 | { 10 | SourceResult = (e,o) => { }; 11 | DestResult = o => { }; 12 | StartQueryExecuteInterceptor = (t, e) => { }; 13 | } 14 | public Action SourceResult { get; set; } 15 | public Action DestResult { get; set; } 16 | public Action StartQueryExecuteInterceptor { get; set; } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/LockingConcurrentDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | 5 | namespace AutoMapper 6 | { 7 | internal struct LockingConcurrentDictionary 8 | { 9 | private readonly ConcurrentDictionary> _dictionary; 10 | private readonly Func> _valueFactory; 11 | 12 | public LockingConcurrentDictionary(Func valueFactory) 13 | { 14 | _dictionary = new ConcurrentDictionary>(); 15 | _valueFactory = key => new Lazy(() => valueFactory(key)); 16 | } 17 | 18 | public TValue GetOrAdd(TKey key) => _dictionary.GetOrAdd(key, _valueFactory).Value; 19 | public TValue GetOrAdd(TKey key, Func> valueFactory) => _dictionary.GetOrAdd(key, valueFactory).Value; 20 | 21 | public TValue this[TKey key] 22 | { 23 | get => _dictionary[key].Value; 24 | set => _dictionary[key] = new Lazy(() => value); 25 | } 26 | 27 | public bool TryGetValue(TKey key, out TValue value) 28 | { 29 | if (_dictionary.TryGetValue(key, out Lazy lazy)) 30 | { 31 | value = lazy.Value; 32 | return true; 33 | } 34 | value = default(TValue); 35 | return false; 36 | } 37 | 38 | public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key); 39 | 40 | public ICollection Keys => _dictionary.Keys; 41 | } 42 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using AutoMapper.Extensions.ExpressionMapping.Extensions; 6 | using AutoMapper.Extensions.ExpressionMapping.Structures; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping 9 | { 10 | public class MapIncludesVisitor : XpressionMapperVisitor 11 | { 12 | public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary typeMappings) 13 | : base(mapper, configurationProvider, typeMappings) 14 | { 15 | } 16 | 17 | protected override Expression VisitLambda(Expression node) 18 | { 19 | if (!node.Body.Type.IsLiteralType()) 20 | return base.VisitLambda(node); 21 | 22 | var ex = this.Visit(node.Body); 23 | 24 | var mapped = Expression.Lambda(ex, node.GetDestinationParameterExpressions(this.InfoDictionary, this.TypeMappings)); 25 | this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type); 26 | return mapped; 27 | } 28 | 29 | protected override Expression VisitMember(MemberExpression node) 30 | { 31 | if (!node.Type.IsLiteralType()) 32 | return base.VisitMember(node); 33 | 34 | var parameterExpression = node.GetParameterExpression(); 35 | if (parameterExpression == null) 36 | return base.VisitMember(node); 37 | 38 | InfoDictionary.Add(parameterExpression, TypeMappings); 39 | return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List()); 40 | 41 | Expression GetMappedMemberExpression(Expression parentExpression, List propertyMapInfoList) 42 | { 43 | Expression mappedParentExpression = this.Visit(parentExpression); 44 | FindDestinationFullName(parentExpression.Type, mappedParentExpression.Type, node.GetPropertyFullName(), propertyMapInfoList); 45 | 46 | if (propertyMapInfoList.Any(x => x.CustomExpression != null))//CustomExpression takes precedence over DestinationPropertyInfo 47 | { 48 | return GetMemberExpression 49 | ( 50 | new FindMemberExpressionsVisitor(mappedParentExpression), 51 | GetMemberExpressionFromCustomExpression 52 | ( 53 | propertyMapInfoList, 54 | propertyMapInfoList.Last(x => x.CustomExpression != null), 55 | mappedParentExpression 56 | ) 57 | ); 58 | } 59 | 60 | return GetExpressionForInclude 61 | ( 62 | GetMemberExpressionFromMemberMaps 63 | ( 64 | BuildFullName(propertyMapInfoList), 65 | mappedParentExpression 66 | ) 67 | ); 68 | } 69 | 70 | Expression GetExpressionForInclude(MemberExpression memberExpression) 71 | => memberExpression.Type.IsLiteralType() ? memberExpression.Expression : memberExpression; 72 | 73 | MemberExpression GetMemberExpression(FindMemberExpressionsVisitor visitor, Expression mappedExpression) 74 | { 75 | visitor.Visit(mappedExpression); 76 | return visitor.Result; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/MapperInfoDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using AutoMapper.Extensions.ExpressionMapping.Structures; 5 | 6 | namespace AutoMapper.Extensions.ExpressionMapping 7 | { 8 | public class MapperInfoDictionary : Dictionary 9 | { 10 | public MapperInfoDictionary(ParameterExpressionEqualityComparer comparer) : base(comparer) 11 | { 12 | } 13 | 14 | //const string PREFIX = "p"; 15 | public void Add(ParameterExpression key, Dictionary typeMappings) 16 | { 17 | if (ContainsKey(key)) 18 | return; 19 | 20 | Add(key, typeMappings.ContainsKey(key.Type) 21 | ? new MapperInfo(Expression.Parameter(typeMappings[key.Type], key.Name), key.Type,typeMappings[key.Type]) 22 | : new MapperInfo(Expression.Parameter(key.Type, key.Name), key.Type, key.Type)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/MemberVisitor.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper.Execution; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping 8 | { 9 | public class MemberVisitor : ExpressionVisitor 10 | { 11 | public static IEnumerable GetMemberPath(Expression expression) 12 | { 13 | var memberVisitor = new MemberVisitor(); 14 | memberVisitor.Visit(expression); 15 | return memberVisitor.MemberPath; 16 | } 17 | 18 | protected override Expression VisitMember(MemberExpression node) 19 | { 20 | _members.AddRange(node.GetMemberExpressions().Select(e => e.Member)); 21 | return node; 22 | } 23 | 24 | private readonly List _members = new List(); 25 | public IEnumerable MemberPath => _members; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/NullsafeQueryRewriter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014-2018 Axel Heer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using System; 25 | using System.Collections.Generic; 26 | using System.Linq.Expressions; 27 | using System.Reflection; 28 | using System.Runtime.CompilerServices; 29 | 30 | namespace AutoMapper.Extensions.ExpressionMapping 31 | { 32 | /// 33 | /// Expression visitor for making member access null-safe. 34 | /// 35 | /// 36 | /// NullSafeQueryRewriter is copied from the NeinLinq project, licensed under the MIT license. 37 | /// Copyright (c) 2014-2018 Axel Heer. 38 | /// See https://github.com/axelheer/nein-linq/blob/master/src/NeinLinq/NullsafeQueryRewriter.cs 39 | /// 40 | internal class NullsafeQueryRewriter : ExpressionVisitor 41 | { 42 | static readonly LockingConcurrentDictionary Cache = new LockingConcurrentDictionary(Fallback); 43 | 44 | /// 45 | protected override Expression VisitMember(MemberExpression node) 46 | { 47 | if (node == null) 48 | throw new ArgumentNullException(nameof(node)); 49 | 50 | var target = Visit(node.Expression); 51 | 52 | if (!IsSafe(target)) 53 | { 54 | // insert null-check before accessing property or field 55 | return BeSafe(target, node, node.Update); 56 | } 57 | 58 | return node.Update(target); 59 | } 60 | 61 | /// 62 | protected override Expression VisitMethodCall(MethodCallExpression node) 63 | { 64 | if (node == null) 65 | throw new ArgumentNullException(nameof(node)); 66 | 67 | var target = Visit(node.Object); 68 | 69 | if (!IsSafe(target)) 70 | { 71 | // insert null-check before invoking instance method 72 | return BeSafe(target, node, fallback => node.Update(fallback, node.Arguments)); 73 | } 74 | 75 | var arguments = Visit(node.Arguments); 76 | 77 | if (IsExtensionMethod(node.Method) && !IsSafe(arguments[0])) 78 | { 79 | // insert null-check before invoking extension method 80 | return BeSafe(arguments[0], node.Update(null, arguments), fallback => 81 | { 82 | var args = new Expression[arguments.Count]; 83 | arguments.CopyTo(args, 0); 84 | args[0] = fallback; 85 | 86 | return node.Update(null, args); 87 | }); 88 | } 89 | 90 | return node.Update(target, arguments); 91 | } 92 | 93 | static Expression BeSafe(Expression target, Expression expression, Func update) 94 | { 95 | var fallback = Cache.GetOrAdd(target.Type); 96 | 97 | if (fallback != null) 98 | { 99 | // coalesce instead, a bit intrusive but fast... 100 | return update(Expression.Coalesce(target, fallback)); 101 | } 102 | 103 | // target can be null, which is why we are actually here... 104 | var targetFallback = Expression.Constant(null, target.Type); 105 | 106 | // expression can be default or null, which is basically the same... 107 | var expressionFallback = !IsNullableOrReferenceType(expression.Type) 108 | ? (Expression)Expression.Default(expression.Type) : Expression.Constant(null, expression.Type); 109 | 110 | return Expression.Condition(Expression.Equal(target, targetFallback), expressionFallback, expression); 111 | } 112 | 113 | static bool IsSafe(Expression expression) 114 | { 115 | // in method call results and constant values we trust to avoid too much conditions... 116 | return expression == null 117 | || expression.NodeType == ExpressionType.Call 118 | || expression.NodeType == ExpressionType.Constant 119 | || !IsNullableOrReferenceType(expression.Type); 120 | } 121 | 122 | static Expression Fallback(Type type) 123 | { 124 | // default values for generic collections 125 | if (type.GetIsConstructedGenericType() && type.GetTypeInfo().GenericTypeArguments.Length == 1) 126 | { 127 | return CollectionFallback(typeof(List<>), type) 128 | ?? CollectionFallback(typeof(HashSet<>), type); 129 | } 130 | 131 | // default value for arrays 132 | if (type.IsArray) 133 | { 134 | return Expression.NewArrayInit(type.GetElementType()); 135 | } 136 | 137 | return null; 138 | } 139 | 140 | static Expression CollectionFallback(Type definition, Type type) 141 | { 142 | var collection = definition.MakeGenericType(type.GetTypeInfo().GenericTypeArguments); 143 | 144 | // try if an instance of this collection would suffice 145 | if (type.GetTypeInfo().IsAssignableFrom(collection.GetTypeInfo())) 146 | { 147 | return Expression.Convert(Expression.New(collection), type); 148 | } 149 | 150 | return null; 151 | } 152 | 153 | static bool IsExtensionMethod(MethodInfo element) 154 | { 155 | return element.IsDefined(typeof(ExtensionAttribute), false); 156 | } 157 | 158 | static bool IsNullableOrReferenceType(Type type) 159 | { 160 | return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null; 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ParameterExpressionEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping 5 | { 6 | public class ParameterExpressionEqualityComparer : IEqualityComparer 7 | { 8 | public bool Equals(ParameterExpression x, ParameterExpression y) => ReferenceEquals(x, y); 9 | 10 | public int GetHashCode(ParameterExpression obj) => obj.GetHashCode(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace AutoMapper.Extensions.ExpressionMapping 4 | { 5 | internal class PrependParentNameVisitor : ExpressionVisitor 6 | { 7 | public PrependParentNameVisitor(ParameterExpression currentParameter, string parentFullName, Expression newParameter) 8 | { 9 | CurrentParameter = currentParameter; 10 | ParentFullName = parentFullName; 11 | NewParameter = newParameter; 12 | } 13 | 14 | public ParameterExpression CurrentParameter { get; } 15 | public string ParentFullName { get; } 16 | public Expression NewParameter { get; } 17 | 18 | protected override Expression VisitParameter(ParameterExpression node) 19 | { 20 | if (object.ReferenceEquals(CurrentParameter, node)) 21 | { 22 | return string.IsNullOrEmpty(ParentFullName) 23 | ? NewParameter 24 | : ExpressionHelpers.MemberAccesses(ParentFullName, NewParameter); 25 | } 26 | 27 | return base.VisitParameter(node); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AutoMapper.Extensions.ExpressionMapping.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoMapper.Extensions.ExpressionMapping.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Cannot create a binary expression for the following pair. Node: {0}, Type: {1} and Node: {2}, Type: {3}.. 65 | /// 66 | internal static string cannotCreateBinaryExpressionFormat { 67 | get { 68 | return ResourceManager.GetString("cannotCreateBinaryExpressionFormat", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Can't rempa expression. 74 | /// 75 | internal static string cantRemapExpression { 76 | get { 77 | return ResourceManager.GetString("cantRemapExpression", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Custom resolvers are not supported for expression mapping.. 83 | /// 84 | internal static string customResolversNotSupported { 85 | get { 86 | return ResourceManager.GetString("customResolversNotSupported", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}.. 92 | /// 93 | internal static string expressionMapValueTypeMustMatchFormat { 94 | get { 95 | return ResourceManager.GetString("expressionMapValueTypeMustMatchFormat", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to The Include value-type expression uses multiple sibling navigation objects "{0}", "{1}" and is too complex to translate.. 101 | /// 102 | internal static string includeExpressionTooComplex { 103 | get { 104 | return ResourceManager.GetString("includeExpressionTooComplex", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to Source and destination must have the same number of arguments.. 110 | /// 111 | internal static string invalidArgumentCount { 112 | get { 113 | return ResourceManager.GetString("invalidArgumentCount", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to Invalid expression type for this operation.. 119 | /// 120 | internal static string invalidExpErr { 121 | get { 122 | return ResourceManager.GetString("invalidExpErr", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match. Parent Source Type: {0}, Parent Destination Type: {1}, Full Member Name "{2}".. 128 | /// 129 | internal static string makeParentTypesMatchForMembersOfLiteralsFormat { 130 | get { 131 | return ResourceManager.GetString("makeParentTypesMatchForMembersOfLiteralsFormat", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to The mapped member {0} is of type {1} and a child of the parameter type {2}. No reference type (parent of) {0} is available to map as an include.. 137 | /// 138 | internal static string mappedMemberIsChildOfTheParameterFormat { 139 | get { 140 | return ResourceManager.GetString("mappedMemberIsChildOfTheParameterFormat", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to Mapper Info dictionary cannot be null.. 146 | /// 147 | internal static string mapperInfoDictionaryIsNull { 148 | get { 149 | return ResourceManager.GetString("mapperInfoDictionaryIsNull", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized string similar to Arguments must be expressions.. 155 | /// 156 | internal static string mustBeExpressions { 157 | get { 158 | return ResourceManager.GetString("mustBeExpressions", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// Looks up a localized string similar to SourceMember cannot be null. Source Type: {0}, Destination Type: {1}, Property: {2}.. 164 | /// 165 | internal static string srcMemberCannotBeNullFormat { 166 | get { 167 | return ResourceManager.GetString("srcMemberCannotBeNullFormat", resourceCulture); 168 | } 169 | } 170 | 171 | /// 172 | /// Looks up a localized string similar to Type Mappings dictionary cannot be null.. 173 | /// 174 | internal static string typeMappingsDictionaryIsNull { 175 | get { 176 | return ResourceManager.GetString("typeMappingsDictionaryIsNull", resourceCulture); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | text/microsoft-resx 91 | 92 | 93 | 1.3 94 | 95 | 96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 97 | 98 | 99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 100 | 101 | 102 | Cannot create a binary expression for the following pair. Node: {0}, Type: {1} and Node: {2}, Type: {3}. 103 | 0=leftNode; 1=leftNodeType; 2=rightNode; 3=rightNodeType 104 | 105 | 106 | Can't rempa expression 107 | 108 | 109 | The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}. 110 | 0=Source Type; 1=SourceDescription; 2=Destination Type; 3=Destination Property. 111 | 112 | 113 | The Include value-type expression uses multiple sibling navigation objects "{0}", "{1}" and is too complex to translate. 114 | 0=FirstNavigationProperty, 1=SecondNavigationProperty 115 | 116 | 117 | Source and destination must have the same number of arguments. 118 | 119 | 120 | Invalid expression type for this operation. 121 | 122 | 123 | Mapper Info dictionary cannot be null. 124 | 125 | 126 | SourceMember cannot be null. Source Type: {0}, Destination Type: {1}, Property: {2}. 127 | 0=SorceType; 1=DestinationType; 2=Name of the source property 128 | 129 | 130 | Type Mappings dictionary cannot be null. 131 | 132 | 133 | Custom resolvers are not supported for expression mapping. 134 | 135 | 136 | Arguments must be expressions. 137 | 138 | 139 | The mapped member {0} is of type {1} and a child of the parameter type {2}. No reference type (parent of) {0} is available to map as an include. 140 | 0=memberName, 1=memberType; 2=parameterType 141 | 142 | 143 | For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match. Parent Source Type: {0}, Parent Destination Type: {1}, Full Member Name "{2}". 144 | 0=typeSource, 1=typeDestination; 2=sourceFullName 145 | 146 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using AutoMapper.Extensions.ExpressionMapping.Impl; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping 5 | { 6 | public static class QueryableExtensions 7 | { 8 | public static IQueryDataSourceInjection UseAsDataSource(this IQueryable dataSource, IConfigurationProvider config) 9 | => dataSource.UseAsDataSource(config.CreateMapper()); 10 | 11 | public static IQueryDataSourceInjection UseAsDataSource(this IQueryable dataSource, IMapper mapper) 12 | => new QueryDataSourceInjection(dataSource, mapper); 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using AutoMapper.Internal; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping 9 | { 10 | using MemberPaths = IEnumerable>; 11 | 12 | internal static class ReflectionExtensions 13 | { 14 | public static object MapMember(this ResolutionContext context, MemberInfo member, object value, object destination = null) 15 | => ReflectionHelper.MapMember(context, member, value, destination); 16 | 17 | public static void SetMemberValue(this MemberInfo propertyOrField, object target, object value) 18 | => ReflectionHelper.SetMemberValue(propertyOrField, target, value); 19 | 20 | public static object GetMemberValue(this MemberInfo propertyOrField, object target) 21 | => ReflectionHelper.GetMemberValue(propertyOrField, target); 22 | 23 | public static IEnumerable GetMemberPath(Type type, string fullMemberName) 24 | => ReflectionHelper.GetMemberPath(type, fullMemberName); 25 | 26 | public static MemberPaths GetMemberPaths(Type type, string[] membersToExpand) => 27 | membersToExpand.Select(m => ReflectionHelper.GetMemberPath(type, m)); 28 | 29 | public static MemberPaths GetMemberPaths(Expression>[] membersToExpand) => 30 | membersToExpand.Select(expr => MemberVisitor.GetMemberPath(expr)); 31 | 32 | public static MemberInfo FindProperty(LambdaExpression lambdaExpression) 33 | => ReflectionHelper.FindProperty(lambdaExpression); 34 | 35 | public static Type GetMemberType(this MemberInfo memberInfo) 36 | => ReflectionHelper.GetMemberType(memberInfo); 37 | 38 | public static IEnumerable GetDefinedTypes(this Assembly assembly) => 39 | assembly.DefinedTypes; 40 | 41 | public static bool GetHasDefaultValue(this ParameterInfo info) => 42 | info.HasDefaultValue; 43 | 44 | public static bool GetIsConstructedGenericType(this Type type) => 45 | type.IsConstructedGenericType; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/ReplaceExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace AutoMapper.Extensions.ExpressionMapping 4 | { 5 | internal class ReplaceExpressionVisitor : ExpressionVisitor 6 | { 7 | private readonly Expression _oldExpression; 8 | private readonly Expression _newExpression; 9 | 10 | public ReplaceExpressionVisitor(Expression oldExpression, Expression newExpression) 11 | { 12 | _oldExpression = oldExpression; 13 | _newExpression = newExpression; 14 | } 15 | 16 | public override Expression Visit(Expression node) 17 | { 18 | if (_oldExpression == node) 19 | node = _newExpression; 20 | 21 | return base.Visit(node); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping.Structures 5 | { 6 | internal class DeclaringMemberKey : IEquatable 7 | { 8 | public DeclaringMemberKey(MemberInfo declaringMemberInfo, string declaringMemberFullName) 9 | { 10 | DeclaringMemberInfo = declaringMemberInfo; 11 | DeclaringMemberFullName = declaringMemberFullName; 12 | } 13 | 14 | public MemberInfo DeclaringMemberInfo { get; set; } 15 | public string DeclaringMemberFullName { get; set; } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (ReferenceEquals(null, obj)) return false; 20 | if (ReferenceEquals(this, obj)) return true; 21 | 22 | DeclaringMemberKey key = obj as DeclaringMemberKey; 23 | if (key == null) return false; 24 | 25 | return Equals(key); 26 | } 27 | 28 | public bool Equals(DeclaringMemberKey other) 29 | { 30 | if (ReferenceEquals(null, other)) return false; 31 | if (ReferenceEquals(this, other)) return true; 32 | 33 | return this.DeclaringMemberInfo.Equals(other.DeclaringMemberInfo) 34 | && this.DeclaringMemberFullName == other.DeclaringMemberFullName; 35 | } 36 | 37 | public override int GetHashCode() => this.DeclaringMemberInfo.GetHashCode(); 38 | 39 | public override string ToString() => this.DeclaringMemberFullName; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Structures/MapperInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping.Structures 5 | { 6 | public class MapperInfo 7 | { 8 | public MapperInfo() 9 | { 10 | 11 | } 12 | 13 | public MapperInfo(ParameterExpression newParameter, Type sourceType, Type destType) 14 | { 15 | NewParameter = newParameter; 16 | SourceType = sourceType; 17 | DestType = destType; 18 | } 19 | 20 | public Type SourceType { get; set; } 21 | public Type DestType { get; set; } 22 | public ParameterExpression NewParameter { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | 6 | namespace AutoMapper.Extensions.ExpressionMapping.Structures 7 | { 8 | internal class MemberAssignmentInfo 9 | { 10 | public MemberAssignmentInfo(PropertyMap propertyMap, MemberAssignment memberAssignment) 11 | { 12 | PropertyMap = propertyMap; 13 | MemberAssignment = memberAssignment; 14 | } 15 | 16 | /// 17 | /// Used to get the source member to be bound with the mapped binding expression. 18 | /// 19 | public PropertyMap PropertyMap { get; set; } 20 | 21 | /// 22 | /// Initial member assignment who's binding expression will be mapped and assigned to the source menber of the new type 23 | /// 24 | public MemberAssignment MemberAssignment { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping.Structures 5 | { 6 | /// 7 | /// Defines the type to be initialized and a list of source bindings. 8 | /// The new bound members will be matched using MemberAssignmentInfos.PropertyMap and 9 | /// assigned to the mapped expression (mapped from MemberAssignmentInfos.MemberAssignment.Expression). 10 | /// 11 | internal class MemberBindingGroup 12 | { 13 | public MemberBindingGroup(DeclaringMemberKey declaringMemberKey, bool isRootMemberAssignment, Type newType, List memberAssignmentInfos) 14 | { 15 | DeclaringMemberKey = declaringMemberKey; 16 | IsRootMemberAssignment = isRootMemberAssignment; 17 | NewType = newType; 18 | MemberAssignmentInfos = memberAssignmentInfos; 19 | } 20 | 21 | /// 22 | /// DeclaringMemberKey will be null when the member assignment is a member binding of OldType on the initial (root) TypeMap (OldType -> NewType) 23 | /// 24 | public DeclaringMemberKey DeclaringMemberKey { get; set; } 25 | 26 | /// 27 | /// MemberAssignment is true if it is a member binding of OldType on the initial (root) TypeMap (OldType -> NewType) 28 | /// 29 | public bool IsRootMemberAssignment { get; set; } 30 | 31 | /// 32 | /// Destination type of the member assignment. If IsRootMemberAssignment == true then this is the destination type of initial (root) TypeMap (OldType -> NewType) 33 | /// Otherwise it is the PropertyType/FieldType of DeclaringMemberInfo 34 | /// 35 | public Type NewType { get; set; } 36 | 37 | /// 38 | /// List of members to be mapped and bound to the new type 39 | /// 40 | public List MemberAssignmentInfos { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/Structures/PropertyMapInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace AutoMapper.Extensions.ExpressionMapping.Structures 6 | { 7 | public class PropertyMapInfo 8 | { 9 | public PropertyMapInfo(LambdaExpression customExpression, List destinationPropertyInfos) 10 | { 11 | CustomExpression = customExpression; 12 | DestinationPropertyInfos = destinationPropertyInfos; 13 | } 14 | 15 | public LambdaExpression CustomExpression { get; set; } 16 | public List DestinationPropertyInfos { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace AutoMapper 8 | { 9 | 10 | internal static class TypeExtensions 11 | { 12 | public static bool Has(this Type type) where TAttribute : Attribute => type.GetTypeInfo().IsDefined(typeof(TAttribute), inherit: false); 13 | 14 | public static Type GetGenericTypeDefinitionIfGeneric(this Type type) => type.IsGenericType() ? type.GetGenericTypeDefinition() : type; 15 | 16 | public static Type[] GetGenericArguments(this Type type) => type.GetTypeInfo().GenericTypeArguments; 17 | 18 | public static Type[] GetGenericParameters(this Type type) => type.GetGenericTypeDefinition().GetTypeInfo().GenericTypeParameters; 19 | 20 | public static IEnumerable GetDeclaredConstructors(this Type type) => type.GetTypeInfo().DeclaredConstructors; 21 | 22 | #if !NET45 23 | public static MethodInfo GetAddMethod(this EventInfo eventInfo) => eventInfo.AddMethod; 24 | 25 | public static MethodInfo GetRemoveMethod(this EventInfo eventInfo) => eventInfo.RemoveMethod; 26 | #endif 27 | 28 | public static IEnumerable GetDeclaredMembers(this Type type) => type.GetTypeInfo().DeclaredMembers; 29 | 30 | public static IEnumerable GetTypeInheritance(this Type type) 31 | { 32 | yield return type; 33 | 34 | var baseType = type.BaseType(); 35 | while(baseType != null) 36 | { 37 | yield return baseType; 38 | baseType = baseType.BaseType(); 39 | } 40 | } 41 | 42 | public static IEnumerable GetDeclaredMethods(this Type type) => type.GetTypeInfo().DeclaredMethods; 43 | 44 | public static MethodInfo GetDeclaredMethod(this Type type, string name) => type.GetAllMethods().FirstOrDefault(mi => mi.Name == name); 45 | 46 | public static MethodInfo GetDeclaredMethod(this Type type, string name, Type[] parameters) => 47 | type.GetAllMethods().Where(mi => mi.Name == name).MatchParameters(parameters); 48 | 49 | public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] parameters) => 50 | type.GetDeclaredConstructors().MatchParameters(parameters); 51 | 52 | private static TMethod MatchParameters(this IEnumerable methods, Type[] parameters) where TMethod : MethodBase => 53 | methods.FirstOrDefault(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameters)); 54 | 55 | public static IEnumerable GetAllMethods(this Type type) => type.GetRuntimeMethods(); 56 | 57 | public static IEnumerable GetDeclaredProperties(this Type type) => type.GetTypeInfo().DeclaredProperties; 58 | 59 | public static PropertyInfo GetDeclaredProperty(this Type type, string name) 60 | => type.GetTypeInfo().GetDeclaredProperty(name); 61 | 62 | public static object[] GetCustomAttributes(this Type type, Type attributeType, bool inherit) 63 | => type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast().ToArray(); 64 | 65 | public static bool IsStatic(this FieldInfo fieldInfo) => fieldInfo?.IsStatic ?? false; 66 | 67 | public static bool IsStatic(this PropertyInfo propertyInfo) => propertyInfo?.GetGetMethod(true)?.IsStatic 68 | ?? propertyInfo?.GetSetMethod(true)?.IsStatic 69 | ?? false; 70 | 71 | public static bool IsStatic(this MemberInfo memberInfo) => (memberInfo as FieldInfo).IsStatic() 72 | || (memberInfo as PropertyInfo).IsStatic() 73 | || ((memberInfo as MethodInfo)?.IsStatic 74 | ?? false); 75 | 76 | public static bool IsPublic(this PropertyInfo propertyInfo) => (propertyInfo?.GetGetMethod(true)?.IsPublic ?? false) 77 | || (propertyInfo?.GetSetMethod(true)?.IsPublic ?? false); 78 | 79 | public static IEnumerable PropertiesWithAnInaccessibleSetter(this Type type) 80 | { 81 | return type.GetDeclaredProperties().Where(pm => pm.HasAnInaccessibleSetter()); 82 | } 83 | 84 | public static bool HasAnInaccessibleSetter(this PropertyInfo property) 85 | { 86 | var setMethod = property.GetSetMethod(true); 87 | return setMethod == null || setMethod.IsPrivate || setMethod.IsFamily; 88 | } 89 | 90 | public static bool IsPublic(this MemberInfo memberInfo) => (memberInfo as FieldInfo)?.IsPublic ?? (memberInfo as PropertyInfo).IsPublic(); 91 | 92 | public static bool IsNotPublic(this ConstructorInfo constructorInfo) => constructorInfo.IsPrivate 93 | || constructorInfo.IsFamilyAndAssembly 94 | || constructorInfo.IsFamilyOrAssembly 95 | || constructorInfo.IsFamily; 96 | 97 | public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly; 98 | 99 | public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType; 100 | 101 | public static bool IsAssignableFrom(this Type type, Type other) => type.GetTypeInfo().IsAssignableFrom(other.GetTypeInfo()); 102 | 103 | public static bool IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract; 104 | 105 | public static bool IsClass(this Type type) => type.GetTypeInfo().IsClass; 106 | 107 | public static bool IsEnum(this Type type) => type.GetTypeInfo().IsEnum; 108 | 109 | public static bool IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType; 110 | 111 | public static bool IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition; 112 | 113 | public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface; 114 | 115 | public static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive; 116 | 117 | public static bool IsSealed(this Type type) => type.GetTypeInfo().IsSealed; 118 | 119 | public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType; 120 | 121 | public static bool IsLiteralType(this Type type) 122 | { 123 | if (type.IsNullableType()) 124 | type = Nullable.GetUnderlyingType(type); 125 | 126 | return LiteralTypes.Contains(type) || NonNetStandardLiteralTypes.Contains(type.FullName); 127 | } 128 | 129 | private static HashSet LiteralTypes => new HashSet(_literalTypes); 130 | 131 | private static readonly HashSet NonNetStandardLiteralTypes = new() 132 | { 133 | "System.DateOnly", 134 | "Microsoft.OData.Edm.Date", 135 | "System.TimeOnly", 136 | "Microsoft.OData.Edm.TimeOfDay" 137 | }; 138 | 139 | private static Type[] _literalTypes => new Type[] { 140 | typeof(bool), 141 | typeof(DateTime), 142 | typeof(DateTimeOffset), 143 | typeof(TimeSpan), 144 | typeof(Guid), 145 | typeof(decimal), 146 | typeof(byte), 147 | typeof(short), 148 | typeof(int), 149 | typeof(long), 150 | typeof(float), 151 | typeof(double), 152 | typeof(char), 153 | typeof(sbyte), 154 | typeof(ushort), 155 | typeof(uint), 156 | typeof(ulong), 157 | typeof(string) 158 | }; 159 | 160 | public static bool IsInstanceOfType(this Type type, object o) => o != null && type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); 161 | 162 | public static PropertyInfo[] GetProperties(this Type type) => type.GetRuntimeProperties().ToArray(); 163 | 164 | public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo, bool ignored) => propertyInfo.GetMethod; 165 | 166 | public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo, bool ignored) => propertyInfo.SetMethod; 167 | 168 | public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) => propertyInfo.GetMethod; 169 | 170 | public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) => propertyInfo.SetMethod; 171 | 172 | public static FieldInfo GetField(this Type type, string name) => type.GetRuntimeField(name); 173 | 174 | public static bool IsQueryableType(this Type type) 175 | => typeof(IQueryable).IsAssignableFrom(type); 176 | 177 | public static Type GetGenericElementType(this Type type) 178 | => type.HasElementType ? type.GetElementType() : type.GetTypeInfo().GenericTypeArguments[0]; 179 | 180 | public static bool IsEnumerableType(this Type type) => 181 | type.IsGenericType && typeof(System.Collections.IEnumerable).IsAssignableFrom(type); 182 | 183 | public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType) 184 | { 185 | if (targetType == oldType) 186 | return newType; 187 | 188 | if (targetType.IsGenericType) 189 | { 190 | var genSubArgs = targetType.GetTypeInfo().GenericTypeArguments; 191 | var newGenSubArgs = new Type[genSubArgs.Length]; 192 | for (var i = 0; i < genSubArgs.Length; i++) 193 | newGenSubArgs[i] = ReplaceItemType(genSubArgs[i], oldType, newType); 194 | return targetType.GetGenericTypeDefinition().MakeGenericType(newGenSubArgs); 195 | } 196 | 197 | return targetType; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper.Internal; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace AutoMapper.Extensions.ExpressionMapping 6 | { 7 | internal static class TypeMapHelper 8 | { 9 | public static bool CanMapConstant(this IConfigurationProvider config, Type sourceType, Type destType) 10 | { 11 | if (sourceType == destType) 12 | return false; 13 | 14 | if (BothTypesAreDictionary()) 15 | { 16 | Type[] sourceGenericTypes = sourceType.GetGenericArguments(); 17 | Type[] destGenericTypes = destType.GetGenericArguments(); 18 | if (sourceGenericTypes.SequenceEqual(destGenericTypes)) 19 | return false; 20 | else if (sourceGenericTypes[0] == destGenericTypes[0]) 21 | return config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]); 22 | else if (sourceGenericTypes[1] == destGenericTypes[1]) 23 | return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]); 24 | else 25 | return config.CanMapConstant(sourceGenericTypes[0], destGenericTypes[0]) && config.CanMapConstant(sourceGenericTypes[1], destGenericTypes[1]); 26 | } 27 | else if (sourceType.IsArray && destType.IsArray) 28 | return config.CanMapConstant(sourceType.GetElementType(), destType.GetElementType()); 29 | else if (BothTypesAreEnumerable()) 30 | return config.CanMapConstant(sourceType.GetGenericArguments()[0], destType.GetGenericArguments()[0]); 31 | else if (BothTypesAreEnums()) 32 | return true; 33 | else 34 | return config.Internal().ResolveTypeMap(sourceType, destType) != null; 35 | 36 | bool BothTypesAreEnums() 37 | => sourceType.IsEnum && destType.IsEnum; 38 | 39 | bool BothTypesAreEnumerable() 40 | { 41 | Type enumerableType = typeof(System.Collections.IEnumerable); 42 | return sourceType.IsGenericType 43 | && destType.IsGenericType 44 | && enumerableType.IsAssignableFrom(sourceType) 45 | && enumerableType.IsAssignableFrom(destType); 46 | } 47 | 48 | bool BothTypesAreDictionary() 49 | { 50 | Type dictionaryType = typeof(System.Collections.IDictionary); 51 | return sourceType.IsGenericType 52 | && destType.IsGenericType 53 | && dictionaryType.IsAssignableFrom(sourceType) 54 | && dictionaryType.IsAssignableFrom(destType) 55 | && sourceType.GetGenericArguments().Length == 2 56 | && destType.GetGenericArguments().Length == 2; 57 | } 58 | } 59 | 60 | public static MemberMap GetMemberMapByDestinationProperty(this TypeMap typeMap, string destinationPropertyName) 61 | { 62 | var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == destinationPropertyName); 63 | if (propertyMap != null) 64 | return propertyMap; 65 | 66 | var memberMap = typeMap.MemberMaps.OfType().SingleOrDefault(mm => string.Compare(mm.Parameter.Name, destinationPropertyName, StringComparison.InvariantCultureIgnoreCase) == 0); 67 | if (memberMap != null) 68 | return memberMap; 69 | 70 | throw PropertyConfigurationException(typeMap, destinationPropertyName); 71 | } 72 | 73 | public static TypeMap CheckIfTypeMapExists(this IConfigurationProvider config, Type sourceType, Type destinationType) 74 | { 75 | var typeMap = config.Internal().ResolveTypeMap(sourceType, destinationType); 76 | if (typeMap == null) 77 | { 78 | throw MissingMapException(sourceType, destinationType); 79 | } 80 | return typeMap; 81 | } 82 | 83 | public static string GetDestinationName(this MemberMap memberMap) 84 | { 85 | if (memberMap is PropertyMap propertyMap) 86 | return propertyMap.DestinationMember.Name; 87 | 88 | if (memberMap is ConstructorParameterMap constructorMap) 89 | return constructorMap.Parameter.Name; 90 | 91 | throw new ArgumentException(nameof(memberMap)); 92 | } 93 | 94 | public static PathMap FindPathMapByDestinationFullPath(this TypeMap typeMap, string destinationFullPath) => 95 | typeMap.PathMaps.SingleOrDefault(item => string.Join(".", item.MemberPath.Members.Select(m => m.Name)) == destinationFullPath); 96 | 97 | private static Exception PropertyConfigurationException(TypeMap typeMap, params string[] unmappedPropertyNames) 98 | => new AutoMapperConfigurationException(new[] { new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, true) }); 99 | 100 | private static Exception MissingMapException(Type sourceType, Type destinationType) 101 | => new InvalidOperationException($"Missing map from {sourceType} to {destinationType}. Create using CreateMap<{sourceType.Name}, {destinationType.Name}>."); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AssertionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using Shouldly; 5 | 6 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 7 | { 8 | public static class AssertionExtensions 9 | { 10 | public static void ShouldContain(this IEnumerable items, object item) 11 | => ShouldBeEnumerableTestExtensions.ShouldContain(items.Cast(), item); 12 | 13 | public static void ShouldBeEmpty(this IEnumerable items) 14 | => ShouldBeEnumerableTestExtensions.ShouldBeEmpty(items.Cast()); 15 | 16 | public static void ShouldBeThrownBy(this Type exceptionType, Action action) 17 | => action.ShouldThrow(exceptionType); 18 | 19 | public static void ShouldThrowException(this Action action, Action customAssertion) where T : Exception 20 | { 21 | bool throws = false; 22 | try 23 | { 24 | action(); 25 | } 26 | catch (T e) 27 | { 28 | throws = true; 29 | customAssertion(e); 30 | } 31 | throws.ShouldBeTrue(); 32 | } 33 | 34 | public static void ShouldNotBeThrownBy(this Type exceptionType, Action action) 35 | => action.ShouldNotThrow(); 36 | } 37 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapperSpecBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 5 | { 6 | public abstract class AutoMapperSpecBase : NonValidatingSpecBase 7 | { 8 | [Fact] 9 | public void Should_have_valid_configuration() 10 | { 11 | Configuration.AssertConfigurationIsValid(); 12 | } 13 | 14 | } 15 | 16 | public abstract class NonValidatingSpecBase : SpecBase 17 | { 18 | private IMapper mapper; 19 | 20 | protected abstract MapperConfiguration Configuration { get; } 21 | protected IConfigurationProvider ConfigProvider => Configuration; 22 | 23 | protected IMapper Mapper => mapper ?? (mapper = Configuration.CreateMapper()); 24 | } 25 | 26 | public abstract class SpecBaseBase 27 | { 28 | protected virtual void MainSetup() 29 | { 30 | Establish_context(); 31 | Because_of(); 32 | } 33 | 34 | protected virtual void MainTeardown() 35 | { 36 | Cleanup(); 37 | } 38 | 39 | protected virtual void Establish_context() 40 | { 41 | } 42 | 43 | protected virtual void Because_of() 44 | { 45 | } 46 | 47 | protected virtual void Cleanup() 48 | { 49 | } 50 | } 51 | public abstract class SpecBase : SpecBaseBase, IDisposable 52 | { 53 | protected SpecBase() 54 | { 55 | Establish_context(); 56 | Because_of(); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | Cleanup(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class CanMapExpressionWithListConstants 10 | { 11 | [Fact] 12 | public void Map_expression_with_constant_array() 13 | { 14 | //Arrange 15 | var config = new MapperConfiguration 16 | ( 17 | cfg => 18 | { 19 | cfg.CreateMap(); 20 | cfg.CreateMap(); 21 | } 22 | ); 23 | config.AssertConfigurationIsValid(); 24 | var mapper = config.CreateMapper(); 25 | List source1 = new() { 26 | new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } 27 | }; 28 | List source2 = new() { 29 | new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } 30 | }; 31 | Entity[] entities = new Entity[] { new Entity { SimpleEnum = SimpleEnum.Value1 }, new Entity { SimpleEnum = SimpleEnum.Value2 } }; 32 | Expression> filter = e => entities.Any(en => e.SimpleEnum == en.SimpleEnum); 33 | 34 | //act 35 | Expression> mappedFilter = mapper.MapExpression>>(filter); 36 | 37 | //assert 38 | Assert.False(source1.AsQueryable().Any(mappedFilter)); 39 | Assert.True(source2.AsQueryable().Any(mappedFilter)); 40 | } 41 | 42 | [Fact] 43 | public void Map_expression_with_constant_list_using_generic_list_dot_contains() 44 | { 45 | //Arrange 46 | var config = new MapperConfiguration 47 | ( 48 | cfg => 49 | { 50 | cfg.CreateMap(); 51 | } 52 | ); 53 | config.AssertConfigurationIsValid(); 54 | var mapper = config.CreateMapper(); 55 | List source1 = new() { 56 | new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } 57 | }; 58 | List source2 = new() { 59 | new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } 60 | }; 61 | List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 }; 62 | Expression> filter = e => enums.Contains(e.SimpleEnum); 63 | 64 | //act 65 | Expression> mappedFilter = mapper.MapExpression>>(filter); 66 | 67 | //assert 68 | Assert.False(source1.AsQueryable().Any(mappedFilter)); 69 | Assert.True(source2.AsQueryable().Any(mappedFilter)); 70 | } 71 | 72 | [Fact] 73 | public void Map_expression_with_constant_list_using_generic_enumerable_dot_contains() 74 | { 75 | //Arrange 76 | var config = new MapperConfiguration 77 | ( 78 | cfg => 79 | { 80 | cfg.CreateMap(); 81 | } 82 | ); 83 | config.AssertConfigurationIsValid(); 84 | var mapper = config.CreateMapper(); 85 | List source1 = new() { 86 | new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } 87 | }; 88 | List source2 = new() { 89 | new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } 90 | }; 91 | List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 }; 92 | Expression> filter = e => Enumerable.Contains(enums, e.SimpleEnum); 93 | 94 | //act 95 | Expression> mappedFilter = mapper.MapExpression>>(filter); 96 | 97 | //assert 98 | Assert.False(source1.AsQueryable().Any(mappedFilter)); 99 | Assert.True(source2.AsQueryable().Any(mappedFilter)); 100 | } 101 | 102 | [Fact] 103 | public void Map_expression_with_constant_dictionary() 104 | { 105 | //Arrange 106 | var config = new MapperConfiguration 107 | ( 108 | cfg => 109 | { 110 | cfg.CreateMap(); 111 | } 112 | ); 113 | config.AssertConfigurationIsValid(); 114 | var mapper = config.CreateMapper(); 115 | List source1 = new() { 116 | new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } 117 | }; 118 | List source2 = new() { 119 | new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } 120 | }; 121 | Dictionary enumDictionary = new() { ["A"] = SimpleEnum.Value1, ["B"] = SimpleEnum.Value2 }; 122 | Expression> filter = e => enumDictionary.Any(i => i.Value == e.SimpleEnum); 123 | 124 | //act 125 | Expression> mappedFilter = mapper.MapExpression>>(filter); 126 | 127 | //assert 128 | Assert.False(source1.AsQueryable().Any(mappedFilter)); 129 | Assert.True(source2.AsQueryable().Any(mappedFilter)); 130 | } 131 | 132 | [Fact] 133 | public void Map_expression_with_constant_dictionary_mapping_both_Key_and_value() 134 | { 135 | //Arrange 136 | var config = new MapperConfiguration 137 | ( 138 | cfg => 139 | { 140 | cfg.CreateMap(); 141 | cfg.CreateMap(); 142 | } 143 | ); 144 | config.AssertConfigurationIsValid(); 145 | var mapper = config.CreateMapper(); 146 | List source1 = new() { 147 | new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } 148 | }; 149 | List source2 = new() { 150 | new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } 151 | }; 152 | Dictionary enumDictionary = new() { [SimpleEnum.Value1] = new Entity { SimpleEnum = SimpleEnum.Value1 }, [SimpleEnum.Value2] = new Entity { SimpleEnum = SimpleEnum.Value2 } }; 153 | Expression> filter = e => enumDictionary.Any(i => i.Key == e.SimpleEnum && i.Value.SimpleEnum == e.SimpleEnum); 154 | 155 | //act 156 | Expression> mappedFilter = mapper.MapExpression>>(filter); 157 | 158 | //assert 159 | Assert.False(source1.AsQueryable().Any(mappedFilter)); 160 | Assert.True(source2.AsQueryable().Any(mappedFilter)); 161 | } 162 | 163 | public enum SimpleEnum 164 | { 165 | Value1, 166 | Value2, 167 | Value3 168 | } 169 | 170 | public record Entity 171 | { 172 | public int Id { get; init; } 173 | public SimpleEnum SimpleEnum { get; init; } 174 | } 175 | 176 | public enum SimpleEnumModel 177 | { 178 | Value1, 179 | Value2, 180 | Value3 181 | } 182 | 183 | public record EntityModel 184 | { 185 | public int Id { get; init; } 186 | public SimpleEnumModel SimpleEnum { get; init; } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithLocalExpressionConstant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class CanMapExpressionWithLocalExpressionConstant 10 | { 11 | [Fact] 12 | public void Map_expression_wchich_includes_local_constant() 13 | { 14 | //Arrange 15 | var config = new MapperConfiguration 16 | ( 17 | cfg => 18 | { 19 | cfg.CreateMap(); 20 | cfg.CreateMap(); 21 | } 22 | ); 23 | config.AssertConfigurationIsValid(); 24 | var mapper = config.CreateMapper(); 25 | List source = [ 26 | new Entity { Id = 1 }, 27 | new Entity { Id = 3 } 28 | ]; 29 | 30 | //act 31 | Expression> filter = f => f.Id > 2; 32 | Expression, IQueryable>> queryableExpression = q => q.Where(filter); 33 | Expression, IQueryable>> queryableExpressionMapped = mapper.MapExpression, IQueryable>>>(queryableExpression); 34 | 35 | //assert 36 | Assert.Equal(1, queryableExpressionMapped.Compile()(source.AsQueryable()).Count()); 37 | } 38 | 39 | public record Entity 40 | { 41 | public int Id { get; init; } 42 | } 43 | 44 | public record EntityModel 45 | { 46 | public int Id { get; init; } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMemberFromTypeBinaryExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class CanMapMemberFromTypeBinaryExpression 10 | { 11 | [Fact] 12 | public void Can_map_using_type_binary_expression_to_test_the_parameter_expression() 13 | { 14 | //arrange 15 | var config = new MapperConfiguration(cfg => 16 | { 17 | cfg.AddExpressionMapping(); 18 | 19 | cfg.CreateMap() 20 | .ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s is Triangle ? ShapeType.Triangle : s is Circle ? ShapeType.Circle : ShapeType.Unknown)) 21 | .ForMember(dto => dto.DynamicProperties, o => o.Ignore()); 22 | }); 23 | 24 | var mapper = config.CreateMapper(); 25 | Expression> where = x => x.ShapeType == ShapeType.Circle; 26 | 27 | //act 28 | Expression> whereMapped = mapper.MapExpression>>(where); 29 | var list = new List { new Circle(), new Triangle() }.AsQueryable().Where(whereMapped).ToList(); 30 | 31 | //assert 32 | Assert.Single(list); 33 | Assert.Equal 34 | ( 35 | "x => (Convert(IIF((x Is Triangle), Triangle, IIF((x Is Circle), Circle, Unknown)), Int32) == 2)", 36 | whereMapped.ToString() 37 | ); 38 | } 39 | 40 | [Fact] 41 | public void Can_map_using_type_binary_expression_to_test_a_member_expression() 42 | { 43 | //arrange 44 | var config = new MapperConfiguration(cfg => 45 | { 46 | cfg.AddExpressionMapping(); 47 | 48 | cfg.CreateMap(); 49 | cfg.CreateMap() 50 | .ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s is Triangle ? ShapeType.Triangle : s is Circle ? ShapeType.Circle : ShapeType.Unknown)) 51 | .ForMember(dto => dto.DynamicProperties, o => o.Ignore()); 52 | }); 53 | 54 | var mapper = config.CreateMapper(); 55 | Expression> where = x => x.Shape.ShapeType == ShapeType.Circle; 56 | 57 | //act 58 | Expression> whereMapped = mapper.MapExpression>>(where); 59 | var list = new List 60 | { 61 | new ShapeHolder { Shape = new Circle() }, 62 | new ShapeHolder { Shape = new Triangle() } 63 | } 64 | .AsQueryable() 65 | .Where(whereMapped).ToList(); 66 | 67 | //assert 68 | Assert.Single(list); 69 | Assert.Equal 70 | ( 71 | "x => (Convert(IIF((x.Shape Is Triangle), Triangle, IIF((x.Shape Is Circle), Circle, Unknown)), Int32) == 2)", 72 | whereMapped.ToString() 73 | ); 74 | } 75 | 76 | [Fact] 77 | public void Can_map_using_instance_method_call_to_test_the_parameter_expression() 78 | { 79 | //arrange 80 | var config = new MapperConfiguration(cfg => 81 | { 82 | cfg.AddExpressionMapping(); 83 | 84 | cfg.CreateMap() 85 | .ForMember(dto => dto.ShapeType, o => o.MapFrom(s => s.GetType() == typeof(Triangle) ? ShapeType.Triangle : s.GetType() == typeof(Circle) ? ShapeType.Circle : ShapeType.Unknown)) 86 | .ForMember(dto => dto.DynamicProperties, o => o.Ignore()); 87 | }); 88 | 89 | var mapper = config.CreateMapper(); 90 | Expression> where = x => x.ShapeType == ShapeType.Circle; 91 | 92 | //act 93 | Expression> whereMapped = mapper.MapExpression>>(where); 94 | var list = new List { new Circle(), new Triangle() }.AsQueryable().Where(whereMapped).ToList(); 95 | 96 | //assert 97 | Assert.Single(list); 98 | } 99 | 100 | [Fact] 101 | public void Can_map_using_static_method_call_to_test_the_parameter_expression() 102 | { 103 | //arrange 104 | var config = new MapperConfiguration(cfg => 105 | { 106 | cfg.AddExpressionMapping(); 107 | 108 | cfg.CreateMap() 109 | .ForMember(dto => dto.HasMany, o => o.MapFrom(s => s.HasMessages())); 110 | }); 111 | 112 | var mapper = config.CreateMapper(); 113 | Expression> where = x => x.HasMany; 114 | 115 | //act 116 | Expression> whereMapped = mapper.MapExpression>>(where); 117 | var list = new List { new Circle() { Messages = new List { "" } }, new Triangle() { Messages = new List() } }.AsQueryable().Where(whereMapped).ToList(); 118 | 119 | //assert 120 | Assert.Single(list); 121 | Assert.Equal 122 | ( 123 | "x => x.HasMessages()", 124 | whereMapped.ToString() 125 | ); 126 | } 127 | 128 | [Fact] 129 | public void Can_map_using_static_generic_method_call_to_test_the_parameter_expression() 130 | { 131 | //arrange 132 | var config = new MapperConfiguration(cfg => 133 | { 134 | cfg.AddExpressionMapping(); 135 | 136 | cfg.CreateMap() 137 | .ForMember(dto => dto.IsCircle, o => o.MapFrom(s => s.IsCircle())); 138 | }); 139 | 140 | var mapper = config.CreateMapper(); 141 | Expression> where = x => x.IsCircle; 142 | 143 | //act 144 | Expression> whereMapped = mapper.MapExpression>>(where); 145 | var list = new List { new Circle(), new Triangle()}.AsQueryable().Where(whereMapped).ToList(); 146 | 147 | //assert 148 | Assert.Single(list); 149 | Assert.Equal 150 | ( 151 | "x => x.IsCircle()", 152 | whereMapped.ToString() 153 | ); 154 | } 155 | 156 | public class ShapeHolder 157 | { 158 | public Shape Shape { get; set; } 159 | } 160 | 161 | public class Shape 162 | { 163 | public string Name { get; set; } 164 | public List Messages { get; set; } 165 | } 166 | 167 | public class Triangle : Shape 168 | { 169 | public double A { get; set; } 170 | public double B { get; set; } 171 | public double C { get; set; } 172 | } 173 | 174 | public class Circle : Shape 175 | { 176 | public double R { get; set; } 177 | } 178 | 179 | public enum ShapeType 180 | { 181 | Unknown, 182 | Triangle, 183 | Circle, 184 | } 185 | 186 | public class ShapeDto 187 | { 188 | public string Name { get; set; } 189 | public IDictionary DynamicProperties { get; set; } 190 | public ShapeType ShapeType { get; set; } 191 | public bool HasMany { get; set; } 192 | public bool IsCircle { get; set; } 193 | } 194 | 195 | public class ShapeHolderDto 196 | { 197 | public ShapeDto Shape { get; set; } 198 | } 199 | } 200 | 201 | public static class ShapeExtentions 202 | { 203 | public static bool HasMessages(this CanMapMemberFromTypeBinaryExpression.Shape shape) 204 | { 205 | return shape.Messages.Any(); 206 | } 207 | 208 | public static bool IsCircle(this T shape) 209 | { 210 | return shape.GetType() == typeof(CanMapMemberFromTypeBinaryExpression.Circle); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class CanMapParameterBodyFromChildReferenceWithoutMemberExpression 10 | { 11 | [Fact] 12 | public void Can_map_parameter_body_from_child_reference_without_member_expression() 13 | { 14 | // Arrange 15 | var config = new MapperConfiguration(c => 16 | { 17 | c.CreateMap() 18 | .ForMember(p => p.Brand, c => c.MapFrom(p => EF.Property(p, "BrandId"))); ; 19 | 20 | c.CreateMap() 21 | .IncludeMembers(p => p.Category); 22 | }); 23 | 24 | config.AssertConfigurationIsValid(); 25 | var mapper = config.CreateMapper(); 26 | 27 | var products = new List() { 28 | new TestProduct { } 29 | }.AsQueryable(); 30 | 31 | //Act 32 | Expression> expr = x => x.Brand == 2; 33 | var mappedExpression = mapper.MapExpression>>(expr); 34 | 35 | //Assert 36 | Assert.Equal("x => (Property(x.Category, \"BrandId\") == 2)", mappedExpression.ToString()); 37 | } 38 | 39 | public class TestCategory 40 | { 41 | // Has FK BrandId 42 | } 43 | public class TestProduct 44 | { 45 | public TestCategory? Category { get; set; } 46 | } 47 | 48 | public class TestProductDTO 49 | { 50 | public int Brand { get; set; } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyWithoutMemberExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class CanMapParameterBodyWithoutMemberExpression 10 | { 11 | [Fact] 12 | public void Can_map_parameter_body_without_member_expression() 13 | { 14 | // Arrange 15 | var config = new MapperConfiguration(c => 16 | { 17 | c.CreateMap() 18 | .ForMember(p => p.Brand, c => c.MapFrom(p => EF.Property(p, "BrandId"))); 19 | }); 20 | 21 | config.AssertConfigurationIsValid(); 22 | var mapper = config.CreateMapper(); 23 | 24 | var products = new List() { 25 | new TestProduct { } 26 | }.AsQueryable(); 27 | 28 | //Act 29 | Expression> expr = x => x.Brand == 2; 30 | var mappedExpression = mapper.MapExpression>>(expr); 31 | 32 | //Assert 33 | Assert.Equal("x => (Property(x, \"BrandId\") == 2)", mappedExpression.ToString()); 34 | } 35 | 36 | public class TestProduct 37 | { 38 | // Empty, has shadow key named BrandId 39 | } 40 | 41 | public class TestProductDTO 42 | { 43 | public int Brand { get; set; } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EF.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 2 | { 3 | internal class EF 4 | { 5 | internal static T Property(object p, string v) 6 | { 7 | return default; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EnumerableDotContainsWorksOnLocalVariables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 9 | { 10 | public class EnumerableDotContainsWorksOnLocalVariables 11 | { 12 | [Fact] 13 | public void Issue87() 14 | { 15 | var config = new MapperConfiguration(cfg => 16 | { 17 | cfg.AddExpressionMapping(); 18 | 19 | cfg.CreateMap() 20 | .ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => s.Name))); 21 | }); 22 | 23 | var mapper = config.CreateMapper(); 24 | 25 | var items = new string[] { "item1", "item2" }; 26 | Expression> expression1 = o => items.Contains("item1"); 27 | Expression> expression2 = o => items.Contains(""); 28 | Expression> expression3 = o => o.Items.Contains("item1"); 29 | Expression> expression4 = o => o.Items.Contains("B"); 30 | 31 | var mapped1 = mapper.MapExpression>>(expression1); 32 | var mapped2 = mapper.MapExpression>>(expression2); 33 | var mapped3 = mapper.MapExpression>>(expression3); 34 | var mapped4 = mapper.MapExpression>>(expression4); 35 | 36 | Assert.Equal(1, new Source[] { new Source { } }.AsQueryable().Where(mapped1).Count()); 37 | Assert.Equal(0, new Source[] { new Source { } }.AsQueryable().Where(mapped2).Count()); 38 | Assert.Equal(1, new Source[] { new Source { Items = new List { new SubSource { Name = "item1" } } } }.AsQueryable().Where(mapped3).Count()); 39 | Assert.Equal(0, new Source[] { new Source { Items = new List { new SubSource { Name = "" } } } }.AsQueryable().Where(mapped4).Count()); 40 | } 41 | 42 | public class Source { public ICollection Items { get; set; } } 43 | 44 | public class SubSource { public int ID { get; set; } public string Name { get; set; } } 45 | 46 | public class SourceDto { public string[] Items { get; set; } } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExplicitExpansionAsDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 2 | { 3 | using System.Linq; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | public class ExplicitExpansionAsDataSource : AutoMapperSpecBase 8 | { 9 | private Dest[] _dests; 10 | 11 | public class Source 12 | { 13 | public ChildSource Child1 { get; set; } 14 | public ChildSource Child2 { get; set; } 15 | public ChildSource Child3 { get; set; } 16 | public ChildSource Child4 { get; set; } 17 | } 18 | 19 | public class ChildSource 20 | { 21 | public GrandChildSource GrandChild { get; set; } 22 | } 23 | 24 | public class GrandChildSource 25 | { 26 | 27 | } 28 | 29 | public class Dest 30 | { 31 | public ChildDest Child1 { get; set; } 32 | public ChildDest Child2 { get; set; } 33 | public ChildDest Child3 { get; set; } 34 | public ChildDest Child4 { get; set; } 35 | } 36 | 37 | public class ChildDest 38 | { 39 | public GrandChildDest GrandChild { get; set; } 40 | } 41 | 42 | public class GrandChildDest 43 | { 44 | 45 | } 46 | 47 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 48 | { 49 | 50 | cfg.CreateMap() 51 | .ForMember(m => m.Child1, opt => opt.ExplicitExpansion()) 52 | .ForMember(m => m.Child2, opt => opt.ExplicitExpansion()) 53 | .ForMember(m => m.Child4, opt => opt.ExplicitExpansion()) 54 | ; 55 | cfg.CreateMap() 56 | .ForMember(m => m.GrandChild, opt => opt.ExplicitExpansion()); 57 | 58 | cfg.CreateMap(); 59 | }); 60 | 61 | protected override void Because_of() 62 | { 63 | var sourceList = new[] 64 | { 65 | new Source 66 | { 67 | Child1 = new ChildSource(), 68 | Child2 = new ChildSource(), 69 | Child3 = new ChildSource(), 70 | Child4 = new ChildSource() 71 | { 72 | GrandChild = new GrandChildSource() 73 | } 74 | } 75 | }; 76 | 77 | _dests = sourceList.AsQueryable().UseAsDataSource(Configuration).For(d => d.Child2, d => d.Child4, d => d.Child4.GrandChild).ToArray(); 78 | } 79 | 80 | [Fact] 81 | public void Should_leave_non_expanded_item_null() 82 | { 83 | _dests[0].Child1.ShouldBeNull(); 84 | } 85 | 86 | [Fact] 87 | public void Should_expand_explicitly_expanded_item() 88 | { 89 | _dests[0].Child2.ShouldNotBeNull(); 90 | } 91 | 92 | [Fact] 93 | public void Should_default_to_expand() 94 | { 95 | _dests[0].Child3.ShouldNotBeNull(); 96 | } 97 | 98 | [Fact] 99 | public void Should_expand_full_path() 100 | { 101 | _dests[0].Child4.ShouldNotBeNull(); 102 | _dests[0].Child4.GrandChild.ShouldNotBeNull(); 103 | } 104 | 105 | } 106 | 107 | 108 | public class ExplicitExpansion_WithElementOperator_AsDataSource : AutoMapperSpecBase 109 | { 110 | private Dest _dest; 111 | 112 | public class Source 113 | { 114 | public ChildSource Child1 { get; set; } 115 | public ChildSource Child2 { get; set; } 116 | public ChildSource Child3 { get; set; } 117 | public ChildSource Child4 { get; set; } 118 | } 119 | 120 | public class ChildSource 121 | { 122 | public GrandChildSource GrandChild { get; set; } 123 | } 124 | 125 | public class GrandChildSource 126 | { 127 | 128 | } 129 | 130 | public class Dest 131 | { 132 | public ChildDest Child1 { get; set; } 133 | public ChildDest Child2 { get; set; } 134 | public ChildDest Child3 { get; set; } 135 | public ChildDest Child4 { get; set; } 136 | } 137 | 138 | public class ChildDest 139 | { 140 | public GrandChildDest GrandChild { get; set; } 141 | } 142 | 143 | public class GrandChildDest 144 | { 145 | 146 | } 147 | 148 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 149 | { 150 | cfg.CreateMap() 151 | .ForMember(m => m.Child1, opt => opt.ExplicitExpansion()) 152 | .ForMember(m => m.Child2, opt => opt.ExplicitExpansion()) 153 | .ForMember(m => m.Child4, opt => opt.ExplicitExpansion()) 154 | ; 155 | cfg.CreateMap() 156 | .ForMember(m => m.GrandChild, opt => opt.ExplicitExpansion()); 157 | 158 | cfg.CreateMap(); 159 | }); 160 | 161 | protected override void Because_of() 162 | { 163 | var sourceList = new[] 164 | { 165 | new Source 166 | { 167 | Child1 = new ChildSource(), 168 | Child2 = new ChildSource(), 169 | Child3 = new ChildSource(), 170 | Child4 = new ChildSource() 171 | { 172 | GrandChild = new GrandChildSource() 173 | } 174 | } 175 | }; 176 | 177 | _dest = sourceList.AsQueryable().UseAsDataSource(Configuration).For(d => d.Child2, d => d.Child4, d => d.Child4.GrandChild).FirstOrDefault(); 178 | } 179 | 180 | [Fact] 181 | public void Should_leave_non_expanded_item_null() 182 | { 183 | _dest.Child1.ShouldBeNull(); 184 | } 185 | 186 | [Fact] 187 | public void Should_expand_explicitly_expanded_item() 188 | { 189 | _dest.Child2.ShouldNotBeNull(); 190 | } 191 | 192 | [Fact] 193 | public void Should_default_to_expand() 194 | { 195 | _dest.Child3.ShouldNotBeNull(); 196 | } 197 | 198 | [Fact] 199 | public void Should_expand_full_path() 200 | { 201 | _dest.Child4.ShouldNotBeNull(); 202 | _dest.Child4.GrandChild.ShouldNotBeNull(); 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionConversion.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 9 | { 10 | public class ExpressionConversion 11 | { 12 | public class Source 13 | { 14 | public int Value { get; set; } 15 | public int Foo { get; set; } 16 | public ChildSrc Child { get; set; } 17 | } 18 | 19 | public class ChildSrc 20 | { 21 | public int Value { get; set; } 22 | } 23 | 24 | public class Dest 25 | { 26 | public int Value { get; set; } 27 | public int Bar { get; set; } 28 | public int ChildValue { get; set; } 29 | } 30 | 31 | public enum SourceEnum 32 | { 33 | Foo, 34 | Bar 35 | } 36 | 37 | public enum DestEnum 38 | { 39 | Foo, 40 | Bar 41 | } 42 | 43 | public class SourceWithEnum : Source 44 | { 45 | public SourceEnum Enum { get; set; } 46 | } 47 | 48 | public class DestWithEnum : Dest 49 | { 50 | public DestEnum Enum { get; set; } 51 | } 52 | 53 | [Fact] 54 | public void Can_map_unary_expression_converting_enum_to_int() 55 | { 56 | var config = new MapperConfiguration(cfg => 57 | { 58 | cfg.AddExpressionMapping(); 59 | cfg.CreateMap(); 60 | cfg.CreateMap(); 61 | }); 62 | 63 | Expression> expr = s => s.Enum == SourceEnum.Bar; 64 | 65 | var mapped = config.CreateMapper().MapExpression>, Expression>>(expr); 66 | 67 | var items = new[] 68 | { 69 | new DestWithEnum {Enum = DestEnum.Foo}, 70 | new DestWithEnum {Enum = DestEnum.Bar}, 71 | new DestWithEnum {Enum = DestEnum.Bar} 72 | }; 73 | 74 | var items2 = items.AsQueryable().Select(mapped).ToList(); 75 | } 76 | 77 | [Fact] 78 | public void Can_map_single_properties() 79 | { 80 | var config = new MapperConfiguration(cfg => 81 | { 82 | cfg.AddExpressionMapping(); 83 | cfg.CreateMap(); 84 | }); 85 | 86 | Expression> expr = d => d.Value == 10; 87 | 88 | var mapped = config.CreateMapper().Map>, Expression>>(expr); 89 | 90 | var items = new[] 91 | { 92 | new Source {Value = 10}, 93 | new Source {Value = 10}, 94 | new Source {Value = 15} 95 | }; 96 | 97 | items.AsQueryable().Where(mapped).Count().ShouldBe(2); 98 | } 99 | 100 | [Fact] 101 | public void Can_map_flattened_properties() 102 | { 103 | var config = new MapperConfiguration(cfg => 104 | { 105 | cfg.AddExpressionMapping(); 106 | cfg.CreateMap(); 107 | }); 108 | 109 | Expression> expr = d => d.ChildValue == 10; 110 | 111 | var mapped = config.CreateMapper().Map>, Expression>>(expr); 112 | 113 | var items = new[] 114 | { 115 | new Source {Child = new ChildSrc {Value = 10}}, 116 | new Source {Child = new ChildSrc {Value = 10}}, 117 | new Source {Child = new ChildSrc {Value = 15}} 118 | }; 119 | 120 | items.AsQueryable().Where(mapped).Count().ShouldBe(2); 121 | } 122 | 123 | [Fact] 124 | public void Can_map_custom_mapped_properties() 125 | { 126 | var config = new MapperConfiguration(cfg => 127 | { 128 | cfg.AddExpressionMapping(); 129 | cfg.CreateMap().ForMember(d => d.Bar, opt => opt.MapFrom(src => src.Foo)); 130 | }); 131 | 132 | Expression> expr = d => d.Bar == 10; 133 | 134 | var mapped = config.CreateMapper().Map>, Expression>>(expr); 135 | 136 | var items = new[] 137 | { 138 | new Source {Foo = 10}, 139 | new Source {Foo = 10}, 140 | new Source {Foo = 15} 141 | }; 142 | 143 | items.AsQueryable().Where(mapped).Count().ShouldBe(2); 144 | } 145 | 146 | [Fact] 147 | public void Throw_AutoMapperMappingException_if_expression_types_dont_match() 148 | { 149 | var config = new MapperConfiguration(cfg => 150 | { 151 | cfg.AddExpressionMapping(); 152 | cfg.CreateMap(); 153 | }); 154 | 155 | Expression> expr = d => d.Bar == 10; 156 | 157 | Assert.Throws(() => config.CreateMapper().Map>, Expression>>(expr)); 158 | } 159 | 160 | [Fact] 161 | public void Can_map_with_different_destination_types() 162 | { 163 | var config = new MapperConfiguration(cfg => 164 | { 165 | cfg.AddExpressionMapping(); 166 | cfg.CreateMap().ForMember(d => d.Bar, opt => opt.MapFrom(src => src.Foo)); 167 | }); 168 | 169 | Expression> expr = d => d; 170 | 171 | var mapped = config.CreateMapper().Map>, Expression>>(expr); 172 | 173 | var items = new[] 174 | { 175 | new Source {Foo = 10}, 176 | new Source {Foo = 10}, 177 | new Source {Foo = 15} 178 | }; 179 | 180 | var items2 = items.AsQueryable().Select(mapped).ToList(); 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using AutoMapper.Internal; 7 | using AutoMapper.QueryableExtensions; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 12 | { 13 | public class ExpressionPropertyMapping : NonValidatingSpecBase 14 | { 15 | 16 | public class SourceExpressionHolder 17 | { 18 | public Expression> Expression { get; set; } 19 | } 20 | 21 | public class DestExpressionHolder 22 | { 23 | public Expression> Expression { get; set; } 24 | } 25 | 26 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 27 | { 28 | cfg.AddExpressionMapping(); 29 | cfg.CreateMap().ReverseMap(); 30 | cfg.CreateMap().ReverseMap(); 31 | cfg.CreateMap() 32 | .ForMember(d => d.ID_, opt => opt.MapFrom(s => s.ID)) 33 | .ReverseMap() 34 | .ForMember(d => d.ID, opt => opt.MapFrom(s => s.ID_)); 35 | }); 36 | 37 | [Fact] 38 | public void Should_Map_Expressions_UsingExpressions() 39 | { 40 | var source = new SourceExpressionHolder() { Expression = p => p.Child != null }; 41 | var dest = Mapper.Map(source); 42 | } 43 | } 44 | 45 | public class ExpressionMapping : NonValidatingSpecBase 46 | { 47 | public class GrandParentDTO 48 | { 49 | public ParentDTO Parent { get; set; } 50 | } 51 | public class ParentDTO 52 | { 53 | public ICollection Children { get; set; } 54 | public ChildDTO Child { get; set; } 55 | public DateTime DateTime { get; set; } 56 | } 57 | 58 | public class ChildDTO 59 | { 60 | public ParentDTO Parent { get; set; } 61 | public ChildDTO GrandChild { get; set; } 62 | public int ID_ { get; set; } 63 | public int? IDs { get; set; } 64 | public int? ID2 { get; set; } 65 | } 66 | 67 | public class GrandParent 68 | { 69 | public Parent Parent { get; set; } 70 | } 71 | 72 | public class Parent 73 | { 74 | public ICollection Children { get; set; } 75 | 76 | private Child _child; 77 | public Child Child 78 | { 79 | get { return _child; } 80 | set 81 | { 82 | _child = value; 83 | _child.Parent = this; 84 | } 85 | } 86 | public DateTime DateTime { get; set; } 87 | } 88 | 89 | public class Child 90 | { 91 | private Parent _parent; 92 | public Parent Parent 93 | { 94 | get { return _parent; } 95 | set 96 | { 97 | _parent = value; 98 | if (GrandChild != null) 99 | GrandChild.Parent = _parent; 100 | } 101 | } 102 | 103 | public int ID { get; set; } 104 | public Child GrandChild { get; set; } 105 | public int IDs { get; set; } 106 | public int? ID2 { get; set; } 107 | } 108 | 109 | private Expression> _predicateExpression; 110 | private Parent _valid; 111 | 112 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 113 | { 114 | cfg.AddExpressionMapping(); 115 | cfg.CreateMap().ReverseMap(); 116 | cfg.CreateMap().ReverseMap(); 117 | cfg.CreateMap() 118 | .ForMember(d => d.ID_, opt => opt.MapFrom(s => s.ID)) 119 | .ReverseMap() 120 | .ForMember(d => d.ID, opt => opt.MapFrom(s => s.ID_)); 121 | cfg.Internal().EnableNullPropagationForQueryMapping = true; 122 | }); 123 | 124 | protected override void MainTeardown() 125 | { 126 | Should_Validate(); 127 | base.MainTeardown(); 128 | } 129 | 130 | private void Should_Validate() 131 | { 132 | var expression = Mapper.Map>>(_predicateExpression); 133 | var items = new[] {_valid}.AsQueryable(); 134 | items.Where(expression).ShouldContain(_valid); 135 | var items2 = items.UseAsDataSource(Mapper).For().Where(_predicateExpression); 136 | //var a = items2.ToList(); 137 | items2.Count().ShouldBe(1); 138 | } 139 | 140 | [Fact] 141 | public void GrandParent_Mapping_To_Sub_Sub_Property_Condition() 142 | { 143 | Expression> _predicateExpression = gp => gp.Parent.Children.Any(c => c.ID2 == 3); 144 | var expression = Mapper.Map>>(_predicateExpression); 145 | var items = new[] {new GrandParent(){Parent = new Parent(){Children = new[]{new Child(){ID2 = 3}}, Child = new Child(){ID2 = 3}}}}.AsQueryable(); 146 | items.Where(expression).ShouldContain(items.First()); 147 | var items2 = items.UseAsDataSource(Mapper).For().Where(_predicateExpression); 148 | items2.Count().ShouldBe(1); 149 | When_Use_Outside_Class_Method_Call(); 150 | } 151 | 152 | [Fact] 153 | public void GrandParent_Mapping_To_Sub_Sub_Property_Condition2() 154 | { 155 | Expression, bool>> _predicateExpression = gps => gps.Any(gp => gp.Parent.Children.Any(c => c.ID_ == 3)); 156 | Expression, IQueryable>> _predicateExpression2 = gps => gps.Where(gp => gp.Parent.Children.Any(c => c.ID_ == 3)); 157 | var expression = Mapper.Map, bool>>>(_predicateExpression); 158 | var expression2 = Mapper.Map, IQueryable>>>(_predicateExpression2); 159 | When_Use_Outside_Class_Method_Call(); 160 | } 161 | 162 | [Fact] 163 | public void When_Use_Outside_Class_Method_Call() 164 | { 165 | var ids = new[] { 4, 5 }; 166 | _predicateExpression = p => p.Children.Where((c, i) => c.ID_ > 4).Any(c => ids.Contains(c.ID_)); 167 | _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; 168 | } 169 | 170 | [Fact] 171 | public void When_Use_Property_From_Child_Property() 172 | { 173 | _predicateExpression = p => p.Child.ID_ > 4; 174 | _valid = new Parent { Child= new Child { ID = 5 } }; 175 | } 176 | 177 | [Fact] 178 | public void When_Use_Null_Substitution_Mappings_Against_Constants() 179 | { 180 | _predicateExpression = p => p.Child.ID_ > 4; 181 | _valid = new Parent { Child = new Child { ID = 5 } }; 182 | } 183 | 184 | [Fact] 185 | public void When_Use_Null_Substitution_Mappings_Against_Constants_Reverse_Order() 186 | { 187 | _predicateExpression = p => 4 < p.Child.ID_; 188 | _valid = new Parent { Child = new Child { ID = 5 } }; 189 | } 190 | 191 | [Fact] 192 | public void When_Use_Reverse_Null_Substitution_Mappings_Against_Constants() 193 | { 194 | _predicateExpression = p => p.Child.ID2 > 4; 195 | _valid = new Parent {Child = new Child {ID2 = 5}}; 196 | } 197 | 198 | [Fact] 199 | public void When_Use_Reverse_Null_Substitution_Mappings_Against_Constants_Reverse_Order() 200 | { 201 | _predicateExpression = p => 4 < p.Child.ID2; 202 | _valid = new Parent { Child = new Child { ID2 = 5 } }; 203 | } 204 | 205 | [Fact] 206 | public void When_Use_Sub_Lambda_Statement() 207 | { 208 | _predicateExpression = p => p.Children.Any(c => c.ID_ > 4); 209 | _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; 210 | } 211 | 212 | [Fact] 213 | public void When_Use_Multiple_Parameter_Lambda_Statement() 214 | { 215 | _predicateExpression = p => p.Children.Any((c, i) => c.ID_ > 4); 216 | _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; 217 | } 218 | 219 | [Fact] 220 | public void When_Use_Lambda_Statement_With_TypeMapped_Property_Being_Other_Than_First() 221 | { 222 | _predicateExpression = p => p.Children.AnyParamReverse((c, c2) => c.ID_ > 4); 223 | _valid = new Parent {Children = new[] {new Child {ID = 5}}}; 224 | } 225 | 226 | [Fact] 227 | public void When_Use_Child_TypeMap_In_Sub_Lambda_Statement() 228 | { 229 | _predicateExpression = p => p.Children.Any(c => c.GrandChild.GrandChild.ID_ == 4); 230 | _valid = new Parent 231 | { 232 | Children = new[] 233 | { 234 | new Child {GrandChild = new Child {GrandChild = new Child {ID = 4}}} 235 | } 236 | }; 237 | } 238 | 239 | [Fact] 240 | public void When_Use_Parent_And_Child_Lambda_Parameters_In_Child_Lambda_Statement() 241 | { 242 | _predicateExpression = p => p.Children.Any(c => c.GrandChild.ID_ == p.Child.ID_); 243 | _valid = new Parent 244 | { 245 | Child = new Child {ID = 4}, 246 | Children = new[] {new Child {GrandChild = new Child {ID = 4}}} 247 | }; 248 | } 249 | 250 | [Fact] 251 | public void When_Use_Lambdas_Where_Type_Matches_Parent_Object() 252 | { 253 | _predicateExpression = p => p.Child.Lambda(c => c.ID_ == 4); 254 | _valid = new Parent {Child = new Child {ID = 4}}; 255 | } 256 | 257 | [Fact] 258 | public void When_Reusing_TypeMaps() 259 | { 260 | _predicateExpression = p => p.Child.Parent.Child.GrandChild.ID_ == 4; 261 | _valid = new Parent {Child = new Child {GrandChild = new Child {ID = 4}}}; 262 | } 263 | 264 | [Fact] 265 | public void When_Using_Non_TypeMapped_Class_Property() 266 | { 267 | var year = DateTime.Now.Year; 268 | _predicateExpression = p => p.DateTime.Year == year; 269 | _valid = new Parent {DateTime = DateTime.Now}; 270 | } 271 | 272 | [Fact] 273 | public void When_Using_Non_TypeMapped_Class_Method() 274 | { 275 | var year = DateTime.Now.Year; 276 | _predicateExpression = p => p.DateTime.Year.Equals(year); 277 | _valid = new Parent { DateTime = DateTime.Now }; 278 | } 279 | 280 | [Fact] 281 | public void When_Using_Non_TypeMapped_Class_Property_Against_Constant() 282 | { 283 | _predicateExpression = p => p.DateTime.Year.ToString() == "2015"; 284 | _valid = new Parent { DateTime = new DateTime(2015, 1, 1) }; 285 | } 286 | 287 | [Fact] 288 | public void When_Using_Non_TypeMapped_Class_Method_Against_Constant() 289 | { 290 | _predicateExpression = p => p.DateTime.Year.ToString().Equals("2015"); 291 | _valid = new Parent { DateTime = new DateTime(2015, 1, 1) }; 292 | } 293 | 294 | [Fact] 295 | public void When_Using_Everything_At_Once() 296 | { 297 | var year = DateTime.Now.Year; 298 | _predicateExpression = p => p.DateTime.Year == year && p.Child.Parent.Child.GrandChild.Parent.Child.GrandChild.GrandChild.ID_ == 4 && p.Children.Any(c => c.GrandChild.GrandChild.ID_ == 4); 299 | _valid = new Parent { DateTime = DateTime.Now, Child = new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } }, Children = new[] { new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } } } }; 300 | } 301 | 302 | [Fact] 303 | public void When_Using_Static_Constants() 304 | { 305 | _predicateExpression = p => p.DateTime.Year.ToString() != string.Empty; 306 | _valid = new Parent { DateTime = DateTime.Now }; 307 | } 308 | } 309 | 310 | public class ExpressionsMappingWithClosures : NonValidatingSpecBase 311 | { 312 | public class TestData 313 | { 314 | public string Code { get; set; } 315 | } 316 | 317 | public class TestModel 318 | { 319 | public string Code { get; set; } 320 | } 321 | 322 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(config => config.CreateMap()); 323 | 324 | public void Should_map_with_closures() 325 | { 326 | var req = new TestData { Code = "DD" }; 327 | Expression> f = s => s.Code == req.Code; 328 | var result = (Expression>) Mapper.Map(f, typeof(Expression>), typeof(Expression>)); 329 | 330 | var func = result.Compile(); 331 | 332 | func(new TestModel {Code = "DD"}).ShouldBeTrue(); 333 | } 334 | } 335 | 336 | 337 | public class A : IQueryable 338 | { 339 | public IEnumerator GetEnumerator() 340 | { 341 | throw new NotImplementedException(); 342 | } 343 | 344 | IEnumerator IEnumerable.GetEnumerator() 345 | { 346 | return GetEnumerator(); 347 | } 348 | 349 | public Expression Expression { get; private set; } 350 | public Type ElementType { get; private set; } 351 | public IQueryProvider Provider { get; private set; } 352 | } 353 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromBaseClass.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 12 | { 13 | public class ExpressionMappingPropertyFromBaseClass : AutoMapperSpecBase 14 | { 15 | private List _source; 16 | private IQueryable entityQuery; 17 | 18 | public class BaseDTO 19 | { 20 | public Guid Id { get; set; } 21 | } 22 | 23 | public class BaseEntity 24 | { 25 | public Guid Id { get; set; } 26 | } 27 | 28 | public class DTO : BaseDTO 29 | { 30 | public string Name { get; set; } 31 | } 32 | 33 | public class Entity : BaseEntity 34 | { 35 | public string Name { get; set; } 36 | } 37 | 38 | protected override MapperConfiguration Configuration 39 | { 40 | get 41 | { 42 | var config = new MapperConfiguration(cfg => 43 | { 44 | cfg.AddExpressionMapping(); 45 | // issue #1886 46 | cfg.CreateMap(); 47 | cfg.CreateMap(); 48 | }); 49 | return config; 50 | } 51 | } 52 | 53 | protected override void Because_of() 54 | { 55 | //Arrange 56 | var guid = Guid.NewGuid(); 57 | var entity = new Entity { Id = guid, Name = "Sofia" }; 58 | _source = new List { entity }; 59 | 60 | // Act 61 | Expression> dtoQueryExpression = r => r.Id == guid; 62 | var entityQueryExpression = Mapper.Map>>(dtoQueryExpression); 63 | entityQuery = _source.AsQueryable().Where(entityQueryExpression); 64 | } 65 | 66 | [Fact] 67 | [Description("Fix for issue #1886")] 68 | public void Should_support_propertypath_expressions_with_properties_from_assignable_types() 69 | { 70 | // Assert 71 | entityQuery.ToList().Count().ShouldBe(1); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromDerviedType.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 10 | { 11 | public class ExpressionMappingPropertyFromDerviedType : AutoMapperSpecBase 12 | { 13 | private List _source; 14 | private IQueryable entityQuery; 15 | 16 | protected override MapperConfiguration Configuration 17 | { 18 | get 19 | { 20 | var config = new MapperConfiguration(cfg => 21 | { 22 | cfg.AddExpressionMapping(); 23 | cfg.AddProfile(typeof(DerivedTypeProfile)); 24 | }); 25 | return config; 26 | } 27 | } 28 | 29 | protected override void Because_of() 30 | { 31 | //Arrange 32 | _source = new List { 33 | new Entity { Id = Guid.NewGuid(), Name = "Sofia" }, 34 | new Entity { Id = Guid.NewGuid(), Name = "Rafael" }, 35 | new BaseEntity { Id = Guid.NewGuid() } 36 | }; 37 | } 38 | 39 | [Fact] 40 | public void Should_support_propertypath_expressions_with_properties_from_sub_types_using_explicit_cast() 41 | { 42 | // Act 43 | Expression> dtoQueryExpression = r => (r is DTO ? ((DTO)r).Name : "") == "Sofia"; 44 | Expression> entityQueryExpression = Mapper.MapExpression>>(dtoQueryExpression); 45 | entityQuery = _source.AsQueryable().Where(entityQueryExpression); 46 | 47 | // Assert 48 | entityQuery.ToList().Count().ShouldBe(1); 49 | } 50 | 51 | [Fact] 52 | public void Should_support_propertypath_expressions_with_properties_from_sub_types_using_as_keyword() 53 | { 54 | // Act 55 | Expression> dtoQueryExpression = r => (r is DTO ? (r as DTO).Name : "") == "Sofia"; 56 | Expression> entityQueryExpression = Mapper.MapExpression>>(dtoQueryExpression); 57 | entityQuery = _source.AsQueryable().Where(entityQueryExpression); 58 | 59 | // Assert 60 | entityQuery.ToList().Count().ShouldBe(1); 61 | } 62 | 63 | public class DerivedTypeProfile : Profile 64 | { 65 | public DerivedTypeProfile() 66 | { 67 | CreateMap(); 68 | 69 | CreateMap() 70 | .ForMember(dest => dest.Description, opts => opts.MapFrom(src => string.Concat(src.Id.ToString(), " - ", src.Name))) 71 | .IncludeBase(); 72 | } 73 | } 74 | 75 | public class BaseDTO 76 | { 77 | public Guid Id { get; set; } 78 | } 79 | 80 | public class BaseEntity 81 | { 82 | public Guid Id { get; set; } 83 | } 84 | 85 | public class DTO : BaseDTO 86 | { 87 | public string Name { get; set; } 88 | public string Description { get; set; } 89 | } 90 | 91 | public class Entity : BaseEntity 92 | { 93 | public string Name { get; set; } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingWithUseAsDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 2 | { 3 | using AutoMapper; 4 | using AutoMapper.QueryableExtensions; 5 | using Shouldly; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Linq.Expressions; 10 | using Xunit; 11 | 12 | public class ExpressionMappingWithUseAsDataSource 13 | { 14 | [Fact] 15 | public void When_Apply_Where_Clause_Over_Queryable_As_Data_Source() 16 | { 17 | // Arrange 18 | var mapper = CreateMapper(); 19 | 20 | var models = new List() 21 | { 22 | new Model { ABoolean = true }, 23 | new Model { ABoolean = false }, 24 | new Model { ABoolean = true }, 25 | new Model { ABoolean = false } 26 | }; 27 | 28 | var queryable = models.AsQueryable(); 29 | 30 | Expression> expOverDTO = (dto) => dto.Nested.AnotherBoolean; 31 | 32 | // Act 33 | var result = queryable 34 | .UseAsDataSource(mapper) 35 | .For() 36 | .Where(expOverDTO) 37 | .ToList(); 38 | 39 | // Assert 40 | result.ShouldNotBeNull(); 41 | result.Count.ShouldBe(2); 42 | result.ShouldAllBe(expOverDTO); 43 | } 44 | 45 | [Fact] 46 | public void Should_Map_From_Generic_Type() 47 | { 48 | // Arrange 49 | var mapper = CreateMapper(); 50 | 51 | var models = new List>() 52 | { 53 | new GenericModel {ABoolean = true}, 54 | new GenericModel {ABoolean = false}, 55 | new GenericModel {ABoolean = true}, 56 | new GenericModel {ABoolean = false} 57 | }; 58 | 59 | var queryable = models.AsQueryable(); 60 | 61 | Expression> expOverDTO = (dto) => dto.Nested.AnotherBoolean; 62 | 63 | // Act 64 | var q = queryable.UseAsDataSource(mapper).For().Where(expOverDTO); 65 | 66 | var result = q.ToList(); 67 | 68 | // Assert 69 | result.ShouldNotBeNull(); 70 | result.Count.ShouldBe(2); 71 | result.ShouldAllBe(expOverDTO); 72 | } 73 | 74 | private static IMapper CreateMapper() 75 | { 76 | var mapperConfig = new MapperConfiguration(cfg => 77 | { 78 | cfg.CreateMap() 79 | .ForMember(d => d.Nested, opt => opt.MapFrom(s => s)); 80 | cfg.CreateMap() 81 | .ForMember(d => d.AnotherBoolean, opt => opt.MapFrom(s => s.ABoolean)); 82 | cfg.CreateMap() 83 | .ForMember(d => d.ABoolean, opt => opt.MapFrom(s => s.Nested.AnotherBoolean)); 84 | cfg.CreateMap, DTO>() 85 | .ForMember(d => d.Nested, opt => opt.MapFrom(s => s)); 86 | cfg.CreateMap, DTO.DTONested>() 87 | .ForMember(d => d.AnotherBoolean, opt => opt.MapFrom(s => s.ABoolean)); 88 | }); 89 | 90 | var mapper = mapperConfig.CreateMapper(); 91 | return mapper; 92 | } 93 | 94 | private class DTO 95 | { 96 | public class DTONested 97 | { 98 | public bool AnotherBoolean { get; set; } 99 | } 100 | 101 | public DTONested Nested { get; set; } 102 | } 103 | 104 | private class Model 105 | { 106 | public bool ABoolean { get; set; } 107 | } 108 | 109 | 110 | private class GenericModel 111 | { 112 | public T ABoolean { get; set; } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/GenericTestExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 6 | { 7 | public static class GenericTestExtensionMethods 8 | { 9 | public static bool Any(this IEnumerable self, Func func) 10 | { 11 | return self.Where(func).Any(); 12 | } 13 | 14 | public static bool AnyParamReverse(this IEnumerable self, Func func) 15 | { 16 | return self.Any(t => func(t,t)); 17 | } 18 | 19 | public static bool Lambda(this T self, Func func) 20 | { 21 | return func(self); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithCustomExpressions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class MappingMemberInitWithCustomExpressions 10 | { 11 | [Fact] 12 | public void Map_member_init_using_custom_expressions() 13 | { 14 | //Arrange 15 | var config = GetConfiguration(); 16 | config.AssertConfigurationIsValid(); 17 | var mapper = config.CreateMapper(); 18 | Expression> selection = s => new PlayerDto 19 | { 20 | NameDto = s.NameDto, 21 | StatsADto = new StatsDto 22 | { 23 | SpeedValueDto = s.StatsADto.SpeedValueDto, 24 | PowerDto = s.StatsADto.PowerDto, 25 | RatingDto = s.StatsADto.RatingDto, 26 | StatsBuilderDto = new BuilderDto 27 | { 28 | IdDto = s.StatsADto.StatsBuilderDto.IdDto, 29 | CityDto = s.StatsADto.StatsBuilderDto.CityDto 30 | } 31 | } 32 | }; 33 | 34 | //Act 35 | Expression> selectionMapped = mapper.MapExpression>>(selection); 36 | List result = Players.Select(selectionMapped).ToList(); 37 | 38 | //Assert 39 | Assert.Equal("Jack", result[0].Name); 40 | Assert.Equal(1, result[0].StatsA.Power); 41 | Assert.Equal(2, result[0].StatsA.SpeedValue); 42 | Assert.Equal(5, result[0].StatsA.Rating); 43 | Assert.Equal(1, result[0].StatsA.StatsBuilder.Id); 44 | Assert.Equal("Atlanta", result[0].StatsA.StatsBuilder.City); 45 | } 46 | 47 | MapperConfiguration GetConfiguration() 48 | => new 49 | ( 50 | cfg => 51 | { 52 | cfg.AddExpressionMapping(); 53 | 54 | cfg.CreateMap() 55 | .ForMember(dest => dest.NameDto, opt => opt.MapFrom(src => src.Name)) 56 | .ForMember(dest => dest.StatsADto, opt => opt.MapFrom(src => src.StatsA)); 57 | 58 | cfg.CreateMap() 59 | .ForMember(dest => dest.SpeedValueDto, opt => opt.MapFrom(src => src.SpeedValue)) 60 | .ForMember(dest => dest.PowerDto, opt => opt.MapFrom(src => src.Power)) 61 | .ForMember(dest => dest.RatingDto, opt => opt.MapFrom(src => src.Rating)) 62 | .ForMember(dest => dest.StatsBuilderDto, opt => opt.MapFrom(src => src.StatsBuilder)); 63 | 64 | cfg.CreateMap() 65 | .ForMember(dest => dest.IdDto, opt => opt.MapFrom(src => src.Id)) 66 | .ForMember(dest => dest.CityDto, opt => opt.MapFrom(src => src.City)); 67 | } 68 | ); 69 | 70 | readonly IQueryable Players = new List 71 | { 72 | new Player 73 | { 74 | Name = "Jack", 75 | StatsA = new Stats 76 | { 77 | Power = 1, 78 | SpeedValue = 2, 79 | Rating = 5, 80 | StatsBuilder = new Builder 81 | { 82 | Id = 1, 83 | City = "Atlanta" 84 | } 85 | } 86 | }, 87 | new Player 88 | { 89 | Name = "Jane", 90 | StatsA = new Stats 91 | { 92 | Power = 1, 93 | SpeedValue = 3, 94 | Rating = 6, 95 | StatsBuilder = new Builder 96 | { 97 | Id = 2, 98 | City = "Charlotte" 99 | } 100 | } 101 | } 102 | }.AsQueryable(); 103 | 104 | public class Player 105 | { 106 | public string Name { get; set; } 107 | public Stats StatsA { get; set; } 108 | } 109 | 110 | public class Stats 111 | { 112 | public int SpeedValue { get; set; } 113 | public int Power { get; set; } 114 | public int Rating { get; set; } 115 | public Builder StatsBuilder { get; set; } 116 | } 117 | 118 | public class Builder 119 | { 120 | public int Id { get; set; } 121 | public string City { get; set; } 122 | } 123 | 124 | public class PlayerDto 125 | { 126 | public string NameDto { get; set; } 127 | public StatsDto StatsADto { get; set; } 128 | } 129 | 130 | public class StatsDto 131 | { 132 | public int SpeedValueDto { get; set; } 133 | public int PowerDto { get; set; } 134 | public int RatingDto { get; set; } 135 | public BuilderDto StatsBuilderDto { get; set; } 136 | } 137 | 138 | public class BuilderDto 139 | { 140 | public int IdDto { get; set; } 141 | public string CityDto { get; set; } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MemberMappingsOfLiteralParentTypesMustMatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Xunit; 4 | 5 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 6 | { 7 | public class MemberMappingsOfLiteralParentTypesMustMatch 8 | { 9 | [Fact] 10 | public void MappingMemberOfNullableParentToMemberOfNonNullableParentWithoutCustomExpressionsThrowsException() 11 | { 12 | //arrange 13 | var mapper = GetMapper(); 14 | Expression> expression = x => x.DateTimeOffset.Value.Day.ToString() == "2"; 15 | 16 | //act 17 | var exception = Assert.Throws(() => mapper.MapExpression>>(expression)); 18 | 19 | //assert 20 | Assert.StartsWith 21 | ( 22 | "For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match.", 23 | exception.Message 24 | ); 25 | } 26 | 27 | [Fact] 28 | public void MappingMemberOfNonNullableParentToMemberOfNullableParentWithoutCustomExpressionsThrowsException() 29 | { 30 | //arrange 31 | var mapper = GetMapper(); 32 | Expression> expression = x => x.DateTime.Day.ToString() == "2"; 33 | 34 | //act 35 | var exception = Assert.Throws(() => mapper.MapExpression>>(expression)); 36 | 37 | //assert 38 | Assert.StartsWith 39 | ( 40 | "For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match.", 41 | exception.Message 42 | ); 43 | } 44 | 45 | [Fact] 46 | public void MappingMemberOfNullableParentToMemberOfNonNullableParentWorksUsingCustomExpressions() 47 | { 48 | //arrange 49 | var mapper = GetMapperWithCustomExpressions(); 50 | Expression> expression = x => x.DateTimeOffset.Value.Day.ToString() == "2"; 51 | 52 | //act 53 | var mappedExpression = mapper.MapExpression>>(expression); 54 | 55 | //assert 56 | Assert.NotNull(mappedExpression); 57 | Func func = mappedExpression.Compile(); 58 | Assert.False(func(new Product { DateTimeOffset = new DateTimeOffset(new DateTime(2000, 3, 3), TimeSpan.Zero) })); 59 | Assert.True(func(new Product { DateTimeOffset = new DateTimeOffset(new DateTime(2000, 2, 2), TimeSpan.Zero) })); 60 | } 61 | 62 | [Fact] 63 | public void MappingMemberOfNonNullableParentToMemberOfNullableParentWorksUsingCustomExpressions() 64 | { 65 | //arrange 66 | var mapper = GetMapperWithCustomExpressions(); 67 | Expression> expression = x => x.DateTime.Day.ToString() == "2"; 68 | 69 | //act 70 | var mappedExpression = mapper.MapExpression>>(expression); 71 | 72 | //assert 73 | Assert.NotNull(mappedExpression); 74 | Func func = mappedExpression.Compile(); 75 | Assert.False(func(new Product { DateTime = new DateTime(2000, 3, 3) })); 76 | Assert.True(func(new Product { DateTime = new DateTime(2000, 2, 2) })); 77 | } 78 | 79 | 80 | private static IMapper GetMapper() 81 | { 82 | var config = new MapperConfiguration(c => 83 | { 84 | c.CreateMap(); 85 | }); 86 | config.AssertConfigurationIsValid(); 87 | return config.CreateMapper(); 88 | } 89 | 90 | private static IMapper GetMapperWithCustomExpressions() 91 | { 92 | var config = new MapperConfiguration(c => 93 | { 94 | c.CreateMap() 95 | .ForMember(d => d.DateTime, o => o.MapFrom(s => s.DateTime.Value)) 96 | .ForMember(d => d.DateTimeOffset, o => o.MapFrom(s => (DateTimeOffset?)s.DateTimeOffset)); 97 | }); 98 | config.AssertConfigurationIsValid(); 99 | return config.CreateMapper(); 100 | } 101 | 102 | class Product 103 | { 104 | public DateTime? DateTime { get; set; } 105 | public DateTimeOffset DateTimeOffset { get; set; } 106 | } 107 | 108 | class ProductModel 109 | { 110 | public DateTime DateTime { get; set; } 111 | public DateTimeOffset? DateTimeOffset { get; set; } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ParameterizedQueriesTestsAsDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | public class ParameterizedQueriesTests_with_anonymous_object_AsDataSource : AutoMapperSpecBase 11 | { 12 | private Dest[] _dests; 13 | private IQueryable _sources; 14 | 15 | public class Source 16 | { 17 | } 18 | 19 | public class Dest 20 | { 21 | public int Value { get; set; } 22 | } 23 | 24 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 25 | { 26 | int value = 0; 27 | 28 | Expression> sourceMember = src => value + 5; 29 | cfg.CreateMap() 30 | .ForMember(dest => dest.Value, opt => opt.MapFrom(sourceMember)); 31 | }); 32 | 33 | protected override void Because_of() 34 | { 35 | _sources = new[] 36 | { 37 | new Source() 38 | }.AsQueryable(); 39 | 40 | _dests = _sources.UseAsDataSource(Configuration).For(new { value = 10 }).ToArray(); 41 | } 42 | 43 | [Fact] 44 | public void Should_substitute_parameter_value() 45 | { 46 | _dests[0].Value.ShouldBe(15); 47 | } 48 | 49 | [Fact] 50 | public void Should_not_cache_parameter_value() 51 | { 52 | var newDests = _sources.UseAsDataSource(Configuration).For(new { value = 15 }).ToArray(); 53 | 54 | newDests[0].Value.ShouldBe(20); 55 | } 56 | } 57 | 58 | public class ParameterizedQueriesTests_with_dictionary_object_AsDataSource : AutoMapperSpecBase 59 | { 60 | private Dest[] _dests; 61 | private IQueryable _sources; 62 | 63 | public class Source 64 | { 65 | } 66 | 67 | public class Dest 68 | { 69 | public int Value { get; set; } 70 | } 71 | 72 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 73 | { 74 | int value = 0; 75 | 76 | Expression> sourceMember = src => value + 5; 77 | cfg.CreateMap() 78 | .ForMember(dest => dest.Value, opt => opt.MapFrom(sourceMember)); 79 | }); 80 | 81 | protected override void Because_of() 82 | { 83 | _sources = new[] 84 | { 85 | new Source() 86 | }.AsQueryable(); 87 | 88 | _dests = _sources.UseAsDataSource(Configuration).For(new Dictionary { { "value", 10 } }).ToArray(); 89 | } 90 | 91 | [Fact] 92 | public void Should_substitute_parameter_value() 93 | { 94 | _dests[0].Value.ShouldBe(15); 95 | } 96 | 97 | [Fact] 98 | public void Should_not_cache_parameter_value() 99 | { 100 | var newDests = _sources.UseAsDataSource(Configuration).For(new Dictionary { { "value", 15 } }).ToArray(); 101 | 102 | newDests[0].Value.ShouldBe(20); 103 | } 104 | } 105 | 106 | public class ParameterizedQueriesTests_with_filter_AsDataSource : AutoMapperSpecBase 107 | { 108 | public class User 109 | { 110 | public int Id { get; set; } 111 | public string Name { get; set; } 112 | public DateTime? DateActivated { get; set; } 113 | } 114 | 115 | public class UserViewModel 116 | { 117 | public int Id { get; set; } 118 | public string Name { get; set; } 119 | public DateTime? DateActivated { get; set; } 120 | public int position { get; set; } 121 | } 122 | 123 | public class DB 124 | { 125 | public DB() 126 | { 127 | Users = new List() 128 | { 129 | new User {DateActivated = new DateTime(2000, 1, 1), Id = 1, Name = "Joe Schmoe"}, 130 | new User {DateActivated = new DateTime(2000, 2, 1), Id = 2, Name = "John Schmoe"}, 131 | new User {DateActivated = new DateTime(2000, 3, 1), Id = 3, Name = "Jim Schmoe"}, 132 | }.AsQueryable(); 133 | } 134 | public IQueryable Users { get; } 135 | } 136 | 137 | protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => 138 | { 139 | DB db = null; 140 | 141 | cfg.CreateMap() 142 | .ForMember(a => a.position, 143 | opt => opt.MapFrom(src => db.Users.Count(u => u.DateActivated < src.DateActivated))); 144 | }); 145 | 146 | [Fact] 147 | public void Should_only_replace_outer_parameters() 148 | { 149 | var db = new DB(); 150 | 151 | var user = db.Users.UseAsDataSource(Configuration).For(new { db }).FirstOrDefault(a => a.Id == 2); 152 | 153 | user.position.ShouldBe(1); 154 | } 155 | 156 | [Fact] 157 | public void Should_make_element_operators_queryable_First() 158 | { 159 | var db = new DB(); 160 | 161 | var user = db.Users.UseAsDataSource(Configuration).For(new { db }).First(a => a.Id == 2); 162 | 163 | user.position.ShouldBe(1); 164 | } 165 | 166 | [Fact] 167 | public void Should_make_element_operators_queryable_FirstOrDefault() 168 | { 169 | var db = new DB(); 170 | 171 | var user = db.Users.UseAsDataSource(Configuration).For(new { db }).FirstOrDefault(a => a.Id == -1); 172 | 173 | user.ShouldBeNull(); 174 | } 175 | 176 | 177 | [Fact] 178 | public void Should_make_element_operators_queryable_Single() 179 | { 180 | var db = new DB(); 181 | 182 | var user = db.Users.UseAsDataSource(Configuration).For(new { db }).Single(a => a.Id == 2); 183 | 184 | user.position.ShouldBe(1); 185 | } 186 | 187 | [Fact] 188 | public void Should_make_element_operators_queryable_SingleOrDefault() 189 | { 190 | var db = new DB(); 191 | 192 | var user = db.Users.UseAsDataSource(Configuration).For(new { db }).SingleOrDefault(a => a.Id == -1); 193 | 194 | user.ShouldBeNull(); 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldOnlyMapExistingTypeMaps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class ShouldOnlyMapExistingTypeMaps 10 | { 11 | [Fact] 12 | public void Issue85() 13 | { 14 | var config = new MapperConfiguration(cfg => 15 | { 16 | cfg.AddExpressionMapping(); 17 | 18 | cfg.CreateMap() 19 | .ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => s.Name))); 20 | 21 | cfg.CreateMap() 22 | .ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => new SubSource { Name = s }))); 23 | }); 24 | 25 | var mapper = config.CreateMapper(); 26 | 27 | Expression> expression1 = o => string.Equals("item1", "item2"); 28 | var mapped1 = mapper.MapExpression>>(expression1); 29 | 30 | Expression> expression2 = o => string.Equals("item1", "item2"); 31 | var mapped2 = mapper.MapExpression>>(expression2); 32 | 33 | Assert.NotNull(mapped1); 34 | Assert.NotNull(mapped2); 35 | } 36 | 37 | [Fact] 38 | public void Issue93() 39 | { 40 | var config = new MapperConfiguration(cfg => 41 | { 42 | cfg.AddExpressionMapping(); 43 | 44 | cfg.CreateMap() 45 | .ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => s.Name))); 46 | }); 47 | 48 | var mapper = config.CreateMapper(); 49 | 50 | Expression> expression1 = 51 | src => ((src != null ? src : null) != null) && src.Items.Any(x => x == "item1"); 52 | 53 | var mapped1 = mapper.MapExpression>>(expression1); 54 | 55 | Assert.NotNull(mapped1); 56 | } 57 | 58 | public class Source { public ICollection Items { get; set; } } 59 | 60 | public class SubSource { public int ID { get; set; } public string Name { get; set; } } 61 | 62 | public class SourceDto { public string[] Items { get; set; } } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OData.Edm; 2 | using System; 3 | using System.Linq.Expressions; 4 | using Xunit; 5 | 6 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 7 | { 8 | public class ShouldThrowInvalidOperationExceptionForUnmatchedLiterals 9 | { 10 | [Theory] 11 | [InlineData(nameof(ProductModel.Bool), typeof(bool?))] 12 | [InlineData(nameof(ProductModel.DateTime), typeof(DateTime?))] 13 | [InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset?))] 14 | [InlineData(nameof(ProductModel.Date), typeof(Date?))] 15 | [InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly?))] 16 | [InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan?))] 17 | [InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay?))] 18 | [InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly?))] 19 | [InlineData(nameof(ProductModel.Guid), typeof(Guid?))] 20 | [InlineData(nameof(ProductModel.Decimal), typeof(decimal?))] 21 | [InlineData(nameof(ProductModel.Byte), typeof(byte?))] 22 | [InlineData(nameof(ProductModel.Short), typeof(short?))] 23 | [InlineData(nameof(ProductModel.Int), typeof(int?))] 24 | [InlineData(nameof(ProductModel.Long), typeof(long?))] 25 | [InlineData(nameof(ProductModel.Float), typeof(float?))] 26 | [InlineData(nameof(ProductModel.Double), typeof(double?))] 27 | [InlineData(nameof(ProductModel.Char), typeof(char?))] 28 | [InlineData(nameof(ProductModel.SByte), typeof(sbyte?))] 29 | [InlineData(nameof(ProductModel.UShort), typeof(ushort?))] 30 | [InlineData(nameof(ProductModel.ULong), typeof(ulong?))] 31 | public void ThrowsCreatingBinaryExpressionCombiningNonNullableParameterWithNullableConstant(string memberName, Type constantType) 32 | { 33 | ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x"); 34 | MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName)); 35 | 36 | Assert.Throws 37 | ( 38 | () => Expression.Lambda> 39 | ( 40 | Expression.Equal 41 | ( 42 | property, 43 | Expression.Constant(Activator.CreateInstance(constantType), constantType) 44 | ), 45 | productParam 46 | ) 47 | ); 48 | } 49 | 50 | [Theory] 51 | [InlineData(nameof(Product.Bool), typeof(bool))] 52 | [InlineData(nameof(Product.DateTime), typeof(DateTime))] 53 | [InlineData(nameof(Product.DateTimeOffset), typeof(DateTimeOffset))] 54 | [InlineData(nameof(Product.Date), typeof(Date))] 55 | [InlineData(nameof(Product.DateOnly), typeof(DateOnly))] 56 | [InlineData(nameof(Product.TimeSpan), typeof(TimeSpan))] 57 | [InlineData(nameof(Product.TimeOfDay), typeof(TimeOfDay))] 58 | [InlineData(nameof(Product.TimeOnly), typeof(TimeOnly))] 59 | [InlineData(nameof(Product.Guid), typeof(Guid))] 60 | [InlineData(nameof(Product.Decimal), typeof(decimal))] 61 | [InlineData(nameof(Product.Byte), typeof(byte))] 62 | [InlineData(nameof(Product.Short), typeof(short))] 63 | [InlineData(nameof(Product.Int), typeof(int))] 64 | [InlineData(nameof(Product.Long), typeof(long))] 65 | [InlineData(nameof(Product.Float), typeof(float))] 66 | [InlineData(nameof(Product.Double), typeof(double))] 67 | [InlineData(nameof(Product.Char), typeof(char))] 68 | [InlineData(nameof(Product.SByte), typeof(sbyte))] 69 | [InlineData(nameof(Product.UShort), typeof(ushort))] 70 | [InlineData(nameof(Product.ULong), typeof(ulong))] 71 | public void ThrowsCreatingBinaryExpressionCombiningNullableParameterWithNonNullableConstant(string memberName, Type constantType) 72 | { 73 | ParameterExpression productParam = Expression.Parameter(typeof(Product), "x"); 74 | MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(Product), memberName)); 75 | 76 | var ex = Assert.Throws 77 | ( 78 | () => Expression.Lambda> 79 | ( 80 | Expression.Equal 81 | ( 82 | property, 83 | Expression.Constant(Activator.CreateInstance(constantType), constantType) 84 | ), 85 | productParam 86 | ) 87 | ); 88 | } 89 | 90 | class Product 91 | { 92 | public bool? Bool { get; set; } 93 | public DateTimeOffset? DateTimeOffset { get; set; } 94 | public DateTime? DateTime { get; set; } 95 | public Date? Date { get; set; } 96 | public DateOnly? DateOnly { get; set; } 97 | public TimeSpan? TimeSpan { get; set; } 98 | public TimeOfDay? TimeOfDay { get; set; } 99 | public TimeOnly? TimeOnly { get; set; } 100 | public Guid? Guid { get; set; } 101 | public decimal? Decimal { get; set; } 102 | public byte? Byte { get; set; } 103 | public short? Short { get; set; } 104 | public int? Int { get; set; } 105 | public long? Long { get; set; } 106 | public float? Float { get; set; } 107 | public double? Double { get; set; } 108 | public char? Char { get; set; } 109 | public sbyte? SByte { get; set; } 110 | public ushort? UShort { get; set; } 111 | public uint? UInt { get; set; } 112 | public ulong? ULong { get; set; } 113 | } 114 | 115 | class ProductModel 116 | { 117 | public bool Bool { get; set; } 118 | public DateTimeOffset DateTimeOffset { get; set; } 119 | public DateTime DateTime { get; set; } 120 | public Date Date { get; set; } 121 | public DateOnly DateOnly { get; set; } 122 | public TimeSpan TimeSpan { get; set; } 123 | public TimeOfDay TimeOfDay { get; set; } 124 | public TimeOnly TimeOnly { get; set; } 125 | public Guid Guid { get; set; } 126 | public decimal Decimal { get; set; } 127 | public byte Byte { get; set; } 128 | public short Short { get; set; } 129 | public int Int { get; set; } 130 | public long Long { get; set; } 131 | public float Float { get; set; } 132 | public double Double { get; set; } 133 | public char Char { get; set; } 134 | public sbyte SByte { get; set; } 135 | public ushort UShort { get; set; } 136 | public uint UInt { get; set; } 137 | public ulong ULong { get; set; } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldUseDeclaringTypeForInstanceMethodCalls.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class ShouldUseDeclaringTypeForInstanceMethodCalls 10 | { 11 | [Fact] 12 | public void MethodInfoShouldRetainDeclaringTypeInMappedExpression() 13 | { 14 | //Arrange 15 | var config = new MapperConfiguration 16 | ( 17 | cfg => 18 | { 19 | cfg.CreateMap(); 20 | cfg.CreateMap(); 21 | } 22 | ); 23 | config.AssertConfigurationIsValid(); 24 | var mapper = config.CreateMapper(); 25 | Expression> filter = e => e.SimpleEnum.HasFlag(SimpleEnum.Value3); 26 | EntityModel entityModel1 = new() { SimpleEnum = SimpleEnumModel.Value3 }; 27 | EntityModel entityModel2 = new() { SimpleEnum = SimpleEnumModel.Value2 }; 28 | 29 | //act 30 | Expression> mappedFilter = mapper.MapExpression>>(filter); 31 | 32 | //assert 33 | Assert.Equal(typeof(Enum), HasFlagVisitor.GetasFlagReflectedType(mappedFilter)); 34 | Assert.Single(new List { entityModel1 }.AsQueryable().Where(mappedFilter)); 35 | Assert.Empty(new List { entityModel2 }.AsQueryable().Where(mappedFilter)); 36 | } 37 | 38 | public enum SimpleEnum 39 | { 40 | Value1, 41 | Value2, 42 | Value3 43 | } 44 | 45 | public record Entity 46 | { 47 | public int Id { get; init; } 48 | public SimpleEnum SimpleEnum { get; init; } 49 | } 50 | 51 | public enum SimpleEnumModel 52 | { 53 | Value1, 54 | Value2, 55 | Value3 56 | } 57 | 58 | public record EntityModel 59 | { 60 | public int Id { get; init; } 61 | public SimpleEnumModel SimpleEnum { get; init; } 62 | } 63 | 64 | public class HasFlagVisitor : ExpressionVisitor 65 | { 66 | public static Type GetasFlagReflectedType(Expression expression) 67 | { 68 | HasFlagVisitor hasFlagVisitor = new(); 69 | hasFlagVisitor.Visit(expression); 70 | return hasFlagVisitor.HasFlagReflectedType; 71 | } 72 | protected override Expression VisitMethodCall(MethodCallExpression node) 73 | { 74 | if (node.Method.Name == "HasFlag") 75 | HasFlagReflectedType = node.Method.ReflectedType; 76 | 77 | return base.VisitMethodCall(node); 78 | } 79 | 80 | public Type HasFlagReflectedType { get; private set; } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace AutoMapper.Extensions.ExpressionMapping.UnitTests 8 | { 9 | public class XpressionMapperForPathTests 10 | { 11 | public XpressionMapperForPathTests() 12 | { 13 | SetupAutoMapper(); 14 | SetupQueryableCollection(); 15 | } 16 | 17 | #region Tests 18 | [Fact] 19 | public void Works_for_inherited_properties() 20 | { 21 | //Arrange 22 | Expression> selection = s => s.Nested.NestedTitle2 == "nested test"; 23 | 24 | //Act 25 | Expression> selectionMapped = mapper.Map>>(selection); 26 | List items = DataObjects.Where(selectionMapped).ToList(); 27 | 28 | //Assert 29 | Assert.True(items.Count == 1); 30 | } 31 | 32 | [Fact] 33 | public void Works_for_inherited_properties_on_base_types() 34 | { 35 | //Arrange 36 | Expression> selection = s => ((DerivedModel)s).Nested.NestedTitle2 == "nested test"; 37 | 38 | //Act 39 | Expression> selectionMapped = mapper.MapExpression>>(selection); 40 | List items = DataObjects.Where(selectionMapped).ToList(); 41 | 42 | //Assert 43 | Assert.True(items.Count == 1); 44 | } 45 | 46 | [Fact] 47 | public void Works_for_top_level_string_member() 48 | { 49 | //Arrange 50 | Expression> selection = s => s.CustomerHolder.Customer.Name == "Jerry Springer"; 51 | 52 | //Act 53 | Expression> selectionMapped = mapper.Map>>(selection); 54 | List items = Orders.Where(selectionMapped).ToList(); 55 | 56 | //Assert 57 | Assert.True(items.Count == 1); 58 | } 59 | 60 | [Fact] 61 | public void Works_for_top_level_value_type() 62 | { 63 | //Arrange 64 | Expression> selection = s => s.CustomerHolder.Customer.Age == 32; 65 | 66 | //Act 67 | Expression> selectionMapped = mapper.Map>>(selection); 68 | List items = Orders.Where(selectionMapped).ToList(); 69 | 70 | //Assert 71 | Assert.True(items.Count == 1); 72 | } 73 | 74 | [Fact] 75 | public void Maps_top_level_string_member_as_include() 76 | { 77 | //Arrange 78 | Expression> selection = s => s.CustomerHolder.Customer.Name; 79 | 80 | //Act 81 | Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); 82 | List orders = Orders.Select(selectionMapped).ToList(); 83 | 84 | //Assert 85 | Assert.True(orders.Count == 2); 86 | } 87 | 88 | [Fact] 89 | public void Maps_top_level_value_type_as_include() 90 | { 91 | //Arrange 92 | Expression> selection = s => s.CustomerHolder.Customer.Total; 93 | 94 | //Act 95 | Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); 96 | List orders = Orders.Select(selectionMapped).ToList(); 97 | 98 | //Assert 99 | Assert.True(orders.Count == 2); 100 | } 101 | 102 | [Fact] 103 | public void Throws_exception_when_mapped_value_type_is_a_child_of_the_parameter() 104 | { 105 | //Arrange 106 | Expression> selection = s => s.CustomerHolder.Customer.Age; 107 | 108 | //Assert 109 | Assert.Throws(() => mapper.MapExpressionAsInclude>>(selection)); 110 | } 111 | 112 | [Fact] 113 | public void Throws_exception_when_mapped_string_is_a_child_of_the_parameter() 114 | { 115 | //Arrange 116 | Expression> selection = s => s.CustomerHolder.Customer.Address; 117 | 118 | //Assert 119 | Assert.Throws(() => mapper.MapExpressionAsInclude>>(selection)); 120 | } 121 | #endregion Tests 122 | 123 | private void SetupQueryableCollection() 124 | { 125 | DataObjects = new DerivedData[] 126 | { 127 | new DerivedData() { OtherID = 2, Title2 = "nested test", ID = 1, Title = "test", DescendantField = "descendant field" }, 128 | new DerivedData() { OtherID = 3, Title2 = "nested", ID = 4, Title = "title", DescendantField = "some text" } 129 | }.AsQueryable(); 130 | 131 | Orders = new OrderDto[] 132 | { 133 | new OrderDto 134 | { 135 | Customer = new CustomerDto{ Name = "George Costanza", Total = 7 }, 136 | CustomerAddress = "333 First Ave", 137 | CustomerAge = 32 138 | }, 139 | new OrderDto 140 | { 141 | Customer = new CustomerDto{ Name = "Jerry Springer", Total = 8 }, 142 | CustomerAddress = "444 First Ave", 143 | CustomerAge = 31 144 | } 145 | }.AsQueryable(); 146 | } 147 | 148 | private static IQueryable Orders { get; set; } 149 | private static IQueryable DataObjects { get; set; } 150 | 151 | private void SetupAutoMapper() 152 | { 153 | var config = new MapperConfiguration(cfg => 154 | { 155 | cfg.AddExpressionMapping(); 156 | cfg.AddMaps(typeof(ForPathCustomerProfile)); 157 | }); 158 | 159 | mapper = config.CreateMapper(); 160 | } 161 | 162 | static IMapper mapper; 163 | } 164 | 165 | public class RootModel 166 | { 167 | public int ID { get; set; } 168 | public string Title { get; set; } 169 | public NestedModel Nested { get; set; } 170 | } 171 | 172 | public class NestedModel 173 | { 174 | public int NestedID { get; set; } 175 | public string NestedTitle { get; set; } 176 | public string NestedTitle2 { get; set; } 177 | } 178 | 179 | public class DerivedModel : RootModel 180 | { 181 | public string DescendantField { get; set; } 182 | } 183 | 184 | public class RootData 185 | { 186 | public int ID { get; set; } 187 | public string Title { get; set; } 188 | 189 | public int OtherID { get; set; } 190 | public string Title2 { get; set; } 191 | } 192 | 193 | public class DerivedData : RootData 194 | { 195 | public string DescendantField { get; set; } 196 | } 197 | 198 | public class Order 199 | { 200 | public CustomerHolder CustomerHolder { get; set; } 201 | public int Value { get; } 202 | } 203 | 204 | public class CustomerHolder 205 | { 206 | public Customer Customer { get; set; } 207 | } 208 | 209 | public class Customer 210 | { 211 | public string Name { get; set; } 212 | public string Address { get; set; } 213 | public decimal? Total { get; set; } 214 | public decimal? Age { get; set; } 215 | } 216 | 217 | public class CustomerDto 218 | { 219 | public string Name { get; set; } 220 | public decimal? Total { get; set; } 221 | } 222 | 223 | public class OrderDto 224 | { 225 | public string CustomerAddress { get; set; } 226 | public decimal? CustomerAge { get; set; } 227 | public CustomerDto Customer { get; set; } 228 | } 229 | 230 | public class ForPathCustomerProfile : Profile 231 | { 232 | public ForPathCustomerProfile() 233 | { 234 | CreateMap() 235 | .Include(); 236 | 237 | CreateMap() 238 | .ForPath(d => d.Nested.NestedTitle, opt => opt.MapFrom(src => src.Title)) 239 | .ForPath(d => d.Nested.NestedTitle2, opt => opt.MapFrom(src => src.Title2)) 240 | .ReverseMap(); 241 | 242 | CreateMap() 243 | .ForPath(o => o.CustomerHolder.Customer.Name, o => o.MapFrom(s => s.Customer.Name)) 244 | .ForPath(o => o.CustomerHolder.Customer.Total, o => o.MapFrom(s => s.Customer.Total)) 245 | .ForPath(o => o.CustomerHolder.Customer.Address, o => o.MapFrom(s => s.CustomerAddress)) 246 | .ForPath(o => o.CustomerHolder.Customer.Age, o => o.MapFrom(s => s.CustomerAge)); 247 | } 248 | } 249 | } 250 | --------------------------------------------------------------------------------