├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .nuget
├── NuGet.Config
├── NuGet.exe
└── NuGet.targets
├── AutoMapper.Collection.sln
├── Directory.Build.props
├── LICENSE
├── Push.ps1
├── README.md
├── build.ps1
├── global.json
├── icon.png
├── src
├── AutoMapper.Collection.EntityFramework.Tests
│ ├── AutoMapper.Collection.EntityFramework.Tests.csproj
│ ├── EntityFramworkTests.cs
│ └── MappingTestBase.cs
├── AutoMapper.Collection.EntityFramework
│ ├── AutoMapper.Collection.EntityFramework.csproj
│ ├── Extensions.cs
│ ├── GenerateEntityFrameworkPrimaryKeyPropertyMaps.cs
│ ├── IPersistence.cs
│ └── Persistence.cs
├── AutoMapper.Collection.LinqToSQL
│ ├── AutoMapper.Collection.LinqToSQL.csproj
│ ├── GetLinqToSQLPrimaryKeyProperties.cs
│ ├── IPersistence.cs
│ ├── Persistence.cs
│ └── PersistenceExtensions.cs
├── AutoMapper.Collection.Tests
│ ├── AutoMapper.Collection.Tests.csproj
│ ├── InheritanceWithCollectionTests.cs
│ ├── MapCollectionWithEqualityTests.cs
│ ├── MapCollectionWithEqualityThreadSafetyTests.cs
│ ├── MappingTestBase.cs
│ ├── NullableIdTests.cs
│ ├── OptionsTests.cs
│ └── ValueTypeTests.cs
├── AutoMapper.Collection
│ ├── AutoMapper.Collection.csproj
│ ├── Configuration
│ │ ├── CollectionMappingExpressionFeature.cs
│ │ └── GeneratePropertyMapsExpressionFeature.cs
│ ├── EquivalencyExpression
│ │ ├── CustomExpressionVisitor.cs
│ │ ├── EquivalentExpression.cs
│ │ ├── EquivalentExpressions.cs
│ │ ├── ExpressionExtentions.cs
│ │ ├── GenerateEquivilentExpressionFromTypeMap.cs
│ │ ├── HashableExpressionsVisitor.cs
│ │ ├── IEquivalentComparer.cs
│ │ └── IGeneratePropertyMaps.cs
│ ├── Mappers
│ │ ├── EquivalentExpressionAddRemoveCollectionMapper.cs
│ │ ├── IConfigurationObjectMapper.cs
│ │ └── ObjectToEquivalencyExpressionByEquivalencyExistingMapper.cs
│ ├── PrimitiveExtensions.cs
│ ├── ReflectionHelper.cs
│ ├── Runtime
│ │ ├── CollectionMappingFeature.cs
│ │ └── GeneratePropertyMapsFeature.cs
│ ├── TypeExtensions.cs
│ └── TypeHelper.cs
└── Key.snk
└── version.props
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | jobs:
11 | build:
12 | strategy:
13 | fail-fast: false
14 | runs-on: windows-2022
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - name: Build and Test
21 | run: ./Build.ps1
22 | shell: pwsh
23 | - name: Push to MyGet
24 | env:
25 | NUGET_URL: https://www.myget.org/F/automapperdev/api/v3/index.json
26 | NUGET_API_KEY: ${{ secrets.MYGET_CI_API_KEY }}
27 | run: ./Push.ps1
28 | shell: pwsh
29 | - name: Artifacts
30 | uses: actions/upload-artifact@v4
31 | with:
32 | name: artifacts
33 | path: artifacts/**/*
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 | jobs:
8 | build:
9 | strategy:
10 | fail-fast: false
11 | runs-on: windows-2022
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 | - name: Build and Test
18 | run: ./Build.ps1
19 | shell: pwsh
20 | - name: Push to MyGet
21 | env:
22 | NUGET_URL: https://www.myget.org/F/automapperdev/api/v3/index.json
23 | NUGET_API_KEY: ${{ secrets.MYGET_CI_API_KEY }}
24 | run: ./Push.ps1
25 | shell: pwsh
26 | - name: Push to NuGet
27 | env:
28 | NUGET_URL: https://api.nuget.org/v3/index.json
29 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
30 | run: ./Push.ps1
31 | shell: pwsh
32 | - name: Artifacts
33 | uses: actions/upload-artifact@v4
34 | with:
35 | name: artifacts
36 | path: artifacts/**/*
37 |
--------------------------------------------------------------------------------
/.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 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | x64/
14 | build/
15 | bld/
16 | [Bb]in/
17 | [Oo]bj/
18 |
19 | # Roslyn cache directories
20 | *.ide/
21 |
22 | # MSTest test Results
23 | [Tt]est[Rr]esult*/
24 | [Bb]uild[Ll]og.*
25 |
26 | #NUNIT
27 | *.VisualState.xml
28 | TestResult.xml
29 |
30 | # Build Results of an ATL Project
31 | [Dd]ebugPS/
32 | [Rr]eleasePS/
33 | dlldata.c
34 |
35 | *_i.c
36 | *_p.c
37 | *_i.h
38 | *.ilk
39 | *.meta
40 | *.obj
41 | *.pch
42 | *.pdb
43 | *.pgc
44 | *.pgd
45 | *.rsp
46 | *.sbr
47 | *.tlb
48 | *.tli
49 | *.tlh
50 | *.tmp
51 | *.tmp_proj
52 | *.log
53 | *.vspscc
54 | *.vssscc
55 | .builds
56 | *.pidb
57 | *.svclog
58 | *.scc
59 |
60 | # Chutzpah Test files
61 | _Chutzpah*
62 |
63 | # Visual C++ cache files
64 | ipch/
65 | *.aps
66 | *.ncb
67 | *.opensdf
68 | *.sdf
69 | *.cachefile
70 |
71 | # Visual Studio profiler
72 | *.psess
73 | *.vsp
74 | *.vspx
75 |
76 | # TFS 2012 Local Workspace
77 | $tf/
78 |
79 | # Guidance Automation Toolkit
80 | *.gpState
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*/
84 | *.[Rr]e[Ss]harper
85 | *.DotSettings.user
86 |
87 | # JustCode is a .NET coding addin-in
88 | .JustCode
89 |
90 | # TeamCity is a build add-in
91 | _TeamCity*
92 |
93 | # DotCover is a Code Coverage Tool
94 | *.dotCover
95 |
96 | # NCrunch
97 | _NCrunch_*
98 | .*crunch*.local.xml
99 |
100 | # MightyMoose
101 | *.mm.*
102 | AutoTest.Net/
103 |
104 | # Web workbench (sass)
105 | .sass-cache/
106 |
107 | # Installshield output folder
108 | [Ee]xpress/
109 |
110 | # DocProject is a documentation generator add-in
111 | DocProject/buildhelp/
112 | DocProject/Help/*.HxT
113 | DocProject/Help/*.HxC
114 | DocProject/Help/*.hhc
115 | DocProject/Help/*.hhk
116 | DocProject/Help/*.hhp
117 | DocProject/Help/Html2
118 | DocProject/Help/html
119 |
120 | # Click-Once directory
121 | publish/
122 |
123 | # Publish Web Output
124 | *.[Pp]ublish.xml
125 | *.azurePubxml
126 | ## TODO: Comment the next line if you want to checkin your
127 | ## web deploy settings but do note that will include unencrypted
128 | ## passwords
129 | #*.pubxml
130 |
131 | # NuGet Packages Directory
132 | packages/*
133 | ## TODO: If the tool you use requires repositories.config
134 | ## uncomment the next line
135 | #!packages/repositories.config
136 |
137 | # Enable "build/" folder in the NuGet Packages folder since
138 | # NuGet packages use it for MSBuild targets.
139 | # This line needs to be after the ignore of the build folder
140 | # (and the packages folder if the line above has been uncommented)
141 | !packages/build/
142 |
143 | # Windows Azure Build Output
144 | csx/
145 | *.build.csdef
146 |
147 | # Windows Store app package directory
148 | AppPackages/
149 |
150 | # Others
151 | sql/
152 | *.Cache
153 | ClientBin/
154 | [Ss]tyle[Cc]op.*
155 | ~$*
156 | *~
157 | *.dbmdl
158 | *.dbproj.schemaview
159 | *.pfx
160 | *.publishsettings
161 | node_modules/
162 |
163 | # RIA/Silverlight projects
164 | Generated_Code/
165 |
166 | # Backup & report files from converting an old project file
167 | # to a newer Visual Studio version. Backup files are not needed,
168 | # because we have git ;-)
169 | _UpgradeReport_Files/
170 | Backup*/
171 | UpgradeLog*.XML
172 | UpgradeLog*.htm
173 |
174 | # SQL Server files
175 | *.mdf
176 | *.ldf
177 |
178 | # Business Intelligence projects
179 | *.rdl.data
180 | *.bim.layout
181 | *.bim_*.settings
182 |
183 | # Microsoft Fakes
184 | FakesAssemblies/
185 |
186 | # LightSwitch generated files
187 | GeneratedArtifacts/
188 | _Pvt_Extensions/
189 | ModelManifest.xml
190 | /packages/
191 | /.vs
192 | *.GhostDoc.xml
193 | /artifacts
194 | *.lock.json
195 | /results
196 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutoMapper/AutoMapper.Collection/eafbf005becf73f989e1b1c0d52e8699439f837c/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | false
8 |
9 |
10 | false
11 |
12 |
13 | true
14 |
15 |
16 | false
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
31 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
32 |
33 |
34 |
35 |
36 | $(SolutionDir).nuget
37 | packages.config
38 |
39 |
40 |
41 |
42 | $(NuGetToolsPath)\NuGet.exe
43 | @(PackageSource)
44 |
45 | "$(NuGetExePath)"
46 | mono --runtime=v4.0.30319 $(NuGetExePath)
47 |
48 | $(TargetDir.Trim('\\'))
49 |
50 | -RequireConsent
51 | -NonInteractive
52 |
53 | "$(SolutionDir) "
54 | "$(SolutionDir)"
55 |
56 |
57 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
58 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
59 |
60 |
61 |
62 | RestorePackages;
63 | $(BuildDependsOn);
64 |
65 |
66 |
67 |
68 | $(BuildDependsOn);
69 | BuildPackage;
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
98 |
100 |
101 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/AutoMapper.Collection.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32210.238
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{578F2483-CF08-409D-A316-31BCB7C5D9D0}"
7 | ProjectSection(SolutionItems) = preProject
8 | Build.ps1 = Build.ps1
9 | .github\workflows\ci.yml = .github\workflows\ci.yml
10 | Directory.Build.props = Directory.Build.props
11 | global.json = global.json
12 | icon.png = icon.png
13 | Push.ps1 = Push.ps1
14 | README.md = README.md
15 | .github\workflows\release.yml = .github\workflows\release.yml
16 | version.props = version.props
17 | EndProjectSection
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection", "src\AutoMapper.Collection\AutoMapper.Collection.csproj", "{37AD667A-8080-476C-88FD-20310AC7CAF3}"
20 | EndProject
21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.EntityFramework", "src\AutoMapper.Collection.EntityFramework\AutoMapper.Collection.EntityFramework.csproj", "{E0FD41FD-AF5B-4BEC-970F-8412E7B6C914}"
22 | EndProject
23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.Tests", "src\AutoMapper.Collection.Tests\AutoMapper.Collection.Tests.csproj", "{2D3D34AD-6A0A-4382-9A2F-894F52D184A7}"
24 | EndProject
25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.EntityFramework.Tests", "src\AutoMapper.Collection.EntityFramework.Tests\AutoMapper.Collection.EntityFramework.Tests.csproj", "{BDE127AB-AC3F-44DF-BC33-210DAFD12E15}"
26 | EndProject
27 | Global
28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
29 | Debug|Any CPU = Debug|Any CPU
30 | Release|Any CPU = Release|Any CPU
31 | EndGlobalSection
32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
33 | {37AD667A-8080-476C-88FD-20310AC7CAF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {37AD667A-8080-476C-88FD-20310AC7CAF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {37AD667A-8080-476C-88FD-20310AC7CAF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {37AD667A-8080-476C-88FD-20310AC7CAF3}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {E0FD41FD-AF5B-4BEC-970F-8412E7B6C914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {E0FD41FD-AF5B-4BEC-970F-8412E7B6C914}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {E0FD41FD-AF5B-4BEC-970F-8412E7B6C914}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {E0FD41FD-AF5B-4BEC-970F-8412E7B6C914}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {2D3D34AD-6A0A-4382-9A2F-894F52D184A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {2D3D34AD-6A0A-4382-9A2F-894F52D184A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {2D3D34AD-6A0A-4382-9A2F-894F52D184A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {2D3D34AD-6A0A-4382-9A2F-894F52D184A7}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {BDE127AB-AC3F-44DF-BC33-210DAFD12E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {BDE127AB-AC3F-44DF-BC33-210DAFD12E15}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {BDE127AB-AC3F-44DF-BC33-210DAFD12E15}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {BDE127AB-AC3F-44DF-BC33-210DAFD12E15}.Release|Any CPU.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(ExtensibilityGlobals) = postSolution
54 | SolutionGuid = {D5C8CD86-1F3F-4D3A-8A90-5F3726E8F998}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 4.15.0
6 | 15.5.0
7 | 2.4.1
8 | 2.2.1
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Tyler Carlson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Push.ps1:
--------------------------------------------------------------------------------
1 | $scriptName = $MyInvocation.MyCommand.Name
2 | $artifacts = "./artifacts"
3 |
4 | if ([string]::IsNullOrEmpty($Env:NUGET_API_KEY)) {
5 | Write-Host "${scriptName}: NUGET_API_KEY is empty or not set. Skipped pushing package(s)."
6 | } else {
7 | Get-ChildItem $artifacts -Filter "*.nupkg" | ForEach-Object {
8 | Write-Host "$($scriptName): Pushing $($_.Name)"
9 | dotnet nuget push $_ --source $Env:NUGET_URL --api-key $Env:NUGET_API_KEY
10 | if ($lastexitcode -ne 0) {
11 | throw ("Exec: " + $errorMessage)
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # AutoMapper.Collection
4 | Adds ability to map collections to existing collections without re-creating the collection object.
5 |
6 | Will Add/Update/Delete items from a preexisting collection object based on user defined equivalency between the collection's generic item type from the source collection and the destination collection.
7 |
8 | [](https://www.nuget.org/packages/AutoMapper.Collection/)
9 |
10 | ## How to add to AutoMapper?
11 | Call AddCollectionMappers when configuring
12 | ```
13 | Mapper.Initialize(cfg =>
14 | {
15 | cfg.AddCollectionMappers();
16 | // Configuration code
17 | });
18 | ```
19 | Will add new IObjectMapper objects into the master mapping list.
20 |
21 | ## Adding equivalency between two classes
22 | Adding equivalence to objects is done with EqualityComparison extended from the IMappingExpression class.
23 | ```
24 | cfg.CreateMap().EqualityComparison((odto, o) => odto.ID == o.ID);
25 | ```
26 | Mapping OrderDTO back to Order will compare Order items list based on if their ID's match
27 | ```
28 | Mapper.Map,List>(orderDtos, orders);
29 | ```
30 | If ID's match, then AutoMapper will map OrderDTO to Order
31 |
32 | If OrderDTO exists and Order doesn't, then AutoMapper will add a new Order mapped from OrderDTO to the collection
33 |
34 | If Order exists and OrderDTO doesn't, then AutoMapper will remove Order from collection
35 |
36 | ## Why update collection? Just recreate it
37 | ORMs don't like setting the collection, so you need to add and remove from preexisting one.
38 |
39 | This automates the process by just specifying what is equal to each other.
40 |
41 | ## Can it just figure out the ID equivalency for me in Entity Framework?
42 | `Automapper.Collection.EntityFramework` or `Automapper.Collection.EntityFrameworkCore` can do that for you.
43 |
44 | ```
45 | Mapper.Initialize(cfg =>
46 | {
47 | cfg.AddCollectionMappers();
48 | // entity framework
49 | cfg.SetGeneratePropertyMaps>();
50 | // entity framework core
51 | cfg.SetGeneratePropertyMaps>();
52 | // Configuration code
53 | });
54 | ```
55 | User defined equality expressions will overwrite primary key expressions.
56 |
57 | ## What about comparing to a single existing Entity for updating?
58 | Automapper.Collection.EntityFramework does that as well through extension method from of DbSet.
59 |
60 | Translate equality between dto and EF object to an expression of just the EF using the dto's values as constants.
61 | ```
62 | dbContext.Orders.Persist().InsertOrUpdate(newOrderDto);
63 | dbContext.Orders.Persist().InsertOrUpdate(existingOrderDto);
64 | dbContext.Orders.Persist().Remove(deletedOrderDto);
65 | dbContext.SubmitChanges();
66 | ```
67 | **Note:** This is done by converting the OrderDTO to Expression> and using that to find matching type in the database. You can also map objects to expressions as well.
68 |
69 | Persist doesn't call submit changes automatically
70 |
71 | ## Where can I get it?
72 |
73 | First, [install NuGet](http://docs.nuget.org/docs/start-here/installing-nuget). Then, install [AutoMapper.Collection](https://www.nuget.org/packages/AutoMapper.Collection/) from the package manager console:
74 | ```
75 | PM> Install-Package AutoMapper.Collection
76 | ```
77 |
78 | ### Additional packages
79 |
80 | #### AutoMapper Collection for Entity Framework
81 | ```
82 | PM> Install-Package AutoMapper.Collection.EntityFramework
83 | ```
84 |
85 | #### AutoMapper Collection for Entity Framework Core
86 | ```
87 | PM> Install-Package AutoMapper.Collection.EntityFrameworkCore
88 | ```
89 |
90 | #### AutoMapper Collection for LinqToSQL
91 | ```
92 | PM> Install-Package AutoMapper.Collection.LinqToSQL
93 | ```
94 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | # Taken from psake https://github.com/psake/psake
2 |
3 | <#
4 | .SYNOPSIS
5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
6 | to see if an error occcured. If an error is detected then an exception is thrown.
7 | This function allows you to run command-line programs without having to
8 | explicitly check the $lastexitcode variable.
9 | .EXAMPLE
10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
11 | #>
12 | function Exec
13 | {
14 | [CmdletBinding()]
15 | param(
16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd)
18 | )
19 | & $cmd
20 | if ($lastexitcode -ne 0) {
21 | throw ("Exec: " + $errorMessage)
22 | }
23 | }
24 |
25 | $artifacts = ".\artifacts"
26 |
27 | if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse }
28 |
29 | exec { & dotnet test -c Release -r $artifacts -l trx --verbosity=normal }
30 |
31 | exec { & dotnet pack .\AutoMapper.Collection.sln -c Release -o $artifacts --no-build }
32 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {"projects":[]}
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AutoMapper/AutoMapper.Collection/eafbf005becf73f989e1b1c0d52e8699439f837c/icon.png
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | AutoMapper.Collection.EntityFramework.Tests
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | %(RecursiveDir)%(FileName)%(Extension)
28 | PreserveNewest
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Entity;
4 | using System.Linq;
5 | using AutoMapper.EntityFramework;
6 | using AutoMapper.EquivalencyExpression;
7 | using FluentAssertions;
8 | using Xunit;
9 |
10 | namespace AutoMapper.Collection.EntityFramework.Tests
11 | {
12 | public class EntityFramworkTests : MappingTestBase
13 | {
14 | private void ConfigureMapper(IMapperConfigurationExpression cfg)
15 | {
16 | cfg.AddCollectionMappers();
17 | cfg.CreateMap().ReverseMap();
18 | cfg.SetGeneratePropertyMaps>();
19 | }
20 |
21 | [Fact]
22 | public void Should_Persist_To_Update()
23 | {
24 | var mapper = CreateMapper(ConfigureMapper);
25 |
26 | var db = new DB();
27 | db.Things.Add(new Thing { Title = "Test2" });
28 | db.Things.Add(new Thing { Title = "Test3" });
29 | db.Things.Add(new Thing { Title = "Test4" });
30 | db.SaveChanges();
31 |
32 | Assert.Equal(3, db.Things.Count());
33 |
34 | var item = db.Things.First();
35 |
36 | db.Things.Persist(mapper).InsertOrUpdate(new ThingDto { ID = item.ID, Title = "Test" });
37 | Assert.Equal(1, db.ChangeTracker.Entries().Count(x => x.State == EntityState.Modified));
38 |
39 | Assert.Equal(3, db.Things.Count());
40 |
41 | db.Things.First(x => x.ID == item.ID).Title.Should().Be("Test");
42 | }
43 |
44 | [Fact]
45 | public void Should_Persist_To_Insert()
46 | {
47 | var mapper = CreateMapper(ConfigureMapper);
48 |
49 | var db = new DB();
50 | db.Things.Add(new Thing { Title = "Test2" });
51 | db.Things.Add(new Thing { Title = "Test3" });
52 | db.Things.Add(new Thing { Title = "Test4" });
53 | db.SaveChanges();
54 |
55 | Assert.Equal(3, db.Things.Count());
56 |
57 | db.Things.Persist(mapper).InsertOrUpdate(new ThingDto { Title = "Test" });
58 | Assert.Equal(3, db.Things.Count());
59 | Assert.Equal(1, db.ChangeTracker.Entries().Count(x => x.State == EntityState.Added));
60 |
61 | db.SaveChanges();
62 |
63 | Assert.Equal(4, db.Things.Count());
64 |
65 | db.Things.OrderByDescending(x => x.ID).First().Title.Should().Be("Test");
66 | }
67 |
68 | public class DB : DbContext
69 | {
70 | public DB()
71 | : base(Effort.DbConnectionFactory.CreateTransient(), contextOwnsConnection: true)
72 | {
73 | Things.RemoveRange(Things);
74 | SaveChanges();
75 | }
76 |
77 | public DbSet Things { get; set; }
78 | }
79 |
80 | public class Thing
81 | {
82 | public int ID { get; set; }
83 | public string Title { get; set; }
84 | public override string ToString() { return Title; }
85 | }
86 |
87 | public class ThingDto
88 | {
89 | public int ID { get; set; }
90 | public string Title { get; set; }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework.Tests/MappingTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AutoMapper.Collection
4 | {
5 | public abstract class MappingTestBase
6 | {
7 | protected IMapper CreateMapper(Action cfg)
8 | {
9 | var map = new MapperConfiguration(cfg);
10 | map.CompileMappings();
11 |
12 | var mapper = map.CreateMapper();
13 | mapper.ConfigurationProvider.AssertConfigurationIsValid();
14 | return mapper;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Collection updating support for EntityFramework with AutoMapper. Extends DBSet<T> with Persist<TDto>().InsertUpdate(dto) and Persist<TDto>().Delete(dto). Will find the matching object and will Insert/Update/Delete.
5 | Tyler Carlson
6 | net8.0
7 | AutoMapper.Collection.EntityFramework
8 | AutoMapper.Collection.EntityFramework
9 | icon.png
10 | https://github.com/AutoMapper/Automapper.Collection
11 | ../Key.snk
12 | true
13 | MIT
14 | true
15 | true
16 | v
17 | snupkg
18 | true
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | all
35 | runtime; build; native; contentfiles; analyzers; buildtransitive
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Data.Entity;
5 | using AutoMapper.Extensions.ExpressionMapping.Impl;
6 |
7 | namespace AutoMapper.EntityFramework
8 | {
9 | public static class Extensions
10 | {
11 | ///
12 | /// Obsolete: Use Persist(IMapper) instead.
13 | /// Create a Persistence object for the to have data persisted or removed from
14 | /// Uses static API's Mapper for finding TypeMap between classes
15 | ///
16 | /// Source table type to be updated
17 | /// DbSet to be updated
18 | /// Persistence object to Update or Remove data
19 | [Obsolete("Use Persist(IMapper) instead.", true)]
20 | public static IPersistence Persist(this DbSet source)
21 | where TSource : class
22 | {
23 | throw new NotSupportedException();
24 | }
25 |
26 | ///
27 | /// Create a Persistence object for the to have data persisted or removed from
28 | ///
29 | /// Source table type to be updated
30 | /// DbSet to be updated
31 | /// IMapper used to find TypeMap between classes
32 | /// Persistence object to Update or Remove data
33 | public static IPersistence Persist(this DbSet source, IMapper mapper)
34 | where TSource : class
35 | {
36 | return new Persistence(source, mapper);
37 | }
38 |
39 | ///
40 | /// Non Generic call for For
41 | ///
42 | ///
43 | ///
44 | ///
45 | ///
46 | public static IEnumerable For(this IQueryDataSourceInjection source, Type destType)
47 | {
48 | var forMethod = source.GetType().GetMethod("For").MakeGenericMethod(destType);
49 | var listType = typeof(List<>).MakeGenericType(destType);
50 | var forResult = forMethod.Invoke(source, new object[] { null });
51 | var enumeratedResult = Activator.CreateInstance(listType, forResult);
52 | return enumeratedResult as IEnumerable;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework/GenerateEntityFrameworkPrimaryKeyPropertyMaps.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Entity.Core.Metadata.Edm;
4 | using System.Data.Entity.Core.Objects;
5 | using System.Data.Entity.Infrastructure;
6 | using System.Linq;
7 | using System.Reflection;
8 | using AutoMapper.EquivalencyExpression;
9 |
10 | namespace AutoMapper.EntityFramework
11 | {
12 | public class GenerateEntityFrameworkPrimaryKeyPropertyMaps : IGeneratePropertyMaps
13 | where TDatabaseContext : IObjectContextAdapter, new()
14 | {
15 | private readonly TDatabaseContext _context = new TDatabaseContext();
16 | private readonly MethodInfo _createObjectSetMethodInfo = typeof(ObjectContext).GetMethod("CreateObjectSet", Type.EmptyTypes);
17 |
18 | public IEnumerable GeneratePropertyMaps(TypeMap typeMap)
19 | {
20 | var propertyMaps = typeMap.PropertyMaps;
21 | try
22 | {
23 | var createObjectSetMethod = _createObjectSetMethodInfo.MakeGenericMethod(typeMap.DestinationType);
24 | dynamic objectSet = createObjectSetMethod.Invoke(_context.ObjectContext, null);
25 |
26 | IEnumerable keyMembers = objectSet.EntitySet.ElementType.KeyMembers;
27 | var primaryKeyPropertyMatches = keyMembers.Select(m => propertyMaps.FirstOrDefault(p => p.DestinationMember.Name == m.Name));
28 |
29 | return primaryKeyPropertyMatches;
30 | }
31 | catch (Exception)
32 | {
33 | return Enumerable.Empty();
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework/IPersistence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AutoMapper.EntityFramework
4 | {
5 | public interface IPersistence
6 | {
7 | ///
8 | /// Insert Or Update the with
9 | ///
10 | /// Uses > to find equality between Source and From Types to determine if insert or update
11 | /// Source Type mapping from
12 | /// Object to update to
13 | void InsertOrUpdate(TFrom from) where TFrom : class;
14 | ///
15 | /// Insert Or Update the with
16 | ///
17 | /// Uses > to find equality between Source and From Types to determine if insert or update
18 | /// Source Type mapping from
19 | /// Object to update to
20 | void InsertOrUpdate(Type type, object from);
21 | ///
22 | /// Remove from with
23 | ///
24 | /// Uses > to find equality between Source and From Types to determine if insert or update
25 | /// Source Type mapping from
26 | /// Object to remove that is Equivalent in
27 | void Remove(TFrom from) where TFrom : class;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.EntityFramework/Persistence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 |
6 | namespace AutoMapper.EntityFramework
7 | {
8 | public class Persistence : IPersistence
9 | where TTo : class
10 | {
11 | private readonly DbSet _sourceSet;
12 | private readonly IMapper _mapper;
13 |
14 | public Persistence(DbSet sourceSet, IMapper mapper)
15 | {
16 | _sourceSet = sourceSet;
17 | _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
18 | }
19 |
20 | public void InsertOrUpdate(TFrom from)
21 | where TFrom : class
22 | {
23 | InsertOrUpdate(typeof(TFrom), from);
24 | }
25 |
26 | public void InsertOrUpdate(Type type, object from)
27 | {
28 | if (!(_mapper.Map(from, type, typeof(Expression>)) is Expression> equivExpr))
29 | return;
30 |
31 | var to = _sourceSet.FirstOrDefault(equivExpr);
32 |
33 | if (to == null)
34 | {
35 | to = _sourceSet.Create();
36 | _sourceSet.Add(to);
37 | }
38 | _mapper.Map(from, to);
39 | }
40 |
41 | public void Remove(TFrom from)
42 | where TFrom : class
43 | {
44 | var equivExpr = _mapper.Map>>(from);
45 | if (equivExpr == null)
46 | return;
47 | var to = _sourceSet.FirstOrDefault(equivExpr);
48 |
49 | if (to != null)
50 | _sourceSet.Remove(to);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.LinqToSQL/AutoMapper.Collection.LinqToSQL.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Collection updating support for LinqToSQL with AutoMapper. Extends Table<T> with Persist<TDto>().InsertUpdate(dto) and Persist<TDto>().Delete(dto). Will find the matching object and will Insert/Update/Delete.
5 | Tyler Carlson
6 | net461
7 | AutoMapper.Collection.LinqToSQL
8 | AutoMapper.Collection.LinqToSQL
9 | icon.png
10 | https://github.com/AutoMapper/Automapper.Collection
11 | ../Key.snk
12 | true
13 | MIT
14 | true
15 | true
16 | v
17 | snupkg
18 | true
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | all
34 | runtime; build; native; contentfiles; analyzers; buildtransitive
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.LinqToSQL/GetLinqToSQLPrimaryKeyProperties.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Data.Linq.Mapping;
3 | using System.Linq;
4 | using System.Reflection;
5 | using AutoMapper.EquivalencyExpression;
6 |
7 | namespace AutoMapper.Collection.LinqToSQL
8 | {
9 | public class GetLinqToSQLPrimaryKeyProperties : IGeneratePropertyMaps
10 | {
11 | public IEnumerable GeneratePropertyMaps(TypeMap typeMap)
12 | {
13 | var propertyMaps = typeMap.PropertyMaps;
14 |
15 | var primaryKeyPropertyMatches = typeMap.DestinationType.GetProperties()
16 | .Where(IsPrimaryKey)
17 | .Select(m => propertyMaps.FirstOrDefault(p => p.DestinationMember.Name == m.Name))
18 | .ToList();
19 |
20 | return primaryKeyPropertyMatches;
21 | }
22 |
23 | private static bool IsPrimaryKey(PropertyInfo propertyInfo)
24 | {
25 | return propertyInfo.GetCustomAttributes(typeof(ColumnAttribute), false)
26 | .OfType().Any(ca => ca.IsPrimaryKey);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.LinqToSQL/IPersistence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AutoMapper.Collection.LinqToSQL
4 | {
5 | public interface IPersistence
6 | {
7 | ///
8 | /// Insert Or Update the with
9 | ///
10 | /// Uses > to find equality between Source and From Types to determine if insert or update
11 | /// Source Type mapping from
12 | /// Object to update to
13 | void InsertOrUpdate(TFrom from) where TFrom : class;
14 |
15 | void InsertOrUpdate(Type type, object from);
16 |
17 | ///
18 | /// Remove from with
19 | ///
20 | /// Uses > to find equality between Source and From Types to determine if insert or update
21 | /// Source Type mapping from
22 | /// Object to remove that is Equivalent in
23 | void Remove(TFrom from) where TFrom : class;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.LinqToSQL/Persistence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Linq;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 |
6 | namespace AutoMapper.Collection.LinqToSQL
7 | {
8 | public class Persistence : IPersistence
9 | where TTo : class
10 | {
11 | private readonly Table _sourceSet;
12 | private readonly IMapper _mapper;
13 |
14 | public Persistence(Table sourceSet, IMapper mapper)
15 | {
16 | _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
17 | _sourceSet = sourceSet;
18 | }
19 |
20 | public void InsertOrUpdate(TFrom from)
21 | where TFrom : class
22 | {
23 | InsertOrUpdate(typeof(TFrom), from);
24 | }
25 |
26 | public void InsertOrUpdate(Type type, object from)
27 | {
28 | if (!(_mapper.Map(from, type, typeof(Expression>)) is Expression> equivExpr))
29 | return;
30 |
31 | var to = _sourceSet.FirstOrDefault(equivExpr);
32 |
33 | if (to == null)
34 | {
35 | to = Activator.CreateInstance();
36 | _sourceSet.InsertOnSubmit(to);
37 | }
38 | _mapper.Map(from, to);
39 | }
40 |
41 | public void Remove(TFrom from)
42 | where TFrom : class
43 | {
44 | var equivExpr = _mapper.Map>>(from);
45 | if (equivExpr == null)
46 | return;
47 | var to = _sourceSet.FirstOrDefault(equivExpr);
48 |
49 | if (to != null)
50 | _sourceSet.DeleteOnSubmit(to);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.LinqToSQL/PersistenceExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Data.Linq;
5 | using AutoMapper.Extensions.ExpressionMapping.Impl;
6 |
7 | namespace AutoMapper.Collection.LinqToSQL
8 | {
9 | public static class PersistenceExtensions
10 | {
11 | ///
12 | /// Obsolete: Use Persist(IMapper) instead.
13 | /// Create a Persistence object for the to have data persisted or removed from
14 | /// Uses static API's Mapper for finding TypeMap between classes
15 | ///
16 | /// Source table type to be updated
17 | /// Table to be updated
18 | /// Persistence object to Update or Remove data
19 | [Obsolete("Use Persist(IMapper) instead.", true)]
20 | public static IPersistence Persist(this Table source)
21 | where TSource : class
22 | {
23 | throw new NotSupportedException();
24 | }
25 |
26 | ///
27 | /// Create a Persistence object for the to have data persisted or removed from
28 | ///
29 | /// Source table type to be updated
30 | /// Table to be updated
31 | /// IMapper used to find TypeMap between classes
32 | /// Persistence object to Update or Remove data
33 | public static IPersistence Persist(this Table source, IMapper mapper)
34 | where TSource : class
35 | {
36 | return new Persistence(source, mapper);
37 | }
38 |
39 | public static IEnumerable For(this IQueryDataSourceInjection source, Type destType)
40 | {
41 | var forMethod = source.GetType().GetMethod("For").MakeGenericMethod(destType);
42 | var listType = typeof(List<>).MakeGenericType(destType);
43 | var forResult = forMethod.Invoke(source, new object[] { null });
44 | var enumeratedResult = Activator.CreateInstance(listType, forResult);
45 | return enumeratedResult as IEnumerable;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/AutoMapper.Collection.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | AutoMapper.Collection.Tests
6 | AutoMapper.Collection
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper.EquivalencyExpression;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using AutoMapper.Internal;
5 | using FluentAssertions;
6 | using Xunit;
7 |
8 | namespace AutoMapper.Collection
9 | {
10 | public abstract class InheritanceWithCollectionTests : MappingTestBase
11 | {
12 | protected abstract void ConfigureMapper(IMapperConfigurationExpression cfg);
13 |
14 | [Fact]
15 | public void TypeMap_Should_include_base_types()
16 | {
17 | var mapper = CreateMapper(ConfigureMapper);
18 | var typeMap = mapper.ConfigurationProvider.Internal().ResolveTypeMap(typeof(MailOrderDomain), typeof(OrderEf));
19 |
20 | var typePairs = new[]{
21 | new TypePair(typeof(OrderDomain), typeof(OrderEf))
22 | };
23 | typeMap.IncludedBaseTypes.ShouldBeEquivalentTo(typePairs);
24 | }
25 |
26 | [Fact]
27 | public void TypeMap_Should_include_derivied_types()
28 | {
29 | var mapper = CreateMapper(ConfigureMapper);
30 | var typeMap = mapper.ConfigurationProvider.Internal().ResolveTypeMap(typeof(OrderDomain), typeof(OrderEf));
31 |
32 | var typePairs = new[]{
33 | new TypePair(typeof(OnlineOrderDomain), typeof(OnlineOrderEf)),
34 | new TypePair(typeof(MailOrderDomain), typeof(MailOrderEf))
35 | };
36 | typeMap.IncludedDerivedTypes.ShouldBeEquivalentTo(typePairs);
37 | }
38 |
39 | [Fact]
40 | public void Map_Should_ReturnOnlineOrderEf_When_ListIsOfTypeOrderEf()
41 | {
42 | var mapper = CreateMapper(ConfigureMapper);
43 |
44 | //arrange
45 | var orderDomain = new OnlineOrderDomain { Id = "Id", Key = "Key" };
46 | var rootDomain = new RootDomain { OnlineOrders = { orderDomain } };
47 |
48 | //act
49 | RootEf mappedRootEf = mapper.Map(rootDomain);
50 |
51 | //assert
52 | OrderEf orderEf = mappedRootEf.Orders[0];
53 |
54 | orderEf.Should().BeOfType();
55 | orderEf.Id.ShouldBeEquivalentTo(orderDomain.Id);
56 |
57 | var onlineOrderEf = (OnlineOrderEf)orderEf;
58 | onlineOrderEf.Key.ShouldBeEquivalentTo(orderDomain.Key);
59 |
60 | // ------------------------------------------------------------- //
61 |
62 | //arrange again
63 | mappedRootEf.Orders.Add(new OnlineOrderEf { Id = "NewId" });
64 |
65 | mapper.Map(mappedRootEf, rootDomain);
66 |
67 | //assert again
68 | rootDomain.OnlineOrders.Count.ShouldBeEquivalentTo(2);
69 | rootDomain.OnlineOrders.Last().Should().BeOfType();
70 |
71 | //Assert.AreSame(rootDomain.OnlineOrders.First(), orderDomain); that doesn't matter when we map from EF to Domain
72 | }
73 |
74 | [Fact]
75 | public void Map_FromEfToDomain_And_AddAnOnlineOrderInTheDomainObject_And_ThenMapBackToEf_Should_UseTheSameReferenceInTheEfCollection()
76 | {
77 | var mapper = CreateMapper(ConfigureMapper);
78 |
79 | //arrange
80 | var onlineOrderEf = new OnlineOrderEf { Id = "Id", Key = "Key" };
81 | var mailOrderEf = new MailOrderEf { Id = "MailOrderId" };
82 | var rootEf = new RootEf { Orders = { onlineOrderEf, mailOrderEf } };
83 |
84 | //act
85 | RootDomain mappedRootDomain = mapper.Map(rootEf);
86 |
87 | //assert
88 | OnlineOrderDomain onlineOrderDomain = mappedRootDomain.OnlineOrders[0];
89 |
90 | onlineOrderDomain.Should().BeOfType();
91 | onlineOrderEf.Id.ShouldBeEquivalentTo(onlineOrderEf.Id);
92 |
93 | // IMPORTANT ASSERT ------------------------------------------------------------- IMPORTANT ASSERT //
94 |
95 | //arrange again
96 | mappedRootDomain.OnlineOrders.Add(new OnlineOrderDomain { Id = "NewOnlineOrderId", Key = "NewKey" });
97 | mappedRootDomain.MailOrders.Add(new MailOrderDomain { Id = "NewMailOrderId", });
98 | onlineOrderDomain.Id = "Hi";
99 |
100 | //act again
101 | mapper.Map(mappedRootDomain, rootEf);
102 |
103 | //assert again
104 | OrderEf existingMailOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == mailOrderEf.Id);
105 | OrderEf existingOnlineOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == onlineOrderEf.Id);
106 |
107 | OrderEf newOnlineOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == "NewOnlineOrderId");
108 | OrderEf newMailOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == "NewMailOrderId");
109 |
110 | rootEf.Orders.Count.ShouldBeEquivalentTo(4);
111 | onlineOrderEf.Should().BeSameAs(existingOnlineOrderEf);
112 | mailOrderEf.Should().BeSameAs(existingMailOrderEf);
113 |
114 | newOnlineOrderEf.Should().BeOfType();
115 | newMailOrderEf.Should().BeOfType();
116 | }
117 |
118 | private static bool BaseEquals(OrderDomain oo, OrderEf dto)
119 | {
120 | return oo.Id == dto.Id;
121 | }
122 |
123 | private static bool DerivedEquals(OnlineOrderDomain ood, OnlineOrderEf ooe)
124 | {
125 | return ood.Key == ooe.Key;
126 | }
127 |
128 | public class MailOrderDomain : OrderDomain
129 | {
130 | }
131 |
132 | public class MailOrderEf : OrderEf
133 | {
134 | }
135 |
136 | public class OnlineOrderDomain : OrderDomain
137 | {
138 | public string Key { get; set; }
139 | }
140 |
141 | public class OnlineOrderEf : OrderEf
142 | {
143 | public string Key { get; set; }
144 | }
145 |
146 | public abstract class OrderDomain
147 | {
148 | public string Id { get; set; }
149 | }
150 |
151 | public abstract class OrderEf
152 | {
153 | public string Id { get; set; }
154 | }
155 |
156 | public class RootDomain
157 | {
158 | public List OnlineOrders { get; set; } = new List();
159 | public List MailOrders { get; set; } = new List();
160 | }
161 |
162 | public class RootEf
163 | {
164 | public List Orders { get; set; } = new List();
165 | }
166 |
167 | public class MergeDomainOrdersToEfOrdersValueResolver : IValueResolver>
168 | {
169 | public List Resolve(RootDomain source, RootEf destination, List destMember, ResolutionContext context)
170 | {
171 | var mappedOnlineOrders = new List(destination.Orders);
172 | var mappedMailOrders = new List(destination.Orders);
173 |
174 | context.Mapper.Map(source.OnlineOrders, mappedOnlineOrders);
175 | context.Mapper.Map(source.MailOrders, mappedMailOrders);
176 |
177 | var efOrders = mappedOnlineOrders.Union(mappedMailOrders).ToList();
178 |
179 | return efOrders;
180 | }
181 | }
182 |
183 | public class Include : InheritanceWithCollectionTests
184 | {
185 | protected override void ConfigureMapper(IMapperConfigurationExpression cfg)
186 | {
187 | cfg.ShouldMapProperty = propertyInfo => propertyInfo.GetMethod.IsPublic || propertyInfo.GetMethod.IsAssembly || propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsPrivate;
188 | cfg.AddCollectionMappers();
189 |
190 | //DOMAIN --> EF
191 | cfg.CreateMap()
192 | .ForMember(rootEf => rootEf.Orders, opt => opt.MapFrom())
193 | ;
194 |
195 | //collection type
196 | cfg.CreateMap()
197 | .EqualityComparison((oo, dto) => BaseEquals(oo, dto))
198 | .Include()
199 | .Include()
200 | ;
201 |
202 | cfg.CreateMap()
203 | .EqualityComparison((ood, ooe) => DerivedEquals(ood, ooe))
204 | ;
205 |
206 | cfg.CreateMap()
207 | ;
208 |
209 | //EF --> DOMAIN
210 | cfg.CreateMap()
211 | .ForMember(rootDomain => rootDomain.OnlineOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(OnlineOrderEf))))
212 | .ForMember(rootDomain => rootDomain.MailOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(MailOrderEf))))
213 | ;
214 |
215 | cfg.CreateMap()
216 | .Include()
217 | .Include()
218 | ;
219 |
220 | cfg.CreateMap()
221 | ;
222 |
223 | cfg.CreateMap()
224 | ;
225 | }
226 | }
227 |
228 | public class IncludeBase : InheritanceWithCollectionTests
229 | {
230 | protected override void ConfigureMapper(IMapperConfigurationExpression cfg)
231 | {
232 | cfg.ShouldMapProperty = propertyInfo => propertyInfo.GetMethod.IsPublic || propertyInfo.GetMethod.IsAssembly || propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsPrivate;
233 | cfg.AddCollectionMappers();
234 |
235 | //DOMAIN --> EF
236 | cfg.CreateMap()
237 | .ForMember(rootEf => rootEf.Orders, opt => opt.MapFrom())
238 | ;
239 |
240 | //collection type
241 | cfg.CreateMap()
242 | .EqualityComparison((oo, dto) => BaseEquals(oo, dto))
243 | ;
244 |
245 | cfg.CreateMap()
246 | .EqualityComparison((ood, ooe) => DerivedEquals(ood, ooe))
247 | .IncludeBase()
248 | ;
249 |
250 | cfg.CreateMap()
251 | .IncludeBase()
252 | ;
253 |
254 | //EF --> DOMAIN
255 | cfg.CreateMap()
256 | .ForMember(rootDomain => rootDomain.OnlineOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(OnlineOrderEf))))
257 | .ForMember(rootDomain => rootDomain.MailOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(MailOrderEf))))
258 | ;
259 |
260 | cfg.CreateMap()
261 | ;
262 |
263 | cfg.CreateMap()
264 | .IncludeBase()
265 | ;
266 |
267 | cfg.CreateMap()
268 | .IncludeBase()
269 | ;
270 | }
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using AutoMapper.EquivalencyExpression;
5 | using FluentAssertions;
6 | using Xunit;
7 |
8 | namespace AutoMapper.Collection
9 | {
10 | public class MapCollectionWithEqualityTests : MappingTestBase
11 | {
12 | protected virtual void ConfigureMapper(IMapperConfigurationExpression cfg)
13 | {
14 | cfg.AddCollectionMappers();
15 | cfg.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID);
16 | }
17 |
18 | [Fact]
19 | public void Should_Keep_Existing_List()
20 | {
21 | var mapper = CreateMapper(ConfigureMapper);
22 | var dtos = new List
23 | {
24 | new ThingDto { ID = 1, Title = "test0" },
25 | new ThingDto { ID = 2, Title = "test2" }
26 | };
27 |
28 | var items = new List
29 | {
30 | new Thing { ID = 1, Title = "test1" },
31 | new Thing { ID = 3, Title = "test3" },
32 | };
33 |
34 | mapper.Map(dtos, items).Should().BeSameAs(items);
35 | }
36 |
37 | [Fact]
38 | public void Should_Update_Existing_Item()
39 | {
40 | var mapper = CreateMapper(ConfigureMapper);
41 |
42 | var dtos = new List
43 | {
44 | new ThingDto { ID = 1, Title = "test0" },
45 | new ThingDto { ID = 2, Title = "test2" }
46 | };
47 |
48 | var items = new List
49 | {
50 | new Thing { ID = 1, Title = "test1" },
51 | new Thing { ID = 3, Title = "test3" },
52 | };
53 |
54 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
55 | }
56 |
57 | [Fact]
58 | public void Should_Be_Fast_With_Large_Lists()
59 | {
60 | var mapper = CreateMapper(ConfigureMapper);
61 |
62 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
63 |
64 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
65 |
66 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
67 | }
68 |
69 | [Fact]
70 | public void Should_Be_Fast_With_Large_Reversed_Lists()
71 | {
72 | var mapper = CreateMapper(ConfigureMapper);
73 |
74 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
75 | dtos.Reverse();
76 |
77 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
78 |
79 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
80 | }
81 |
82 | [Fact]
83 | public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping()
84 | {
85 | var mapper = CreateMapper(x =>
86 | {
87 | x.AddCollectionMappers();
88 | x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID && dto.ID == entity.ID);
89 | });
90 |
91 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
92 |
93 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
94 |
95 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
96 | }
97 |
98 | [Fact]
99 | public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping_Cant_Extract()
100 | {
101 | var mapper = CreateMapper(x =>
102 | {
103 | x.AddCollectionMappers();
104 | x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID || dto.ID == entity.ID);
105 | });
106 |
107 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
108 |
109 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
110 |
111 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
112 | }
113 |
114 | [Fact]
115 | public void Should_Be_Fast_With_Large_Lists_Cant_Extract_Negative()
116 | {
117 | var mapper = CreateMapper(x =>
118 | {
119 | x.AddCollectionMappers();
120 | // ReSharper disable once NegativeEqualityExpression
121 | x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => !(dto.ID != entity.ID));
122 | });
123 |
124 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
125 |
126 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
127 |
128 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
129 | }
130 |
131 | [Fact]
132 | public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping_Cant_Extract_Negative()
133 | {
134 | var mapper = CreateMapper(x =>
135 | {
136 | x.AddCollectionMappers();
137 | // ReSharper disable once NegativeEqualityExpression
138 | x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID && !(dto.ID != entity.ID));
139 | });
140 |
141 | var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList();
142 |
143 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
144 |
145 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
146 | }
147 |
148 | [Fact]
149 | public void Should_Be_Fast_With_Large_Lists_SubObject()
150 | {
151 | var mapper = CreateMapper(x =>
152 | {
153 | x.AddCollectionMappers();
154 | x.CreateMap().EqualityComparison((ThingDto source, Thing dest) => dest.ID == (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID));
155 | });
156 |
157 | var dtos = new object[100000].Select((_, i) => new ThingSubDto { ID = i + 100000 }).Cast().ToList();
158 |
159 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
160 |
161 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
162 | }
163 |
164 | [Fact]
165 | public void Should_Be_Fast_With_Large_Lists_SubObject_switch_left_and_right_expression()
166 | {
167 | var mapper = CreateMapper(x =>
168 | {
169 | x.AddCollectionMappers();
170 | x.CreateMap().EqualityComparison((ThingDto source, Thing dest) => (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID) == dest.ID);
171 | });
172 |
173 | var dtos = new object[100000].Select((_, i) => new ThingSubDto { ID = i + 100000 }).ToList();
174 |
175 | var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();
176 |
177 | mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
178 | }
179 |
180 | [Fact]
181 | public void Should_Work_With_Conditionals()
182 | {
183 | var mapper = CreateMapper(cfg =>
184 | {
185 | cfg.AddCollectionMappers();
186 | cfg.CreateMap()
187 | .ForMember(x => x.DtoId, m => m.Ignore())
188 | .EqualityComparison((ClientDto src, Client dest) => dest.DtoId == 0 ? src.Code == dest.Code : src.Id == dest.DtoId);
189 | });
190 |
191 | var dto = new ClientDto
192 | {
193 | Code = "abc",
194 | Id = 1
195 | };
196 | var entity = new Client {Code = dto.Code, Id = 42};
197 | var entityCollection = new List {entity};
198 |
199 | mapper.Map(new[] { dto }, entityCollection);
200 |
201 | entity.ShouldBeEquivalentTo(entityCollection[0]);
202 | }
203 |
204 | public class Client
205 | {
206 | public long Id { get; set; }
207 | public string Code { get; set; }
208 | public long DtoId { get; set; }
209 | }
210 |
211 | public class ClientDto
212 | {
213 | public long Id { get; set; }
214 | public string Code { get; set; }
215 | }
216 |
217 |
218 | [Fact]
219 | public void Should_Work_With_Null_Destination()
220 | {
221 | var mapper = CreateMapper(ConfigureMapper);
222 |
223 | var dtos = new List
224 | {
225 | new ThingDto { ID = 1, Title = "test0" },
226 | new ThingDto { ID = 2, Title = "test2" }
227 | };
228 |
229 | mapper.Map>(dtos).Should().HaveSameCount(dtos);
230 | }
231 |
232 | [Fact]
233 | public void Should_Work_With_Comparing_String_Types()
234 | {
235 | var mapper = CreateMapper(cfg =>
236 | {
237 | cfg.AddCollectionMappers();
238 | cfg.CreateMap()
239 | .ForMember(d => d.SaleId, (IMemberConfigurationExpression o) => o.Ignore())
240 | .EqualityComparison((Charge c, SaleCharge sc) => sc.Category == c.Category && sc.Description == c.Description);
241 |
242 | cfg.CreateMap()
243 | .ConstructUsing(
244 | (saleCharge => new Charge(saleCharge.Category, saleCharge.Description, saleCharge.Value)))
245 | .EqualityComparison((SaleCharge sc, Charge c) => sc.Category == c.Category && sc.Description == c.Description);
246 | });
247 |
248 | var dto = new Charge("catagory", "description", 5);
249 | var entity = new SaleCharge { Category = dto.Category, Description = dto.Description };
250 | var entityCollection = new List { entity };
251 |
252 | mapper.Map(new[] { dto }, entityCollection);
253 |
254 | entity.ShouldBeEquivalentTo(entityCollection[0]);
255 | }
256 |
257 | public class Charge
258 | {
259 | public Charge(string category, string description, decimal value)
260 | {
261 | Category = category;
262 | Description = description;
263 | Value = value;
264 | }
265 |
266 | public string Category { get; }
267 | public string Description { get; }
268 | public decimal Value { get; }
269 |
270 | public override string ToString()
271 | {
272 | return $"{Category}|{Description}|{Value}";
273 | }
274 |
275 | public override int GetHashCode()
276 | {
277 | return $"{Category}|{Description}|{Value}".GetHashCode();
278 | }
279 |
280 | public override bool Equals(object obj)
281 | {
282 | if (ReferenceEquals(this, obj))
283 | {
284 | return true;
285 | }
286 |
287 | if (ReferenceEquals(null, obj))
288 | {
289 | return false;
290 | }
291 |
292 | var _obj = obj as Charge;
293 |
294 | if (_obj == null)
295 | {
296 | return false;
297 | }
298 |
299 | return Category == _obj.Category && Description == _obj.Description && Value == _obj.Value;
300 | }
301 | }
302 |
303 | public class SaleCharge
304 | {
305 | public Guid SaleId { get; set; }
306 | public string Category { get; set; }
307 | public string Description { get; set; }
308 | public decimal Value { get; set; }
309 | }
310 |
311 | [Fact]
312 | public void Should_Be_Instanced_Based()
313 | {
314 | var mapper = CreateMapper(x =>
315 | {
316 | x.AddCollectionMappers();
317 | x.CreateMap().ReverseMap();
318 | });
319 |
320 | var dtos = new List
321 | {
322 | new ThingDto { ID = 1, Title = "test0" },
323 | new ThingDto { ID = 2, Title = "test2" }
324 | };
325 |
326 | var items = new List
327 | {
328 | new Thing { ID = 1, Title = "test1" },
329 | new Thing { ID = 3, Title = "test3" },
330 | };
331 |
332 | mapper.Map(dtos, items.ToList()).Should().NotContain(items.First());
333 | }
334 |
335 | [Fact]
336 | public void Parent_Should_Be_Same_As_Root_Object()
337 | {
338 | var mapper = CreateMapper(
339 | cfg =>
340 | {
341 | cfg.AddCollectionMappers();
342 | cfg.CreateMap().PreserveReferences();
343 | cfg.CreateMap()
344 | .EqualityComparison((src, dst) => src.ID == dst.ID).PreserveReferences();
345 | });
346 |
347 | var root = new ThingWithCollection()
348 | {
349 | Children = new List()
350 | };
351 | root.Children.Add(new ThingCollectionItem() { ID = 1, Parent = root });
352 |
353 | var target = new ThingWithCollection() { Children = new List() };
354 | mapper.Map(root, target).Should().Be(target);
355 |
356 | target.Children.Count.Should().Be(1);
357 | target.Children.Single().Parent.Should().Be(target);
358 | }
359 |
360 | public class Thing
361 | {
362 | public int ID { get; set; }
363 | public string Title { get; set; }
364 | public override string ToString() { return Title; }
365 | }
366 |
367 | public class ThingSubDto : ThingDto
368 | {
369 | public int ID2 => ID - 100000;
370 | }
371 |
372 | public class ThingDto
373 | {
374 | public int ID { get; set; }
375 | public string Title { get; set; }
376 | }
377 |
378 | public class ThingWithCollection
379 | {
380 | public ICollection Children { get; set; }
381 | }
382 |
383 | public class ThingCollectionItem
384 | {
385 | public int ID { get; set; }
386 | public ThingWithCollection Parent { get; set; }
387 | }
388 | }
389 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityThreadSafetyTests.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper.EquivalencyExpression;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace AutoMapper.Collection
7 | {
8 | public class MapCollectionWithEqualityThreadSafetyTests
9 | {
10 | public async Task Should_Work_When_Initialized_Concurrently()
11 | {
12 | Action act = () =>
13 | {
14 | new MapperConfiguration(cfg =>
15 | {
16 | cfg.AddCollectionMappers();
17 | });
18 | };
19 | var tasks = new List();
20 | for (var i = 0; i < 5; i++)
21 | {
22 | tasks.Add(Task.Run(act));
23 | }
24 |
25 | await Task.WhenAll(tasks.ToArray());
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/MappingTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AutoMapper.Collection
4 | {
5 | public abstract class MappingTestBase
6 | {
7 | protected IMapper CreateMapper(Action cfg)
8 | {
9 | var map = new MapperConfiguration(cfg);
10 | map.CompileMappings();
11 |
12 | var mapper = map.CreateMapper();
13 | mapper.ConfigurationProvider.AssertConfigurationIsValid();
14 | return mapper;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/NullableIdTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AutoMapper.EquivalencyExpression;
3 | using FluentAssertions;
4 | using Xunit;
5 |
6 | namespace AutoMapper.Collection
7 | {
8 | public class NullableIdTests : MappingTestBase
9 | {
10 | [Fact]
11 | public void Should_Work_With_Null_Id()
12 | {
13 | var mapper = CreateMapper(x =>
14 | {
15 | x.AddCollectionMappers();
16 | x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID);
17 | });
18 |
19 | var original = new List
20 | {
21 | new ThingWithStringId { ID = "1", Title = "test0" },
22 | new ThingWithStringId { ID = "2", Title = "test2" },
23 | };
24 |
25 | var dtos = new List
26 | {
27 | new ThingWithStringIdDto { ID = "1", Title = "test0" },
28 | new ThingWithStringIdDto { ID = "2", Title = "test2" },
29 | new ThingWithStringIdDto { Title = "test3" }
30 | };
31 |
32 | mapper.Map(dtos, original);
33 |
34 | original.Should().HaveSameCount(dtos);
35 | }
36 |
37 |
38 | [Fact]
39 | public void Should_Work_With_Multiple_Null_Id()
40 | {
41 | var mapper = CreateMapper(x =>
42 | {
43 | x.AddCollectionMappers();
44 | x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID);
45 | });
46 |
47 | var original = new List
48 | {
49 | new ThingWithStringId { ID = "1", Title = "test0" },
50 | new ThingWithStringId { ID = "2", Title = "test2" },
51 | new ThingWithStringId { ID = "3", Title = "test3" },
52 | };
53 |
54 | var dtos = new List
55 | {
56 | new ThingWithStringIdDto { ID = "1", Title = "test0" },
57 | new ThingWithStringIdDto { ID = "2", Title = "test2" },
58 | new ThingWithStringIdDto { Title = "test3" },
59 | new ThingWithStringIdDto { Title = "test4" },
60 | };
61 |
62 | mapper.Map(dtos, original);
63 |
64 | original.Should().HaveSameCount(dtos);
65 | }
66 |
67 | public class ThingWithStringId
68 | {
69 | public string ID { get; set; }
70 | public string Title { get; set; }
71 | public override string ToString() { return Title; }
72 | }
73 |
74 | public class ThingWithStringIdDto
75 | {
76 | public string ID { get; set; }
77 | public string Title { get; set; }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/OptionsTests.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper.EquivalencyExpression;
2 | using AutoMapper.Mappers;
3 | using FluentAssertions;
4 | using System.Collections.Generic;
5 | using Xunit;
6 |
7 | namespace AutoMapper.Collection
8 | {
9 | public class OptionsTests : MappingTestBase
10 | {
11 | [Fact]
12 | public void Should_Retain_Options_Passed_In_Map()
13 | {
14 | var collectionTestValue = 0;
15 | var collectionMapper = CreateMapper(cfg =>
16 | {
17 | cfg.AddCollectionMappers();
18 | cfg.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID).AfterMap((_, __, ctx) => collectionTestValue = (int)ctx.Items["Test"]);
19 | });
20 |
21 | var normalTestValue = 0;
22 | var normalMapper = CreateMapper(cfg =>
23 | {
24 | cfg.CreateMap().AfterMap((_, __, ctx) => normalTestValue = (int)ctx.Items["Test"]);
25 | });
26 |
27 | var dtos = new List
28 | {
29 | new ThingDto { ID = 1, Title = "test0" },
30 | new ThingDto { ID = 2, Title = "test2" }
31 | };
32 |
33 | var items = new List
34 | {
35 | new Thing { ID = 1, Title = "test1" },
36 | new Thing { ID = 3, Title = "test3" },
37 | };
38 |
39 | collectionMapper.Map(dtos, items, opts => opts.Items.Add("Test", 1));
40 | normalMapper.Map(dtos, items, opts => opts.Items.Add("Test", 1));
41 |
42 | collectionTestValue.ShouldBeEquivalentTo(1);
43 | normalTestValue.ShouldBeEquivalentTo(1);
44 | }
45 |
46 | public class Thing
47 | {
48 | public int ID { get; set; }
49 | public string Title { get; set; }
50 |
51 | public override string ToString()
52 | {
53 | return Title;
54 | }
55 | }
56 |
57 | public class ThingDto
58 | {
59 | public int ID { get; set; }
60 | public string Title { get; set; }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection.Tests/ValueTypeTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using AutoMapper.EquivalencyExpression;
7 | using Xunit;
8 |
9 | namespace AutoMapper.Collection
10 | {
11 | public class ValueTypeTests
12 | {
13 | [Fact]
14 | public void MapValueTypes()
15 | {
16 | var mapper = new Mapper(new MapperConfiguration(c =>
17 | {
18 | c.AddCollectionMappers();
19 |
20 | c.CreateMap()
21 | .ForMember(x => x.Nationalities, m => m.MapFrom(x => x.Persons))
22 | .ReverseMap();
23 |
24 | c.CreateMap()
25 | .EqualityComparison((src, dest) => dest.NationalityCountryId == src);
26 | }));
27 |
28 | var persons = new[]
29 | {
30 | new PersonNationality{PersonId = 1, NationalityCountryId = 101},
31 | new PersonNationality{PersonId = 2, NationalityCountryId = 102},
32 | new PersonNationality{PersonId = 3, NationalityCountryId = 103},
33 | new PersonNationality{PersonId = 4, NationalityCountryId = 104},
34 | };
35 |
36 | var country = new Country { Persons = new List(persons) };
37 | var countryDto = new CountryDto { Nationalities = new List { 104, 103, 105 } };
38 |
39 | mapper.Map(countryDto, country);
40 |
41 | Assert.NotStrictEqual(new[] { persons[3], persons[2], country.Persons.Last() }, country.Persons);
42 | Assert.Equal(0, country.Persons.Last().PersonId);
43 | }
44 |
45 | public class PersonNationality
46 | {
47 | public int PersonId { get; set; }
48 | public int NationalityCountryId { get; set; }
49 | }
50 |
51 | public class Country
52 | {
53 | public IList Persons { get; set; }
54 | }
55 |
56 | public class CountryDto
57 | {
58 | public IList Nationalities { get; set; }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/AutoMapper.Collection.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Collection Add/Remove/Update support for AutoMapper. AutoMapper.Collection adds EqualityComparison Expressions for TypeMaps to determine if Source and Destination type are equivalent to each other when mapping collections.
5 | Tyler Carlson
6 | net8.0
7 | AutoMapper.Collection
8 | AutoMapper.Collection
9 | icon.png
10 | https://github.com/AutoMapper/Automapper.Collection
11 | ../Key.snk
12 | true
13 | MIT
14 | true
15 | true
16 | v
17 | snupkg
18 | true
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | all
30 | runtime; build; native; contentfiles; analyzers; buildtransitive
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using AutoMapper.Features;
4 | using AutoMapper.Collection.Runtime;
5 | using AutoMapper.EquivalencyExpression;
6 |
7 | namespace AutoMapper.Collection.Configuration
8 | {
9 | public class CollectionMappingExpressionFeature : IMappingFeature
10 | {
11 | private readonly Expression> _expression;
12 |
13 | public CollectionMappingExpressionFeature(Expression> expression)
14 | {
15 | _expression = expression;
16 | }
17 |
18 | public void Configure(TypeMap typeMap)
19 | {
20 | var equivalentExpression = new EquivalentExpression(_expression);
21 | typeMap.Features.Set(new CollectionMappingFeature(equivalentExpression));
22 | }
23 |
24 | public IMappingFeature Reverse()
25 | {
26 | var reverseExpression = Expression.Lambda>(_expression.Body, _expression.Parameters[1], _expression.Parameters[0]);
27 | return new CollectionMappingExpressionFeature(reverseExpression);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using AutoMapper.Collection.Runtime;
5 | using AutoMapper.EquivalencyExpression;
6 | using AutoMapper.Features;
7 | using AutoMapper.Internal;
8 | using AutoMapper.Mappers;
9 |
10 | namespace AutoMapper.Collection.Configuration
11 | {
12 | public class GeneratePropertyMapsExpressionFeature : IGlobalFeature
13 | {
14 | private readonly ObjectToEquivalencyExpressionByEquivalencyExistingMapper _mapper;
15 | private readonly List, IGeneratePropertyMaps>> _generators = new List, IGeneratePropertyMaps>>();
16 |
17 | public GeneratePropertyMapsExpressionFeature(ObjectToEquivalencyExpressionByEquivalencyExistingMapper mapper)
18 | {
19 | _mapper = mapper;
20 | }
21 |
22 | public void Add(Func, IGeneratePropertyMaps> creator)
23 | {
24 | _generators.Add(creator);
25 | }
26 |
27 | void IGlobalFeature.Configure(IGlobalConfiguration configurationProvider)
28 | {
29 | _mapper.Configuration = configurationProvider;
30 | var generators = _generators
31 | .Select(x => x.Invoke(configurationProvider.ServiceCtor))
32 | .ToList();
33 | configurationProvider.Features.Set(new GeneratePropertyMapsFeature(generators));
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/CustomExpressionVisitor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 |
7 | namespace AutoMapper.EquivalencyExpression
8 | {
9 | internal class CustomExpressionVisitor : ExpressionVisitor
10 | {
11 | readonly ParameterExpression _parameter;
12 | private readonly IEnumerable _propertyMaps;
13 |
14 | internal CustomExpressionVisitor(ParameterExpression parameter, IEnumerable propertyMaps)
15 | {
16 | _parameter = parameter;
17 | _propertyMaps = propertyMaps;
18 | }
19 |
20 | protected override Expression VisitParameter(ParameterExpression node)
21 | {
22 | return _parameter;
23 | }
24 |
25 | protected override Expression VisitMember(MemberExpression node)
26 | {
27 | if (node.Member is PropertyInfo)
28 | {
29 | var matchPM = _propertyMaps.FirstOrDefault(pm => pm.DestinationMember == node.Member);
30 | if (matchPM == null)
31 | throw new Exception("No matching PropertyMap");
32 | var memberGetters = matchPM.SourceMembers;
33 |
34 | var memberExpression = Expression.Property(Visit(node.Expression), memberGetters.First() as PropertyInfo);
35 |
36 | foreach (var memberGetter in memberGetters.Skip(1))
37 | memberExpression = Expression.Property(memberExpression, memberGetter as PropertyInfo);
38 | return memberExpression;
39 | }
40 | return base.VisitMember(node);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpression.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Reflection;
4 | using AutoMapper.Collection;
5 |
6 | namespace AutoMapper.EquivalencyExpression
7 | {
8 | internal class EquivalentExpression : IEquivalentComparer
9 | {
10 | internal static IEquivalentComparer BadValue { get; private set; }
11 |
12 | static EquivalentExpression()
13 | {
14 | BadValue = new EquivalentExpression();
15 | }
16 |
17 | public int GetHashCode(object obj)
18 | {
19 | throw new Exception("How'd you get here");
20 | }
21 |
22 | public bool IsEquivalent(object source, object destination)
23 | {
24 | return false;
25 | }
26 | }
27 |
28 | internal class EquivalentExpression : IEquivalentComparer
29 | {
30 | private readonly Expression> _equivalentExpression;
31 | private readonly Func _equivalentFunc;
32 | private readonly Func _sourceHashCodeFunc;
33 | private readonly Func _destinationHashCodeFunc;
34 |
35 | public EquivalentExpression(Expression> equivalentExpression)
36 | {
37 | _equivalentExpression = equivalentExpression;
38 | _equivalentFunc = _equivalentExpression.Compile();
39 |
40 | var sourceParameter = equivalentExpression.Parameters[0];
41 | var destinationParameter = equivalentExpression.Parameters[1];
42 |
43 | var members = HashableExpressionsVisitor.Expand(sourceParameter, destinationParameter, equivalentExpression);
44 |
45 | _sourceHashCodeFunc = members.Item1.GetHashCodeExpression(sourceParameter).Compile();
46 | _destinationHashCodeFunc = members.Item2.GetHashCodeExpression(destinationParameter).Compile();
47 | }
48 |
49 | public bool IsEquivalent(object source, object destination)
50 | {
51 |
52 | if (source == null && destination == null)
53 | {
54 | return true;
55 | }
56 |
57 | if (source == null || destination == null)
58 | {
59 | return false;
60 | }
61 |
62 | if (!(source is TSource src) || !(destination is TDestination dest))
63 | {
64 | return false;
65 | }
66 |
67 | return _equivalentFunc(src, dest);
68 | }
69 |
70 | public Expression> ToSingleSourceExpression(TSource source)
71 | {
72 | if (source == null)
73 | throw new Exception("Invalid somehow");
74 |
75 | var expression = new ParametersToConstantVisitor(source).Visit(_equivalentExpression) as LambdaExpression;
76 | return Expression.Lambda>(expression.Body, _equivalentExpression.Parameters[1]);
77 | }
78 |
79 | public int GetHashCode(object obj)
80 | {
81 | if (obj is TSource src)
82 | return _sourceHashCodeFunc(src);
83 | if (obj is TDestination dest)
84 | return _destinationHashCodeFunc(dest);
85 | return default(int);
86 | }
87 | }
88 |
89 | internal class ParametersToConstantVisitor : ExpressionVisitor
90 | {
91 | private readonly T _value;
92 |
93 | public ParametersToConstantVisitor(T value)
94 | {
95 | _value = value;
96 | }
97 |
98 | protected override Expression VisitParameter(ParameterExpression node)
99 | {
100 | return node;
101 | }
102 |
103 | protected override Expression VisitMember(MemberExpression node)
104 | {
105 | if (node.Member is PropertyInfo && node.Member.DeclaringType.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()))
106 | {
107 | var memberExpression = Expression.Constant(node.Member.GetMemberValue(_value));
108 | return memberExpression;
109 | }
110 | return base.VisitMember(node);
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using AutoMapper.Collection.Configuration;
5 | using AutoMapper.Collection.Runtime;
6 | using AutoMapper.Internal;
7 | using AutoMapper.Internal.Mappers;
8 | using AutoMapper.Mappers;
9 |
10 | namespace AutoMapper.EquivalencyExpression
11 | {
12 | public static class EquivalentExpressions
13 | {
14 | public static void AddCollectionMappers(this IMapperConfigurationExpression cfg)
15 | {
16 | var mapper = new ObjectToEquivalencyExpressionByEquivalencyExistingMapper();
17 | cfg.Internal().Features.Set(new GeneratePropertyMapsExpressionFeature(mapper));
18 | cfg.Internal().InsertBefore(
19 | mapper,
20 | new EquivalentExpressionAddRemoveCollectionMapper());
21 | }
22 |
23 | private static void InsertBefore(this IMapperConfigurationExpression cfg, params IObjectMapper[] adds)
24 | where TObjectMapper : IObjectMapper
25 | {
26 | var mappers = cfg.Internal().Mappers;
27 | var targetMapper = mappers.FirstOrDefault(om => om is TObjectMapper);
28 | var index = targetMapper == null ? 0 : mappers.IndexOf(targetMapper);
29 | foreach (var mapper in adds.Reverse())
30 | {
31 | mappers.Insert(index, mapper);
32 | }
33 | }
34 |
35 | internal static IEquivalentComparer GetEquivalentExpression(this IObjectMapper mapper, Type sourceType,
36 | Type destinationType, IConfigurationProvider configuration)
37 | {
38 | var typeMap = configuration.Internal().ResolveTypeMap(sourceType, destinationType);
39 | if (typeMap == null)
40 | {
41 | return null;
42 | }
43 |
44 | var comparer = GetEquivalentExpression(configuration, typeMap);
45 | if (comparer == null)
46 | {
47 | foreach (var item in typeMap.IncludedBaseTypes)
48 | {
49 | var baseTypeMap = configuration.Internal().ResolveTypeMap(item.SourceType, item.DestinationType);
50 | if (baseTypeMap == null)
51 | {
52 | continue;
53 | }
54 |
55 | comparer = GetEquivalentExpression(configuration, baseTypeMap);
56 | if (comparer != null)
57 | {
58 | break;
59 | }
60 | }
61 | }
62 | return comparer;
63 | }
64 |
65 | internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvider configurationProvider, TypeMap typeMap)
66 | {
67 | return typeMap.Features.Get()?.EquivalentComparer
68 | ?? configurationProvider.Internal().Features.Get().Get(typeMap);
69 | }
70 |
71 | ///
72 | /// Make Comparison between and
73 | ///
74 | /// Compared type
75 | /// Type being compared to
76 | /// Base Mapping Expression
77 | /// Equivalent Expression between and
78 | ///
79 | public static IMappingExpression EqualityComparison(this IMappingExpression mappingExpression, Expression> EquivalentExpression)
80 | {
81 | mappingExpression.Features.Set(new CollectionMappingExpressionFeature(EquivalentExpression));
82 | return mappingExpression;
83 | }
84 |
85 | public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg)
86 | where TGeneratePropertyMaps : IGeneratePropertyMaps
87 | {
88 | (cfg.Internal().Features.Get()
89 | ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps."))
90 | .Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps)));
91 | }
92 |
93 | public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg, IGeneratePropertyMaps generatePropertyMaps)
94 | {
95 | (cfg.Internal().Features.Get()
96 | ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps."))
97 | .Add(_ => generatePropertyMaps);
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/ExpressionExtentions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 | using AutoMapper.Collection;
7 |
8 | namespace AutoMapper.EquivalencyExpression
9 | {
10 | internal static class ExpressionExtentions
11 | {
12 | private static readonly ConcurrentDictionary _singleParameterTypeDictionary = new ConcurrentDictionary();
13 |
14 | public static Type GetSinglePredicateExpressionArgumentType(this Type type)
15 | {
16 | return _singleParameterTypeDictionary.GetOrAdd(type, t =>
17 | {
18 | var isExpression = typeof(Expression).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo());
19 | if (!isExpression)
20 | return null;
21 |
22 | var expressionOf = t.GetTypeInfo().GenericTypeArguments[0];
23 | var isFunction = expressionOf.GetGenericTypeDefinition() == typeof(Func<,>);
24 | if (!isFunction)
25 | return null;
26 |
27 | var isPredicate = expressionOf.GetTypeInfo().GenericTypeArguments[1] == typeof(bool);
28 | if (!isPredicate)
29 | return null;
30 |
31 | var objType = expressionOf.GetTypeInfo().GenericTypeArguments[0];
32 | return CacheAndReturnType(type, objType);
33 | });
34 | }
35 |
36 | private static Type CacheAndReturnType(Type type, Type objType)
37 | {
38 | _singleParameterTypeDictionary.AddOrUpdate(type, objType, (_, __) => objType);
39 | return objType;
40 | }
41 |
42 | public static Expression> GetHashCodeExpression(this List members, ParameterExpression sourceParam)
43 | {
44 | var hashMultiply = Expression.Constant(397L);
45 |
46 | var hashVariable = Expression.Variable(typeof(long), "hashCode");
47 | var returnTarget = Expression.Label(typeof(int));
48 | var returnExpression = Expression.Return(returnTarget, Expression.Convert(hashVariable, typeof(int)), typeof(int));
49 | var returnLabel = Expression.Label(returnTarget, Expression.Constant(-1));
50 |
51 | var expressions = new List();
52 | foreach (var member in members)
53 | {
54 | // Call the GetHashCode method
55 | var hasCodeExpression = Expression.Convert(Expression.Call(member, member.Type.GetDeclaredMethod(nameof(GetHashCode))), typeof(long));
56 |
57 | // return (((object)x) == null ? 0 : x.GetHashCode())
58 | var hashCodeReturnTarget = Expression.Label(typeof(long));
59 | var hashCode = Expression.Block(
60 | Expression.IfThenElse(
61 | Expression.ReferenceEqual(Expression.Convert(member, typeof(object)), Expression.Constant(null)),
62 | Expression.Return(hashCodeReturnTarget, Expression.Constant(0L, typeof(long))),
63 | Expression.Return(hashCodeReturnTarget, hasCodeExpression)),
64 | Expression.Label(hashCodeReturnTarget, Expression.Constant(0L, typeof(long))));
65 |
66 | if (expressions.Count == 0)
67 | {
68 | expressions.Add(Expression.Assign(hashVariable, hashCode));
69 | }
70 | else
71 | {
72 | var oldHashMultiplied = Expression.Multiply(hashVariable, hashMultiply);
73 | var xOrHash = Expression.ExclusiveOr(oldHashMultiplied, hashCode);
74 | expressions.Add(Expression.Assign(hashVariable, xOrHash));
75 | }
76 | }
77 |
78 | expressions.Add(returnExpression);
79 | expressions.Add(returnLabel);
80 |
81 | var resutltBlock = Expression.Block(new[] { hashVariable }, expressions);
82 |
83 | return Expression.Lambda>(resutltBlock, sourceParam);
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/GenerateEquivilentExpressionFromTypeMap.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 |
5 | namespace AutoMapper.EquivalencyExpression
6 | {
7 | internal class GenerateEquivalentExpressionFromTypeMap
8 | {
9 | private static readonly ConcurrentDictionary _EquivalentExpressionses = new ConcurrentDictionary();
10 | internal static Expression GetExpression(TypeMap typeMap, object value)
11 | {
12 | return _EquivalentExpressionses.GetOrAdd(typeMap, t => new GenerateEquivalentExpressionFromTypeMap(t))
13 | .CreateEquivalentExpression(value);
14 | }
15 |
16 | private readonly TypeMap _typeMap;
17 |
18 | private GenerateEquivalentExpressionFromTypeMap(TypeMap typeMap)
19 | {
20 | _typeMap = typeMap;
21 | }
22 |
23 | private Expression CreateEquivalentExpression(object value)
24 | {
25 | var express = value as LambdaExpression;
26 | var destExpr = Expression.Parameter(_typeMap.SourceType, express.Parameters[0].Name);
27 |
28 | var result = new CustomExpressionVisitor(destExpr, _typeMap.PropertyMaps).Visit(express.Body);
29 |
30 | return Expression.Lambda(result, destExpr);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/HashableExpressionsVisitor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 |
6 | namespace AutoMapper.EquivalencyExpression
7 | {
8 | internal class HashableExpressionsVisitor : ExpressionVisitor
9 | {
10 | private readonly List _destinationMembers = new List();
11 | private readonly ParameterExpression _destinationParameter;
12 | private readonly List _sourceMembers = new List();
13 | private readonly ParameterExpression _sourceParameter;
14 |
15 | private readonly ParameterFinderVisitor _paramFinder = new ParameterFinderVisitor();
16 |
17 | internal HashableExpressionsVisitor(ParameterExpression sourceParameter, ParameterExpression destinationParameter)
18 | {
19 | _sourceParameter = sourceParameter;
20 | _destinationParameter = destinationParameter;
21 | }
22 |
23 | internal static Tuple, List> Expand(ParameterExpression sourceParameter, ParameterExpression destinationParameter, Expression expression)
24 | {
25 | var visitor = new HashableExpressionsVisitor(sourceParameter, destinationParameter);
26 | visitor.Visit(expression);
27 | return Tuple.Create(visitor._sourceMembers, visitor._destinationMembers);
28 | }
29 |
30 | protected override Expression VisitConditional(ConditionalExpression node)
31 | {
32 | return node;
33 | }
34 |
35 | protected override Expression VisitBinary(BinaryExpression node)
36 | {
37 | switch (node.NodeType)
38 | {
39 | case ExpressionType.Equal:
40 | VisitCompare(node.Left, node.Right);
41 | break;
42 | case ExpressionType.And:
43 | case ExpressionType.AndAlso:
44 | return base.VisitBinary(node);
45 | case ExpressionType.Or:
46 | case ExpressionType.OrElse:
47 | return node; // Maybe compare 0r's for expression matching on side
48 | }
49 |
50 | return node;
51 | }
52 |
53 | private void VisitCompare(Expression leftNode, Expression rightNode)
54 | {
55 | _paramFinder.Visit(leftNode);
56 | var left = _paramFinder.Parameters;
57 | _paramFinder.Visit(rightNode);
58 | var right = _paramFinder.Parameters;
59 |
60 | if (left.All(p => p == _destinationParameter) && right.All(p => p == _sourceParameter))
61 | {
62 | _sourceMembers.Add(rightNode);
63 | _destinationMembers.Add(leftNode);
64 | }
65 | if (left.All(p => p == _sourceParameter) && right.All(p => p == _destinationParameter))
66 | {
67 | _sourceMembers.Add(leftNode);
68 | _destinationMembers.Add(rightNode);
69 | }
70 | }
71 | }
72 |
73 | internal class ParameterFinderVisitor : ExpressionVisitor
74 | {
75 | public IList Parameters { get; private set; }
76 |
77 | public override Expression Visit(Expression node)
78 | {
79 | Parameters = new List();
80 | return base.Visit(node);
81 | }
82 |
83 | protected override Expression VisitParameter(ParameterExpression node)
84 | {
85 | Parameters.Add(node);
86 | return node;
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/IEquivalentComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 |
4 | namespace AutoMapper.EquivalencyExpression
5 | {
6 | public interface IEquivalentComparer
7 | {
8 | int GetHashCode(object obj);
9 | bool IsEquivalent(object source, object destination);
10 | }
11 |
12 | public interface IEquivalentComparer : IEquivalentComparer
13 | {
14 | Expression> ToSingleSourceExpression(TSource destination);
15 | }
16 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/EquivalencyExpression/IGeneratePropertyMaps.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace AutoMapper.EquivalencyExpression
4 | {
5 | public interface IGeneratePropertyMaps
6 | {
7 | IEnumerable GeneratePropertyMaps(TypeMap typeMap);
8 | }
9 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 | using AutoMapper.Collection;
7 | using AutoMapper.EquivalencyExpression;
8 | using AutoMapper.Internal;
9 | using AutoMapper.Internal.Mappers;
10 | using static System.Linq.Expressions.Expression;
11 | using static AutoMapper.Execution.ExpressionBuilder;
12 |
13 | namespace AutoMapper.Mappers
14 | {
15 | public class EquivalentExpressionAddRemoveCollectionMapper : IObjectMapper
16 | {
17 | private readonly CollectionMapper _collectionMapper = new CollectionMapper();
18 |
19 | public static TDestination Map(TSource source, TDestination destination, ResolutionContext context, IEquivalentComparer equivalentComparer)
20 | where TSource : IEnumerable
21 | where TDestination : ICollection
22 | {
23 | if (source == null || destination == null)
24 | {
25 | return destination;
26 | }
27 |
28 | var destList = destination.ToLookup(x => equivalentComparer.GetHashCode(x)).ToDictionary(x => x.Key, x => x.ToList());
29 |
30 | var items = source.Select(x =>
31 | {
32 | var sourceHash = equivalentComparer.GetHashCode(x);
33 |
34 | var item = default(TDestinationItem);
35 | if (destList.TryGetValue(sourceHash, out var itemList))
36 | {
37 | item = itemList.FirstOrDefault(dest => equivalentComparer.IsEquivalent(x, dest));
38 | if (item != null)
39 | {
40 | itemList.Remove(item);
41 | }
42 | }
43 | return new { SourceItem = x, DestinationItem = item };
44 | });
45 |
46 | foreach (var keypair in items)
47 | {
48 | if (keypair.DestinationItem == null)
49 | {
50 | destination.Add((TDestinationItem)context.Mapper.Map(keypair.SourceItem, null, typeof(TSourceItem), typeof(TDestinationItem)));
51 | }
52 | else
53 | {
54 | context.Mapper.Map(keypair.SourceItem, keypair.DestinationItem);
55 | }
56 | }
57 |
58 | foreach (var removedItem in destList.SelectMany(x => x.Value))
59 | {
60 | destination.Remove(removedItem);
61 | }
62 |
63 | return destination;
64 | }
65 |
66 | private static readonly MethodInfo _mapMethodInfo = typeof(EquivalentExpressionAddRemoveCollectionMapper).GetRuntimeMethods().Single(x => x.IsStatic && x.Name == nameof(Map));
67 | private static readonly ConcurrentDictionary _objectMapperCache = new ConcurrentDictionary();
68 |
69 | public bool IsMatch(TypePair typePair)
70 | {
71 | return typePair.SourceType.IsEnumerableType()
72 | && typePair.DestinationType.IsCollectionType();
73 | }
74 |
75 | public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap,
76 | Expression sourceExpression, Expression destExpression)
77 | {
78 | var sourceType = TypeHelper.GetElementType(sourceExpression.Type);
79 | var destType = TypeHelper.GetElementType(destExpression.Type);
80 |
81 | var equivalencyExpression = this.GetEquivalentExpression(sourceType, destType, configurationProvider);
82 | if (equivalencyExpression == null)
83 | {
84 | var typePair = new TypePair(sourceExpression.Type, destExpression.Type);
85 | return _objectMapperCache.GetOrAdd(typePair, _ =>
86 | {
87 | var mappers = new List(configurationProvider.GetMappers());
88 | for (var i = mappers.IndexOf(this) + 1; i < mappers.Count; i++)
89 | {
90 | var mapper = mappers[i];
91 | if (mapper.IsMatch(typePair))
92 | {
93 | return mapper;
94 | }
95 | }
96 | return _collectionMapper;
97 | })
98 | .MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression);
99 | }
100 |
101 | var method = _mapMethodInfo.MakeGenericMethod(sourceExpression.Type, sourceType, destExpression.Type, destType);
102 | var map = Call(null, method, sourceExpression, destExpression, ContextParameter, Constant(equivalencyExpression));
103 |
104 | var notNull = NotEqual(destExpression, Constant(null));
105 | var collectionMapperExpression = _collectionMapper.MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression);
106 | return Condition(notNull, map, Convert(collectionMapperExpression, destExpression.Type));
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Mappers/IConfigurationObjectMapper.cs:
--------------------------------------------------------------------------------
1 | //using AutoMapper.Internal.Mappers;
2 |
3 | //namespace AutoMapper.Mappers
4 | //{
5 | // public interface IConfigurationObjectMapper : IObjectMapper
6 | // {
7 | // IConfigurationProvider ConfigurationProvider { get; set; }
8 | // }
9 | //}
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Mappers/ObjectToEquivalencyExpressionByEquivalencyExistingMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Reflection;
5 | using AutoMapper.EquivalencyExpression;
6 | using AutoMapper.Internal;
7 | using AutoMapper.Internal.Mappers;
8 | using static System.Linq.Expressions.Expression;
9 |
10 | namespace AutoMapper.Mappers
11 | {
12 | public class ObjectToEquivalencyExpressionByEquivalencyExistingMapper : IObjectMapper
13 | {
14 | internal IConfigurationProvider Configuration { get; set; }
15 |
16 | public static Expression> Map(TSource source, IEquivalentComparer toSourceExpression)
17 | {
18 | return toSourceExpression.ToSingleSourceExpression(source);
19 | }
20 |
21 | private static readonly MethodInfo MapMethodInfo = typeof(ObjectToEquivalencyExpressionByEquivalencyExistingMapper).GetRuntimeMethods().First(_ => _.IsStatic);
22 |
23 | public bool IsMatch(TypePair typePair)
24 | {
25 | var destExpressArgType = typePair.DestinationType.GetSinglePredicateExpressionArgumentType();
26 | if (destExpressArgType == null)
27 | return false;
28 | return this.GetEquivalentExpression(typePair.SourceType, destExpressArgType, Configuration) != null;
29 | }
30 |
31 | public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap,
32 | Expression sourceExpression, Expression destExpression)
33 | {
34 | var destExpressArgType = destExpression.Type.GetSinglePredicateExpressionArgumentType();
35 | var toSourceExpression = this.GetEquivalentExpression(sourceExpression.Type, destExpressArgType, configurationProvider);
36 | return Call(null, MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpressArgType), sourceExpression, Constant(toSourceExpression));
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/PrimitiveExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace AutoMapper.Collection
2 | {
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Reflection;
7 |
8 | internal static class PrimitiveExtensions
9 | {
10 | public static bool IsCollectionType(this Type type) => type.ImplementsGenericInterface(typeof(ICollection<>));
11 | public static bool IsDictionaryType(this Type type) => type.ImplementsGenericInterface(typeof(IDictionary<,>));
12 | public static bool IsEnumerableType(this Type type) => typeof(IEnumerable).IsAssignableFrom(type);
13 |
14 | private static bool ImplementsGenericInterface(this Type type, Type interfaceType)
15 | {
16 | if (type.IsGenericType(interfaceType))
17 | {
18 | return true;
19 | }
20 | foreach (var @interface in type.GetTypeInfo().ImplementedInterfaces)
21 | {
22 | if (@interface.IsGenericType(interfaceType))
23 | {
24 | return true;
25 | }
26 | }
27 | return false;
28 | }
29 |
30 | private static bool IsGenericType(this Type type, Type genericType) => type.IsGenericType() && type.GetGenericTypeDefinition() == genericType;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/ReflectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace AutoMapper.Collection
5 | {
6 | internal static class ReflectionHelper
7 | {
8 | public static object GetMemberValue(this MemberInfo propertyOrField, object target)
9 | {
10 | var property = propertyOrField as PropertyInfo;
11 | if (property != null)
12 | return property.GetValue(target, null);
13 | var field = propertyOrField as FieldInfo;
14 | if (field != null)
15 | return field.GetValue(target);
16 | throw Expected(propertyOrField);
17 | }
18 |
19 | private static ArgumentOutOfRangeException Expected(MemberInfo propertyOrField)
20 | => new ArgumentOutOfRangeException("propertyOrField",
21 | "Expected a property or field, not " + propertyOrField);
22 |
23 | public static Type GetMemberType(this MemberInfo memberInfo)
24 | {
25 | if (memberInfo is MethodInfo)
26 | return ((MethodInfo)memberInfo).ReturnType;
27 | if (memberInfo is PropertyInfo)
28 | return ((PropertyInfo)memberInfo).PropertyType;
29 | if (memberInfo is FieldInfo)
30 | return ((FieldInfo)memberInfo).FieldType;
31 | return null;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Runtime/CollectionMappingFeature.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper.EquivalencyExpression;
2 | using AutoMapper.Features;
3 | using AutoMapper.Internal;
4 |
5 | namespace AutoMapper.Collection.Runtime
6 | {
7 | public class CollectionMappingFeature : IRuntimeFeature
8 | {
9 | public CollectionMappingFeature(IEquivalentComparer equivalentComparer)
10 | {
11 | EquivalentComparer = equivalentComparer;
12 | }
13 |
14 | public IEquivalentComparer EquivalentComparer { get; }
15 |
16 | void IRuntimeFeature.Seal(IGlobalConfiguration configurationProvider)
17 | {
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Linq.Expressions;
6 | using System.Reflection;
7 | using AutoMapper.EquivalencyExpression;
8 | using AutoMapper.Features;
9 | using AutoMapper.Internal;
10 |
11 | namespace AutoMapper.Collection.Runtime
12 | {
13 | public class GeneratePropertyMapsFeature : IRuntimeFeature
14 | {
15 | private readonly IList _generators;
16 | private readonly ConcurrentDictionary _comparers = new ConcurrentDictionary();
17 |
18 | public GeneratePropertyMapsFeature(List generators)
19 | {
20 | _generators = generators.AsReadOnly();
21 | }
22 |
23 | public IEquivalentComparer Get(TypeMap typeMap)
24 | {
25 | return _comparers
26 | .GetOrAdd(typeMap.Types, _ =>
27 | _generators
28 | .Select(x => CreateEquivalentExpression(x.GeneratePropertyMaps(typeMap)))
29 | .FirstOrDefault(x => x != null));
30 | }
31 |
32 | void IRuntimeFeature.Seal(IGlobalConfiguration configurationProvider)
33 | {
34 | }
35 |
36 | private IEquivalentComparer CreateEquivalentExpression(IEnumerable propertyMaps)
37 | {
38 | if (!propertyMaps.Any() || propertyMaps.Any(pm => pm.DestinationMember.GetMemberType() != pm.SourceMember.GetMemberType()))
39 | {
40 | return null;
41 | }
42 |
43 | var typeMap = propertyMaps.First().TypeMap;
44 | var srcType = typeMap.SourceType;
45 | var destType = typeMap.DestinationType;
46 | var srcExpr = Expression.Parameter(srcType, "src");
47 | var destExpr = Expression.Parameter(destType, "dest");
48 |
49 | var equalExpr = propertyMaps.Select(pm => SourceEqualsDestinationExpression(pm, srcExpr, destExpr)).ToList();
50 | if (equalExpr.Count == 0)
51 | {
52 | return EquivalentExpression.BadValue;
53 | }
54 |
55 | var finalExpression = equalExpr.Skip(1).Aggregate(equalExpr[0], Expression.And);
56 |
57 | var expr = Expression.Lambda(finalExpression, srcExpr, destExpr);
58 | var genericExpressionType = typeof(EquivalentExpression<,>).MakeGenericType(srcType, destType);
59 | return Activator.CreateInstance(genericExpressionType, expr) as IEquivalentComparer;
60 | }
61 |
62 | private BinaryExpression SourceEqualsDestinationExpression(PropertyMap propertyMap, Expression srcExpr, Expression destExpr)
63 | {
64 | var srcPropExpr = Expression.Property(srcExpr, propertyMap.SourceMember as PropertyInfo);
65 | var destPropExpr = Expression.Property(destExpr, propertyMap.DestinationMember as PropertyInfo);
66 | return Expression.Equal(srcPropExpr, destPropExpr);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace AutoMapper.Collection
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Linq.Expressions;
7 | using System.Reflection;
8 | using System.Reflection.Emit;
9 |
10 | internal static class TypeExtensions
11 | {
12 | public static Type GetGenericTypeDefinitionIfGeneric(this Type type)
13 | {
14 | return type.IsGenericType() ? type.GetGenericTypeDefinition() : type;
15 | }
16 |
17 | public static Type[] GetGenericArguments(this Type type)
18 | {
19 | return type.GetTypeInfo().GenericTypeArguments;
20 | }
21 |
22 | public static Type[] GetGenericParameters(this Type type)
23 | {
24 | return type.GetGenericTypeDefinition().GetTypeInfo().GenericTypeParameters;
25 | }
26 |
27 | public static IEnumerable GetDeclaredConstructors(this Type type)
28 | {
29 | return type.GetTypeInfo().DeclaredConstructors;
30 | }
31 |
32 | #if NET45
33 | public static Type CreateType(this TypeBuilder type)
34 | {
35 | return type.CreateTypeInfo().AsType();
36 | }
37 | #endif
38 |
39 | public static IEnumerable GetDeclaredMembers(this Type type)
40 | {
41 | return type.GetTypeInfo().DeclaredMembers;
42 | }
43 |
44 | public static IEnumerable GetAllMembers(this Type type)
45 | {
46 | while (true)
47 | {
48 | foreach (var memberInfo in type.GetTypeInfo().DeclaredMembers)
49 | {
50 | yield return memberInfo;
51 | }
52 |
53 | type = type.BaseType();
54 |
55 | if (type == null)
56 | {
57 | yield break;
58 | }
59 | }
60 | }
61 |
62 | public static MemberInfo[] GetMember(this Type type, string name)
63 | {
64 | return type.GetAllMembers().Where(mi => mi.Name == name).ToArray();
65 | }
66 |
67 | public static IEnumerable GetDeclaredMethods(this Type type)
68 | {
69 | return type.GetTypeInfo().DeclaredMethods;
70 | }
71 |
72 | public static MethodInfo GetDeclaredMethod(this Type type, string name)
73 | {
74 | return type.GetAllMethods().FirstOrDefault(mi => mi.Name == name);
75 | }
76 |
77 | public static MethodInfo GetDeclaredMethod(this Type type, string name, Type[] parameters)
78 | {
79 | return type
80 | .GetAllMethods()
81 | .Where(mi => mi.Name == name)
82 | .Where(mi => mi.GetParameters().Length == parameters.Length)
83 | .FirstOrDefault(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameters));
84 | }
85 |
86 | public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] parameters)
87 | {
88 | return type
89 | .GetTypeInfo()
90 | .DeclaredConstructors
91 | .Where(mi => mi.GetParameters().Length == parameters.Length)
92 | .FirstOrDefault(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameters));
93 | }
94 |
95 | public static IEnumerable GetAllMethods(this Type type)
96 | {
97 | return type.GetRuntimeMethods();
98 | }
99 |
100 | public static IEnumerable GetDeclaredProperties(this Type type)
101 | {
102 | return type.GetTypeInfo().DeclaredProperties;
103 | }
104 |
105 | public static PropertyInfo GetDeclaredProperty(this Type type, string name)
106 | {
107 | return type.GetTypeInfo().GetDeclaredProperty(name);
108 | }
109 |
110 | public static object[] GetCustomAttributes(this Type type, Type attributeType, bool inherit)
111 | {
112 | return type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).ToArray();
113 | }
114 |
115 | public static bool IsStatic(this FieldInfo fieldInfo)
116 | {
117 | return fieldInfo?.IsStatic ?? false;
118 | }
119 |
120 | public static bool IsStatic(this PropertyInfo propertyInfo)
121 | {
122 | return propertyInfo?.GetGetMethod(true)?.IsStatic
123 | ?? propertyInfo?.GetSetMethod(true)?.IsStatic
124 | ?? false;
125 | }
126 |
127 | public static bool IsStatic(this MemberInfo memberInfo)
128 | {
129 | return (memberInfo as FieldInfo).IsStatic()
130 | || (memberInfo as PropertyInfo).IsStatic()
131 | || ((memberInfo as MethodInfo)?.IsStatic
132 | ?? false);
133 | }
134 |
135 | public static bool IsPublic(this PropertyInfo propertyInfo)
136 | {
137 | return (propertyInfo?.GetGetMethod(true)?.IsPublic ?? false)
138 | || (propertyInfo?.GetSetMethod(true)?.IsPublic ?? false);
139 | }
140 |
141 | public static IEnumerable PropertiesWithAnInaccessibleSetter(this Type type)
142 | {
143 | return type.GetDeclaredProperties().Where(pm => pm.HasAnInaccessibleSetter());
144 | }
145 |
146 | public static bool HasAnInaccessibleSetter(this PropertyInfo property)
147 | {
148 | var setMethod = property.GetSetMethod(true);
149 | return setMethod == null || setMethod.IsPrivate || setMethod.IsFamily;
150 | }
151 |
152 | public static bool IsPublic(this MemberInfo memberInfo)
153 | {
154 | return (memberInfo as FieldInfo)?.IsPublic ?? (memberInfo as PropertyInfo).IsPublic();
155 | }
156 |
157 | public static bool IsNotPublic(this ConstructorInfo constructorInfo)
158 | {
159 | return constructorInfo.IsPrivate
160 | || constructorInfo.IsFamilyAndAssembly
161 | || constructorInfo.IsFamilyOrAssembly
162 | || constructorInfo.IsFamily;
163 | }
164 |
165 | public static Assembly Assembly(this Type type)
166 | {
167 | return type.GetTypeInfo().Assembly;
168 | }
169 |
170 | public static Type BaseType(this Type type)
171 | {
172 | return type.GetTypeInfo().BaseType;
173 | }
174 |
175 | public static bool IsAssignableFrom(this Type type, Type other)
176 | {
177 | return type.GetTypeInfo().IsAssignableFrom(other.GetTypeInfo());
178 | }
179 |
180 | public static bool IsAbstract(this Type type)
181 | {
182 | return type.GetTypeInfo().IsAbstract;
183 | }
184 |
185 | public static bool IsClass(this Type type)
186 | {
187 | return type.GetTypeInfo().IsClass;
188 | }
189 |
190 | public static bool IsEnum(this Type type)
191 | {
192 | return type.GetTypeInfo().IsEnum;
193 | }
194 |
195 | public static bool IsGenericType(this Type type)
196 | {
197 | return type.GetTypeInfo().IsGenericType;
198 | }
199 |
200 | public static bool IsGenericTypeDefinition(this Type type)
201 | {
202 | return type.GetTypeInfo().IsGenericTypeDefinition;
203 | }
204 |
205 | public static bool IsInterface(this Type type)
206 | {
207 | return type.GetTypeInfo().IsInterface;
208 | }
209 |
210 | public static bool IsPrimitive(this Type type)
211 | {
212 | return type.GetTypeInfo().IsPrimitive;
213 | }
214 |
215 | public static bool IsSealed(this Type type)
216 | {
217 | return type.GetTypeInfo().IsSealed;
218 | }
219 |
220 | public static bool IsValueType(this Type type)
221 | {
222 | return type.GetTypeInfo().IsValueType;
223 | }
224 |
225 | public static bool IsInstanceOfType(this Type type, object o)
226 | {
227 | return o != null && type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
228 | }
229 |
230 | public static ConstructorInfo[] GetConstructors(this Type type)
231 | {
232 | return type.GetTypeInfo().DeclaredConstructors.ToArray();
233 | }
234 |
235 | public static PropertyInfo[] GetProperties(this Type type)
236 | {
237 | return type.GetRuntimeProperties().ToArray();
238 | }
239 |
240 | public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo, bool ignored)
241 | {
242 | return propertyInfo.GetMethod;
243 | }
244 |
245 | public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo, bool ignored)
246 | {
247 | return propertyInfo.SetMethod;
248 | }
249 |
250 | public static FieldInfo GetField(this Type type, string name)
251 | {
252 | return type.GetRuntimeField(name);
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/AutoMapper.Collection/TypeHelper.cs:
--------------------------------------------------------------------------------
1 | namespace AutoMapper.Collection
2 | {
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Reflection;
8 |
9 | internal static class TypeHelper
10 | {
11 | public static Type GetElementType(Type enumerableType) => GetElementTypes(enumerableType, null)[0];
12 |
13 | private static Type[] GetElementTypes(Type enumerableType, IEnumerable enumerable,
14 | ElemntTypeFlags flags = ElemntTypeFlags.None)
15 | {
16 | if (enumerableType.HasElementType)
17 | {
18 | return new[] { enumerableType.GetElementType() };
19 | }
20 |
21 | if (flags.HasFlag(ElemntTypeFlags.BreakKeyValuePair) && enumerableType.IsGenericType() &&
22 | enumerableType.IsDictionaryType())
23 | {
24 | return enumerableType.GetTypeInfo().GenericTypeArguments;
25 | }
26 |
27 | if (enumerableType.IsGenericType() &&
28 | enumerableType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
29 | {
30 | return enumerableType.GetTypeInfo().GenericTypeArguments;
31 | }
32 |
33 | Type ienumerableType = GetIEnumerableType(enumerableType);
34 | if (ienumerableType != null)
35 | {
36 | return ienumerableType.GetTypeInfo().GenericTypeArguments;
37 | }
38 |
39 | if (typeof(IEnumerable).IsAssignableFrom(enumerableType))
40 | {
41 | var first = enumerable?.Cast