├── .gitignore
├── LICENSE
├── NuGet
├── Slapper.AutoMapper.1.0.0.2.nupkg
├── Slapper.AutoMapper.1.0.0.3.nupkg
├── Slapper.AutoMapper.1.0.0.4.nupkg
├── Slapper.AutoMapper.1.0.0.5.nupkg
├── Slapper.AutoMapper.1.0.0.6.nupkg
├── Slapper.AutoMapper.1.0.0.7.nupkg
├── Slapper.AutoMapper.1.0.0.8.nupkg
├── Slapper.AutoMapper.1.0.0.9.nupkg
├── Slapper.AutoMapper.2.0.1.nupkg
├── Slapper.AutoMapper.2.0.2.nupkg
├── Slapper.AutoMapper.2.0.3.nupkg
├── Slapper.AutoMapper.2.0.4.nupkg
├── Slapper.AutoMapper.2.0.5.nupkg
└── install.ps1
├── README.md
├── Slapper.AutoMapper.Tests
├── ArrayTests.cs
├── CachingBehaviorTests.cs
├── ComplexMapTests.cs
├── ComplexMapsParentsAndChlidTest.cs
├── EmptyList.cs
├── ExceptionTests.cs
├── GuidConverterTests.cs
├── HashCollisionTests.cs
├── IdentifierTests.cs
├── MapCollectionsTypedTest.cs
├── MapDynamicTests.cs
├── MapUniqueChildsIdTest.cs
├── MappingToEnumTests.cs
├── MappingToGuidTests.cs
├── MappingToNullableTypesTests.cs
├── MatchingChildNameTests.cs
├── NoIdentifierTests.cs
├── NullTests.cs
├── ParentMappingTests.cs
├── PerformanceTests.cs
├── ReadMeTests.cs
├── SimpleMapTests.cs
├── SimpleTypeConversionTests.cs
├── Slapper.Tests.csproj
├── TestBase.cs
├── TestHelpers.cs
└── TypeActivatorTests.cs
├── Slapper.AutoMapper.Tests47
├── Properties
│ └── AssemblyInfo.cs
├── Slapper.Tests47.csproj
└── packages.config
├── Slapper.AutoMapper.sln
├── Slapper.AutoMapper.sln.DotSettings
└── Slapper.AutoMapper
├── CallContext.cs
├── Slapper.AutoMapper.Cache.cs
├── Slapper.AutoMapper.Configuration.cs
├── Slapper.AutoMapper.InternalHelpers.cs
├── Slapper.AutoMapper.Logging.cs
├── Slapper.AutoMapper.cs
└── Slapper.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
2 | [Bb]in/
3 | [Oo]bj/
4 |
5 | # mstest test results
6 | TestResults
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # Visual Studio cache/options directory
12 | .vs/
13 |
14 | # User-specific files
15 | *.suo
16 | *.user
17 | *.sln.docstates
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Rr]elease/
22 | x64/
23 | *_i.c
24 | *_p.c
25 | *.ilk
26 | *.meta
27 | *.obj
28 | *.pch
29 | *.pdb
30 | *.pgc
31 | *.pgd
32 | *.rsp
33 | *.sbr
34 | *.tlb
35 | *.tli
36 | *.tlh
37 | *.tmp
38 | *.log
39 | *.vspscc
40 | *.vssscc
41 | .builds
42 |
43 | # Visual C++ cache files
44 | ipch/
45 | *.aps
46 | *.ncb
47 | *.opensdf
48 | *.sdf
49 |
50 | # Visual Studio profiler
51 | *.psess
52 | *.vsp
53 | *.vspx
54 |
55 | # Guidance Automation Toolkit
56 | *.gpState
57 |
58 | # ReSharper is a .NET coding add-in
59 | _ReSharper*
60 |
61 | # NCrunch
62 | *.ncrunch*
63 | .*crunch*.local.xml
64 |
65 | # Installshield output folder
66 | [Ee]xpress
67 |
68 | # DocProject is a documentation generator add-in
69 | DocProject/buildhelp/
70 | DocProject/Help/*.HxT
71 | DocProject/Help/*.HxC
72 | DocProject/Help/*.hhc
73 | DocProject/Help/*.hhk
74 | DocProject/Help/*.hhp
75 | DocProject/Help/Html2
76 | DocProject/Help/html
77 |
78 | # Click-Once directory
79 | publish
80 |
81 | # Publish Web Output
82 | *.Publish.xml
83 |
84 | # NuGet Packages Directory
85 | packages
86 |
87 | # Windows Azure Build Output
88 | csx
89 | *.build.csdef
90 |
91 | # Windows Store app package directory
92 | AppPackages/
93 |
94 | # Others
95 | [Bb]in
96 | [Oo]bj
97 | sql
98 | TestResults
99 | [Tt]est[Rr]esult*
100 | *.Cache
101 | ClientBin
102 | [Ss]tyle[Cc]op.*
103 | ~$*
104 | *.dbmdl
105 | Generated_Code #added for RIA/Silverlight projects
106 |
107 | # Backup & report files from converting an old project file to a newer
108 | # Visual Studio version. Backup files are not needed, because we have git ;-)
109 | _UpgradeReport_Files/
110 | Backup*/
111 | UpgradeLog*.XML
112 | .vs
113 |
114 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2021, Randy Burden and contributors. All rights reserved.
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.
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.2.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.2.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.3.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.3.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.4.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.4.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.5.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.5.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.6.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.6.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.7.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.7.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.8.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.8.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.1.0.0.9.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.1.0.0.9.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.2.0.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.2.0.1.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.2.0.2.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.2.0.2.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.2.0.3.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.2.0.3.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.2.0.4.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.2.0.4.nupkg
--------------------------------------------------------------------------------
/NuGet/Slapper.AutoMapper.2.0.5.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlapperAutoMapper/Slapper.AutoMapper/8d82a2e3bf146f2724f3633c220d8b0d03950109/NuGet/Slapper.AutoMapper.2.0.5.nupkg
--------------------------------------------------------------------------------
/NuGet/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | # open splash page on package install
4 | # don't open if it is installed as a dependency
5 | # attribution: Modified from: https://github.com/JamesNK/Newtonsoft.Json/blob/master/Build/install.ps1
6 |
7 | try
8 | {
9 | $url = "http://randyburden.com/Slapper.AutoMapper/"
10 | $packageName = "slapper.automapper"
11 | $dte2 = Get-Interface $dte ([EnvDTE80.DTE2])
12 |
13 | if ($dte2.ActiveWindow.Caption -eq "Package Manager Console")
14 | {
15 | # user is installing from VS NuGet console
16 | # get reference to the window, the console host and the input history
17 | # show webpage if "install-package YourPackageName" was last input
18 |
19 | $consoleWindow = $(Get-VSComponentModel).GetService([NuGetConsole.IPowerConsoleWindow])
20 |
21 | $props = $consoleWindow.GetType().GetProperties([System.Reflection.BindingFlags]::Instance -bor `
22 | [System.Reflection.BindingFlags]::NonPublic)
23 |
24 | $prop = $props | ? { $_.Name -eq "ActiveHostInfo" } | select -first 1
25 | if ($prop -eq $null) { return }
26 |
27 | $hostInfo = $prop.GetValue($consoleWindow)
28 | if ($hostInfo -eq $null) { return }
29 |
30 | $history = $hostInfo.WpfConsole.InputHistory.History
31 |
32 | $lastCommand = $history | select -last 1
33 |
34 | if ($lastCommand)
35 | {
36 | $lastCommand = $lastCommand.Trim().ToLower()
37 | if ($lastCommand.StartsWith("install-package") -and $lastCommand.Contains($packageName))
38 | {
39 | $dte2.ItemOperations.Navigate($url) | Out-Null
40 | }
41 | }
42 | }
43 | else
44 | {
45 | # user is installing from VS NuGet dialog
46 | # get reference to the window, then smart output console provider
47 | # show webpage if messages in buffered console contains "installing...YourPackageName" in last operation
48 |
49 | $instanceField = [NuGet.Dialog.PackageManagerWindow].GetField("CurrentInstance", [System.Reflection.BindingFlags]::Static -bor `
50 | [System.Reflection.BindingFlags]::NonPublic)
51 | $consoleField = [NuGet.Dialog.PackageManagerWindow].GetField("_smartOutputConsoleProvider", [System.Reflection.BindingFlags]::Instance -bor `
52 | [System.Reflection.BindingFlags]::NonPublic)
53 | if ($instanceField -eq $null -or $consoleField -eq $null) { return }
54 |
55 | $instance = $instanceField.GetValue($null)
56 | if ($instance -eq $null) { return }
57 |
58 | $consoleProvider = $consoleField.GetValue($instance)
59 | if ($consoleProvider -eq $null) { return }
60 |
61 | $console = $consoleProvider.CreateOutputConsole($false)
62 |
63 | $messagesField = $console.GetType().GetField("_messages", [System.Reflection.BindingFlags]::Instance -bor `
64 | [System.Reflection.BindingFlags]::NonPublic)
65 | if ($messagesField -eq $null) { return }
66 |
67 | $messages = $messagesField.GetValue($console)
68 | if ($messages -eq $null) { return }
69 |
70 | $operations = $messages -split "=============================="
71 |
72 | $lastOperation = $operations | select -last 1
73 |
74 | if ($lastOperation)
75 | {
76 | $lastOperation = $lastOperation.ToLower()
77 |
78 | $lines = $lastOperation -split "`r`n"
79 |
80 | $installMatch = $lines | ? { $_.StartsWith("------- installing..." + $packageName) } | select -first 1
81 |
82 | if ($installMatch)
83 | {
84 | $dte2.ItemOperations.Navigate($url) | Out-Null
85 | }
86 | }
87 | }
88 | }
89 | catch
90 | {
91 | # stop potential errors from bubbling up
92 | # worst case the splash page won't open
93 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Slapper.AutoMapper
2 | =================
3 | *Slap your data into submission.*
4 |
5 | Slapper.AutoMapper maps dynamic data to static types.
6 |
7 |
8 |
9 |
10 | ### What is it? ###
11 |
12 | Slapper.AutoMapper ( Pronounced Slap-er dot aw-toe-map-er ) is a mapping library that can convert dynamic data into
13 | static types and populate complex nested child objects.
14 |
15 | It primarily converts C# dynamics and `IDictionary` to strongly typed objects and supports
16 | populating an entire object graph by using underscore notation to underscore into nested objects.
17 |
18 | Why use an IDictionary? Because a C# dynamic ( well really an ExpandoObject ) can easily be cast to an `IDictionary` allowing
19 | this library to be used in a variety of ways not only with dictionaries of property names and values but with dynamics as well.
20 |
21 | Okay, so what... doesn't other ORMs do this?
22 |
23 | Answer: Yes and no but the philosophy of this project is much different. This small library is meant to be used as a
24 | building block in a larger solution and puts a great emphasis on its ability to map to complex nested properties such as mapping
25 | a Customer and it's list of Orders and it's list of OrderDetails.
26 |
27 | ### Is this an ORM? ###
28 |
29 | No, this is not an ORM but can be easily extended to create one. This library can be thought of as a building
30 | block of an ORM or used as an extension to an existing ORM or Micro-ORM.
31 |
32 | ORMs typically query the database and then map the data into objects. Slapper handles the mapping part and essentially
33 | only has one input: a dictionary of property names and values.
34 |
35 | ### What problems does this solve? ###
36 |
37 | Simply put, it allows you to convert dynamic data into strongly typed objects with ease and populating complex nested child
38 | objects in your object hierarchy comes for free out of the box --something severely lacking in almost every Micro-ORM solution!
39 |
40 | ### Auto mapping? ###
41 |
42 | Yep, Slapper.AutoMapper stays true to its name and allows auto-mapping between dynamic data and static types by using
43 | conventions to find a given classes identifier ( the property that gives the class uniqueness ). This allows Slapper to
44 | figure out how to effectively group objects together so that you do not get duplicate results. You can even supply your
45 | own conventions or manually specify the identifiers by either calling a simple API method or decorating your types with
46 | an attribute.
47 |
48 | And yes, multiple identifiers aka Composite Primary Keys are supported out of the box!
49 |
50 | ### Some more ramblings... ###
51 |
52 | Micro-ORMs have been springing up left and right but many of them are quite basic in their functionality. Many have also
53 | been opting for either very basic mapping to strongly typed objects or skipping it completely and opting for a completely
54 | dynamic solution.
55 |
56 | Dynamics are super cool and have their place but strongly typed objects have their place too and that is what this library
57 | focuses on... converting dynamic data into strongly typed objects with strong support for populating nested child properties.
58 |
59 | ### Target Audience ###
60 |
61 | The target audience is C# developers looking to enhance an ORM or write their own. Slapper.AutoMapper
62 | can take care of a lot of the hard work of mapping back to strongly typed objects.
63 |
64 | Because Slapper.AutoMappers primary input is simply a dictionary of property names and values, as long as you can get your data
65 | into that form, you're good to go. One thing to note is that the values must be the same data types as the strongly typed object's properties/fields
66 | you are wishing to populate. Slapper.AutoMapper does not handle data type conversions, that is up to you the consumer to feed the proper
67 | data into the library.
68 |
69 | And that's it, feel free to explore the examples below and the unit tests and hack away. This library is licensed with the MIT license
70 | so feel free to re-use the code in your own projects any way you please as long as you provide proper attribution.
71 |
72 | Slapper.AutoMapper is also available on NuGet available here: http://www.nuget.org/packages/Slapper.AutoMapper/
73 |
74 | Now let the slapping commence! :)
75 |
76 |
77 | Usage - Mapping
78 | ===============
79 |
80 | ### Simple Example Using a Dictionary ###
81 |
82 | The following simple example maps a dictionary of property names and values to a Person class.
83 |
84 | ```csharp
85 | public class Person
86 | {
87 | public int Id;
88 | public string FirstName;
89 | public string LastName;
90 | }
91 |
92 | [Test]
93 | public void Can_Map_Matching_Field_Names_With_Ease()
94 | {
95 | // Arrange
96 | var dictionary = new Dictionary
97 | {
98 | { "Id", 1 },
99 | { "FirstName", "Clark" },
100 | { "LastName", "Kent" }
101 | };
102 |
103 | // Act
104 | var person = Slapper.AutoMapper.Map( dictionary );
105 |
106 | // Assert
107 | Assert.NotNull( person );
108 | Assert.That( person.Id == 1 );
109 | Assert.That( person.FirstName == "Clark" );
110 | Assert.That( person.LastName == "Kent" );
111 | }
112 | ```
113 |
114 | ### Simple Example Using Dynamic ###
115 |
116 | The following simple example maps a dynamic object to a Person class.
117 |
118 | When mapping dynamics use the `MapDynamic()` method instead of the `Map()` method.
119 |
120 | ```csharp
121 | public class Person
122 | {
123 | public int Id;
124 | public string FirstName;
125 | public string LastName;
126 | }
127 |
128 | [Test]
129 | public void Can_Map_Matching_Field_Names_Using_Dynamic()
130 | {
131 | // Arrange
132 | dynamic dynamicPerson = new ExpandoObject();
133 | dynamicPerson.Id = 1;
134 | dynamicPerson.FirstName = "Clark";
135 | dynamicPerson.LastName = "Kent";
136 |
137 | // Act
138 | var person = Slapper.AutoMapper.MapDynamic( dynamicPerson ) as Person;
139 |
140 | // Assert
141 | Assert.NotNull( person );
142 | Assert.That( person.Id == 1 );
143 | Assert.That( person.FirstName == "Clark" );
144 | Assert.That( person.LastName == "Kent" );
145 | }
146 | ```
147 |
148 | ### Mapping Nested Types Using a Dictionary ###
149 |
150 | The following example maps a list of dictionaries of property names and values to a Customer class and using underscore notation ("_"),
151 | Slapper.AutoMapper properly populates the nested child types. This is really what I would consider this library's secret sauce.
152 | You can just as easily use a list of dynamics which is demonstrated below too which is what is typically returned back from Micro ORMs.
153 |
154 | As an example, the following SQL would return similar results to what is in the dictionaries in the example below ( Note the use of SQL aliases ).
155 |
156 | *Now it may not seem immediately obvious but what we are really achieving here is something very interesting... we are effectively combining
157 | SQL and the mapping to C# objects at the same time by use of SQL aliases.*
158 |
159 | ```sql
160 | SELECT c.CustomerId,
161 | c.FirstName,
162 | c.LastName,
163 | o.OrderId AS Orders_OrderId,
164 | o.OrderTotal AS Orders_OrderTotal,
165 | od.OrderDetailId AS Orders_OrderDetails_OrderId,
166 | od.OrderDetailId AS Orders_OrderDetails_OrderDetailId,
167 | od.OrderDetailTotal AS Orders_OrderDetails_OrderDetailTotal
168 | FROM Customer c
169 | JOIN Order o ON c.CustomerId = o.CustomerId
170 | JOIN OrderDetail od ON o.OrderId = od.OrderId
171 | ```
172 |
173 | This example is indicative of the results you would commonly encounter when querying a database and joining on an Orders
174 | and OrderDetails table --you would get back duplicate results in some fields. Notice how the CustomerId in both dictionaries
175 | are the same. Because of Slapper.AutoMapper's default conventions, it will identify the CustomerId field as being the
176 | identifier ( or primary key so to speak ). This means that when it attempts to convert the second dictionary to a Customer
177 | object, it will see that it has already created a Customer object with a CustomerId of 1 and will simply re-use the previous
178 | instance resulting in only one Customer object being returned back. This is how Slapper.AutoMapper effectively groups results
179 | together and is the key to this library's awesomeness.
180 |
181 |
182 | ```csharp
183 | public class Customer
184 | {
185 | public int CustomerId;
186 | public string FirstName;
187 | public string LastName;
188 | public IList Orders;
189 | }
190 |
191 | public class Order
192 | {
193 | public int OrderId;
194 | public decimal OrderTotal;
195 | public IList OrderDetails;
196 | }
197 |
198 | public class OrderDetail
199 | {
200 | public int OrderDetailId;
201 | public decimal OrderDetailTotal;
202 | }
203 |
204 | [Test]
205 | public void I_Can_Map_Nested_Types_And_Resolve_Duplicate_Entries_Properly()
206 | {
207 | // Arrange
208 | var dictionary = new Dictionary
209 | {
210 | { "CustomerId", 1 },
211 | { "FirstName", "Bob" },
212 | { "LastName", "Smith" },
213 | { "Orders_OrderId", 1 },
214 | { "Orders_OrderTotal", 50.50m },
215 | { "Orders_OrderDetails_OrderDetailId", 1 },
216 | { "Orders_OrderDetails_OrderDetailTotal", 25.00m }
217 | };
218 |
219 | var dictionary2 = new Dictionary
220 | {
221 | { "CustomerId", 1 },
222 | { "FirstName", "Bob" },
223 | { "LastName", "Smith" },
224 | { "Orders_OrderId", 1 },
225 | { "Orders_OrderTotal", 50.50m },
226 | { "Orders_OrderDetails_OrderDetailId", 2 },
227 | { "Orders_OrderDetails_OrderDetailTotal", 25.50m }
228 | };
229 |
230 | var list = new List> { dictionary, dictionary2 };
231 |
232 | // Act
233 | var customers = Slapper.AutoMapper.Map( list );
234 |
235 | // Assert
236 |
237 | // There should only be a single customer
238 | Assert.That( customers.Count() == 1 );
239 |
240 | // There should only be a single Order
241 | Assert.That( customers.FirstOrDefault().Orders.Count == 1 );
242 |
243 | // There should be two OrderDetails
244 | Assert.That( customers.FirstOrDefault().Orders.FirstOrDefault().OrderDetails.Count == 2 );
245 | }
246 |
247 | [Test]
248 | public void I_Can_Map_Nested_Types_And_Resolve_Duplicate_Entries_Properly_Using_Dynamics()
249 | {
250 | // Arrange
251 | dynamic customer1 = new ExpandoObject();
252 | customer1.CustomerId = 1;
253 | customer1.FirstName = "Bob";
254 | customer1.LastName = "Smith";
255 | customer1.Orders_OrderId = 1;
256 | customer1.Orders_OrderTotal = 50.50m;
257 | customer1.Orders_OrderDetails_OrderDetailId = 1;
258 | customer1.Orders_OrderDetails_OrderDetailTotal = 25.00m;
259 |
260 | dynamic customer2 = new ExpandoObject();
261 | customer2.CustomerId = 1;
262 | customer2.FirstName = "Bob";
263 | customer2.LastName = "Smith";
264 | customer2.Orders_OrderId = 1;
265 | customer2.Orders_OrderTotal = 50.50m;
266 | customer2.Orders_OrderDetails_OrderDetailId = 2;
267 | customer2.Orders_OrderDetails_OrderDetailTotal = 25.50m;
268 |
269 | var customerList = new List { customer1, customer2 };
270 |
271 | // Act
272 | var customers = Slapper.AutoMapper.MapDynamic( customerList );
273 |
274 | // Assert
275 |
276 | // There should only be a single customer
277 | Assert.That( customers.Count() == 1 );
278 |
279 | // There should only be a single Order
280 | Assert.That( customers.FirstOrDefault().Orders.Count == 1 );
281 |
282 | // There should be two OrderDetails
283 | Assert.That( customers.FirstOrDefault().Orders.FirstOrDefault().OrderDetails.Count == 2 );
284 | }
285 | ```
286 |
287 | Usage - Auto Mapping and Identifiers
288 | ====================================
289 |
290 | ### Auto Mapping ###
291 |
292 | Auto mapping allows Slapper to figure out how to effectively group objects together so that you do not get
293 | duplicate results. Now internally, no actual grouping is happening but this is the easiest way to conceptualize
294 | how it works.
295 |
296 | *For the curious, the actual implementation relies upon an instance cache implemented as a Dictionary where the key is all of
297 | the identifier's hashes summed together and the value is the instance.*
298 |
299 | A class' identifier(s) play an important role in the ability of the mapper to effectively group objects together. If no
300 | identifiers are found, the mapper will still map the results to the requested type but there will be duplicates in the results.
301 |
302 |
303 | #### Default Convention ####
304 | Slapper.AutoMapper uses three different conventions in an attempt to locate/match a requested types
305 | identifier:
306 | - Id
307 | - TypeName + Id
308 | - TypeName + Nbr
309 |
310 | For example, if your Customer object has any of the following properties or fields, it will use it as the identifier:
311 | - Id
312 | - CustomerId
313 | - CustomerNbr
314 |
315 | #### Creating Your Own Convention ####
316 |
317 | You can specify your own conventions very easily. The following example creates a convention of TypeName + _Id:
318 |
319 | ```csharp
320 | Slapper.AutoMapper.Configuration.IdentifierConventions.Add( type => type.Name + "_Id" );
321 | ````
322 |
323 | #### Manually Specifying the Identifier(s) ####
324 |
325 | Slapper allows you to manually specify a classes identifiers. 1 through N number of identifiers are supported.
326 |
327 | The following example specifies two identifiers for the Customer object by using the AddIdentifiers() method:
328 |
329 |
330 | ```csharp
331 | public class Customer
332 | {
333 | public int CustomerId;
334 |
335 | public string CustomerType;
336 |
337 | public string FirstName;
338 |
339 | public string LastName;
340 | }
341 |
342 | Slapper.AutoMapper.Configuration.AddIdentifiers( typeof( Customer ), new List { "CustomerId", "CustomerType" } );
343 | ````
344 |
345 | #### Attribute-based Identifiers ####
346 |
347 | Slapper.AutoMapper also supports attribute-based identifiers.
348 |
349 | By default, the library uses its own Id attribute that allows you to simply decorate the identifiers on your class with
350 | a `[Slapper.AutoMapper.Id]` attribute.
351 |
352 | If you wish to use your own attribute instead of the default one, just set the Type to use on the following field:
353 |
354 | ```csharp
355 | Slapper.AutoMapper.Configuration.IdentifierAttributeType = typeof( YourCustomAttribute );
356 | ```
357 |
358 | The following example specifies two identifiers for the Customer object:
359 |
360 | ```csharp
361 | public class Customer
362 | {
363 | [Slapper.AutoMapper.Id]
364 | public int CustomerId;
365 |
366 | [Slapper.AutoMapper.Id]
367 | public string CustomerType;
368 |
369 | public string FirstName;
370 |
371 | public string LastName;
372 | }
373 | ````
374 |
375 | Usage - Caching
376 | ===============
377 |
378 | #### Caching Explained ####
379 |
380 | Slapper.AutoMapper internally maintains a cache of every object it creates, referred to as the instance cache.
381 | This cache plays an important role in Slapper's ability to easily look up existing objects and ultimately assists
382 | in the ability for Slapper.AutoMapper to populate complex nested types.
383 |
384 | Slapper.AutoMapper itself never removes an instance from this cache, so if you tell it to create 50,000 objects,
385 | then there are going to be 50,000 objects in the cache for the lifetime of the current thread or HttpContext.
386 |
387 | The instance cache exists for the lifetime of the current thread and each of your application's threads will
388 | get its own unique cache making use of this library thread safe.
389 |
390 | #### Cache Backing Store ####
391 |
392 | The instance cache backing store will either use the HttpContext if one exists or the CallContext of the
393 | executing thread. The library uses reflection to persist the cache in the HttpContext when
394 | necessary so that the library does not require a dependency on the System.Web library.
395 |
396 | #### Clearing the Cache ###
397 |
398 | Slapper never clears the cache because we feel that it should be the consumer of this library that should have that
399 | responsibility.
400 |
401 | If you would like to clear this cache, you can do so at any time like so:
402 |
403 | ```csharp
404 | Slapper.AutoMapper.Cache.ClearInstanceCache();
405 | ````
406 |
407 |
408 | ### License ###
409 |
410 | MIT License:
411 |
412 | Copyright (c) 2016, Randy Burden and contributors.
413 | All rights reserved.
414 |
415 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
416 | associated documentation files (the "Software"), to deal in the Software without restriction, including
417 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
418 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
419 | following conditions:
420 |
421 | The above copyright notice and this permission notice shall be included in all copies or substantial
422 | portions of the Software.
423 |
424 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
425 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
426 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
427 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
428 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
429 |
430 | Description:
431 |
432 | Slapper.AutoMapper maps dynamic data to static types. Slap your data into submission!
433 |
434 | Slapper.AutoMapper ( Pronounced Slapper-Dot-Automapper ) is a mapping library that can convert
435 | dynamic data into static types and populate complex nested child objects.
436 | It primarily converts C# dynamics and IDictionary to strongly typed objects and supports
437 | populating an entire object graph by using underscore notation to underscore into nested objects.
438 |
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/ArrayTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 |
4 | namespace Slapper.Tests
5 | {
6 | [TestFixture]
7 | public class ArrayTests : TestBase
8 | {
9 | public class PersonWithFields
10 | {
11 | public int Id;
12 | public string FirstName;
13 | public string LastName;
14 | public string[] FavoriteFoods;
15 | }
16 |
17 | public class PersonWithProperties
18 | {
19 | public int Id { get; set; }
20 | public string FirstName { get; set; }
21 | public string LastName { get; set; }
22 | public string[] FavoriteFoods { get; set; }
23 | }
24 |
25 | [Test]
26 | public void Can_Map_Null_Values_To_Null_Arrays()
27 | {
28 | // Arrange
29 | const int id = 1;
30 | const string firstName = null;
31 | const string lastName = "Smith";
32 | const string[] favoriteFoods = null;
33 |
34 | var dictionary = new Dictionary
35 | {
36 | { "Id", id },
37 | { "FirstName", null },
38 | { "LastName", lastName },
39 | { "FavoriteFoods", favoriteFoods }
40 | };
41 |
42 | // Act
43 | var customer = Slapper.AutoMapper.Map( dictionary );
44 |
45 | // Assert
46 | Assert.NotNull( customer );
47 | Assert.That( customer.Id == id );
48 | Assert.That( customer.FirstName == firstName );
49 | Assert.That( customer.LastName == lastName );
50 | Assert.Null( customer.FavoriteFoods );
51 | }
52 |
53 | [Test]
54 | public void Can_Map_Array_Values_To_Arrays()
55 | {
56 | // Arrange
57 | const int id = 1;
58 | const string firstName = null;
59 | const string lastName = "Smith";
60 | string[] favoriteFoods = new [] { "Ice Cream", "Jello" };
61 |
62 | var dictionary = new Dictionary
63 | {
64 | { "Id", id },
65 | { "FirstName", null },
66 | { "LastName", lastName },
67 | { "FavoriteFoods", favoriteFoods }
68 | };
69 |
70 | // Act
71 | var customer = Slapper.AutoMapper.Map( dictionary );
72 |
73 | // Assert
74 | Assert.NotNull( customer );
75 | Assert.That( customer.Id == id );
76 | Assert.That( customer.FirstName == firstName );
77 | Assert.That( customer.LastName == lastName );
78 | Assert.That( customer.FavoriteFoods == favoriteFoods );
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/CachingBehaviorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 |
4 | // ReSharper disable InconsistentNaming
5 |
6 | namespace Slapper.Tests
7 | {
8 | using System;
9 | using System.Linq;
10 |
11 | [TestFixture]
12 | public class CachingBehaviorTests : TestBase
13 | {
14 | public class Customer
15 | {
16 | public int CustomerId;
17 |
18 | public string FirstName;
19 |
20 | public string LastName;
21 | }
22 |
23 | public class Employee
24 | {
25 | public int Id { get; set; }
26 |
27 | public string Name { get; set; }
28 |
29 | public Department Department { get; set; }
30 | }
31 |
32 | public class Department
33 | {
34 | public int Id { get; set; }
35 |
36 | public string Name { get; set; }
37 | }
38 |
39 | public class Order
40 | {
41 | public int Id { get; set; }
42 | public List OrderItems { get; set; }
43 | }
44 |
45 | public class OrderItem
46 | {
47 | public int Id { get; set; }
48 | }
49 |
50 |
51 | public class OrderWithLongId
52 | {
53 | public long Id { get; set; }
54 | public List OrderItems { get; set; }
55 | }
56 |
57 | [Test]
58 | public void Previously_Instantiated_Objects_Will_Be_Returned_Until_The_Cache_Is_Cleared()
59 | {
60 | // Arrange
61 | var dictionary = new Dictionary
62 | {
63 | { "CustomerId", 1 },
64 | { "FirstName", "Bob" },
65 | { "LastName", "Smith" }
66 | };
67 |
68 | // Act
69 | var customer = Slapper.AutoMapper.Map(dictionary);
70 |
71 | // Assert
72 | Assert.AreEqual("Bob", customer.FirstName);
73 |
74 | // Arrange
75 | var dictionary2 = new Dictionary { { "CustomerId", 1 } };
76 |
77 | // Act
78 | var customer2 = Slapper.AutoMapper.Map(dictionary2);
79 |
80 | // Assert that this will be "Bob" because the identifier of the Customer object was the same,
81 | // so we recieved back the cached instance of the Customer object.
82 | Assert.AreEqual("Bob", customer2.FirstName);
83 |
84 | // Arrange
85 | var dictionary3 = new Dictionary { { "CustomerId", 1 } };
86 |
87 | Slapper.AutoMapper.Cache.ClearInstanceCache();
88 |
89 | // Act
90 | var customer3 = Slapper.AutoMapper.Map(dictionary3);
91 |
92 | // Assert
93 | Assert.Null(customer3.FirstName);
94 | }
95 |
96 | [Test]
97 | public void Test_Nested_Duplicate_Instances()
98 | {
99 | var item1 = new Dictionary()
100 | {
101 | { "Id", 1 },
102 | { "Name", "Employee1" },
103 | { "Department_Id", 1 },
104 | { "Department_Name", "Department1" }
105 | };
106 |
107 | var item2 = new Dictionary()
108 | {
109 | { "Id", 2 },
110 | { "Name", "Employee2" },
111 | { "Department_Id", 1 },
112 | { "Department_Name", "Department1" }
113 | };
114 |
115 | var list = new List>() { item1, item2 };
116 | var employeeList = AutoMapper.Map(list).ToList();
117 |
118 | Assert.AreSame(employeeList[0].Department, employeeList[1].Department);
119 | }
120 |
121 | [Test]
122 | public void Test_Long_Ids_With_Colliding_HashValues()
123 | {
124 | // This test could fail if MS GetHashCode implementation for long changed. We would then have to find new long values
125 | // having the same hashcode.
126 | const long longId1 = 95988224123597;
127 | var item1 = new Dictionary()
128 | {
129 | { "Id", longId1 },
130 | { "OrderDetail_Id", 1 }
131 | };
132 |
133 | const long longId2 = 95983929156300;
134 | var item2 = new Dictionary()
135 | {
136 | { "Id", longId2 },
137 | { "OrderDetail_Id", 2 }
138 | };
139 |
140 | var list = new List>() { item1, item2 };
141 | var orderList = AutoMapper.Map(list).ToList();
142 |
143 | Assert.AreEqual(longId1.GetHashCode(), longId2.GetHashCode());
144 | Assert.AreEqual(orderList.Count, list.Count);
145 | }
146 |
147 | [Test]
148 | public void Cache_is_cleared_if_KeepCache_is_false()
149 | {
150 | var item1 = new Dictionary {
151 | { "Id", 1 },
152 | { "OrderItems_Id", 1 }
153 | };
154 |
155 | var item2 = new Dictionary {
156 | { "Id", 1 },
157 | { "OrderItems_Id", 2 }
158 | };
159 |
160 | var firstResult = AutoMapper.Map(item1, false);
161 | var secondResult = AutoMapper.Map(item2, false);
162 |
163 | Assert.AreEqual(1, firstResult.OrderItems.Count);
164 | Assert.AreEqual(1, firstResult.OrderItems[0].Id);
165 | Assert.AreEqual(1, secondResult.OrderItems.Count);
166 | Assert.AreEqual(2, secondResult.OrderItems[0].Id);
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/ComplexMapTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using NUnit.Framework;
4 |
5 | // ReSharper disable InconsistentNaming, RedundantNameQualifier
6 | namespace Slapper.Tests
7 | {
8 | [TestFixture]
9 | public class ComplexMapTests : TestBase
10 | {
11 | public class Customer
12 | {
13 | public int CustomerId;
14 | public string FirstName;
15 | public string LastName;
16 | public IList Orders;
17 | }
18 |
19 | public class Order
20 | {
21 | public int OrderId;
22 | public decimal OrderTotal;
23 | public IList OrderDetails;
24 | }
25 |
26 | public class OrderDetail
27 | {
28 | public int OrderDetailId;
29 | public decimal OrderDetailTotal;
30 | }
31 |
32 | public class MapTestModels
33 | {
34 | public class CustomerWithMultipleIdAttributes
35 | {
36 | [Slapper.AutoMapper.Id]
37 | public int Customer_Id;
38 |
39 | [Slapper.AutoMapper.Id]
40 | public string Customer_Type;
41 |
42 | public string FirstName;
43 |
44 | public string LastName;
45 |
46 | public List Orders;
47 | }
48 |
49 | public class CustomerWithOrdersList
50 | {
51 | public int Id;
52 | public string FirstName;
53 | public string LastName;
54 | public List Orders;
55 | }
56 |
57 | public class CustomerWithAnIEnumerableOrdersCollection
58 | {
59 | public int Id;
60 | public string FirstName;
61 | public string LastName;
62 | public IEnumerable Orders;
63 | }
64 |
65 | public class Order
66 | {
67 | public int Id;
68 | public decimal OrderTotal;
69 | public IList OrderDetails;
70 | }
71 |
72 | public class OrderDetail
73 | {
74 | public int Id;
75 | public decimal OrderDetailTotal;
76 | public Product Product;
77 | }
78 |
79 | public class Product
80 | {
81 | public int Id;
82 | public string ProductName;
83 | }
84 | }
85 |
86 | [Test]
87 | public void Can_Map_Complex_Nested_Members()
88 | {
89 | // Arrange
90 | const int id = 1;
91 | const string firstName = "Bob";
92 | const string lastName = "Smith";
93 | const int orderId = 1;
94 | const decimal orderTotal = 50.50m;
95 |
96 | var dictionary = new Dictionary
97 | {
98 | { "Id", id },
99 | { "FirstName", firstName },
100 | { "LastName", lastName },
101 | { "Orders_Id", orderId },
102 | { "Orders_OrderTotal", orderTotal }
103 | };
104 |
105 | // Act
106 | var customer = Slapper.AutoMapper.Map( dictionary );
107 |
108 | // Assert
109 | Assert.NotNull( customer );
110 | Assert.That( customer.Id == id );
111 | Assert.That( customer.FirstName == firstName );
112 | Assert.That( customer.LastName == lastName );
113 | Assert.NotNull( customer.Orders );
114 | Assert.That( customer.Orders.Count == 1 );
115 | Assert.That( customer.Orders.First().Id == orderId );
116 | Assert.That( customer.Orders.First().OrderTotal == orderTotal );
117 | }
118 |
119 | ///
120 | /// OLD SUMMARY ===
121 | /// When mapping, it internally keeps a cache of instantiated objects with the key being the
122 | /// hash of the objects identifier hashes summed together so when another record with the exact
123 | /// same identifier hash is detected, it will re-use the existing instantiated object instead of
124 | /// creating a second one alleviating the burden of the consumer of the library to group objects
125 | /// by their identifier.
126 | /// ===
127 | /// This was flawed as SAME HASHCODE DOESN'T MEAN SAME VALUE. Hash collisions would lead to
128 | /// wrongly reusing an instance instead of creating a new one (issue #48).
129 | /// It's now fixed as real identifier values are compared, not their hashes anymore.
130 | ///
131 | [Test]
132 | public void Can_Detect_Duplicate_Parent_Members_And_Properly_Instantiate_The_Object_Only_Once()
133 | {
134 | // Arrange
135 | const int id = 1;
136 | const string firstName = "Bob";
137 | const string lastName = "Smith";
138 | const int orderId = 1;
139 | const decimal orderTotal = 50.50m;
140 |
141 | var dictionary = new Dictionary
142 | {
143 | { "Id", id },
144 | { "FirstName", firstName },
145 | { "LastName", lastName },
146 | { "Orders_Id", orderId },
147 | { "Orders_OrderTotal", orderTotal }
148 | };
149 |
150 | var dictionary2 = new Dictionary
151 | {
152 | { "Id", id },
153 | { "FirstName", firstName },
154 | { "LastName", lastName },
155 | { "Orders_Id", orderId + 1 },
156 | { "Orders_OrderTotal", orderTotal + 1 }
157 | };
158 |
159 | var listOfDictionaries = new List> { dictionary, dictionary2 };
160 |
161 | // Act
162 | var customers = Slapper.AutoMapper.Map( listOfDictionaries );
163 |
164 | var customer = customers.FirstOrDefault();
165 |
166 | // Assert
167 | Assert.That( customers.Count() == 1 );
168 | Assert.NotNull( customer );
169 | Assert.That( customer.Id == id );
170 | Assert.That( customer.FirstName == firstName );
171 | Assert.That( customer.LastName == lastName );
172 | Assert.NotNull( customer.Orders );
173 | Assert.That( customer.Orders.Count == 2 );
174 | Assert.That( customer.Orders[ 0 ].Id == orderId );
175 | Assert.That( customer.Orders[ 0 ].OrderTotal == orderTotal );
176 | Assert.That( customer.Orders[ 1 ].Id == orderId + 1 );
177 | Assert.That( customer.Orders[ 1 ].OrderTotal == orderTotal + 1 );
178 | }
179 |
180 | [Test]
181 | public void Can_Handle_Nested_Members_That_Implements_ICollection()
182 | {
183 | // Arrange
184 | const int id = 1;
185 | const string firstName = "Bob";
186 | const string lastName = "Smith";
187 | const int orderId = 1;
188 | const decimal orderTotal = 50.50m;
189 |
190 | var dictionary = new Dictionary
191 | {
192 | { "Id", id },
193 | { "FirstName", firstName },
194 | { "LastName", lastName },
195 | { "Orders_Id", orderId },
196 | { "Orders_OrderTotal", orderTotal }
197 | };
198 |
199 | var dictionary2 = new Dictionary
200 | {
201 | { "Id", id },
202 | { "FirstName", firstName },
203 | { "LastName", lastName },
204 | { "Orders_Id", orderId + 1 },
205 | { "Orders_OrderTotal", orderTotal + 1 }
206 | };
207 |
208 | var listOfDictionaries = new List> { dictionary, dictionary2 };
209 |
210 | // Act
211 | var customers = Slapper.AutoMapper.Map( listOfDictionaries );
212 |
213 | var customer = customers.FirstOrDefault();
214 |
215 | // Assert
216 | Assert.That( customer.Orders.Count() == 2 );
217 | }
218 |
219 | [Test]
220 | public void Can_Handle_Mapping_Objects_With_Multiple_Identifiers()
221 | {
222 | // Arrange
223 | const int customerId = 1;
224 | const string customerType = "Commercial";
225 | const string firstName = "Bob";
226 | const string lastName = "Smith";
227 | const int orderId = 1;
228 | const decimal orderTotal = 50.50m;
229 |
230 | var dictionary = new Dictionary
231 | {
232 | { "Customer_Id", customerId },
233 | { "Customer_Type", customerType },
234 | { "FirstName", firstName },
235 | { "LastName", lastName },
236 | { "Orders_Id", orderId },
237 | { "Orders_OrderTotal", orderTotal }
238 | };
239 |
240 | var dictionary2 = new Dictionary
241 | {
242 | { "Customer_Id", customerId },
243 | { "Customer_Type", customerType },
244 | { "FirstName", firstName },
245 | { "LastName", lastName },
246 | { "Orders_Id", orderId + 1 },
247 | { "Orders_OrderTotal", orderTotal + 1 }
248 | };
249 |
250 | var dictionary3 = new Dictionary
251 | {
252 | { "Customer_Id", customerId + 1 },
253 | { "Customer_Type", customerType },
254 | { "FirstName", firstName },
255 | { "LastName", lastName },
256 | { "Orders_Id", orderId + 1 },
257 | { "Orders_OrderTotal", orderTotal + 1 }
258 | };
259 |
260 | var listOfDictionaries = new List> { dictionary, dictionary2, dictionary3 };
261 |
262 | // Act
263 | var customers = Slapper.AutoMapper.Map( listOfDictionaries );
264 |
265 | // Assert
266 | Assert.That( customers.Count() == 2 );
267 | Assert.That( customers.First().Orders.Count == 2 );
268 | Assert.That( customers.ToList()[ 1 ].Orders.First().Id == orderId + 1 );
269 | }
270 |
271 | [Test]
272 | public void Can_Map_To_Multiple_Objects()
273 | {
274 | // Arrange
275 | var dictionary = new Dictionary
276 | {
277 | { "Id", 1 },
278 | { "FirstName", "Bob" },
279 | { "LastName", "Smith" },
280 | { "Orders_Id", 1 },
281 | { "Orders_OrderTotal", 50.50m }
282 | };
283 |
284 | var dictionary2 = new Dictionary
285 | {
286 | { "Id", 2 },
287 | { "FirstName", "Jane" },
288 | { "LastName", "Doe" },
289 | { "Orders_Id", 2 },
290 | { "Orders_OrderTotal", 23.40m }
291 | };
292 |
293 | var listOfDictionaries = new List> { dictionary, dictionary2 };
294 |
295 | // Act
296 | var customers = Slapper.AutoMapper.Map( listOfDictionaries );
297 |
298 | // Assert
299 | Assert.That( customers.Count() == 2 );
300 | Assert.That( customers.ToList()[ 0 ].FirstName == "Bob" );
301 | Assert.That( customers.ToList()[ 1 ].FirstName == "Jane" );
302 | }
303 |
304 | [Test]
305 | public void Can_Handle_Mapping_Deeply_Nested_Members()
306 | {
307 | // Arrange
308 | var dictionary = new Dictionary
309 | {
310 | { "Id", 1 },
311 | { "FirstName", "Bob" },
312 | { "LastName", "Smith" },
313 | { "Orders_Id", 1 },
314 | { "Orders_OrderTotal", 50.50m },
315 | { "Orders_OrderDetails_Id", 1 },
316 | { "Orders_OrderDetails_OrderDetailTotal", 50.50m },
317 | { "Orders_OrderDetails_Product_Id", 546 },
318 | { "Orders_OrderDetails_Product_ProductName", "Black Bookshelf" }
319 | };
320 |
321 | // Act
322 | var customer = Slapper.AutoMapper.Map( dictionary );
323 |
324 | // Assert
325 | Assert.That( customer.Orders.Count() == 1 );
326 | Assert.That( customer.Orders.First().OrderDetails.Count == 1 );
327 | Assert.That( customer.Orders.First().OrderDetails.First().Product.ProductName == "Black Bookshelf" );
328 | }
329 |
330 | [Test]
331 | public void Can_Handle_Resolving_Duplicate_Nested_Members()
332 | {
333 | // Arrange
334 | var dictionary = new Dictionary
335 | {
336 | { "CustomerId", 1 },
337 | { "FirstName", "Bob" },
338 | { "LastName", "Smith" },
339 | { "Orders_OrderId", 1 },
340 | { "Orders_OrderTotal", 50.50m },
341 |
342 | { "Orders_OrderDetails_OrderDetailId", 1 },
343 | { "Orders_OrderDetails_OrderDetailTotal", 25.00m }
344 | };
345 |
346 | var dictionary2 = new Dictionary
347 | {
348 | { "CustomerId", 1 },
349 | { "FirstName", "Bob" },
350 | { "LastName", "Smith" },
351 | { "Orders_OrderId", 1 },
352 | { "Orders_OrderTotal", 50.50m },
353 |
354 | { "Orders_OrderDetails_OrderDetailId", 2 },
355 | { "Orders_OrderDetails_OrderDetailTotal", 25.50m }
356 | };
357 |
358 | var list = new List> { dictionary, dictionary2 };
359 |
360 | // Act
361 | var customers = Slapper.AutoMapper.Map( list );
362 |
363 | // Assert
364 | Assert.That( customers.Count() == 1 );
365 |
366 | // We should only have one Order object
367 | Assert.That( customers.FirstOrDefault().Orders.Count == 1 );
368 |
369 | // We should only have one Order object and two OrderDetail objects
370 | Assert.That( customers.FirstOrDefault().Orders.FirstOrDefault().OrderDetails.Count == 2 );
371 | }
372 | }
373 | }
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/ComplexMapsParentsAndChlidTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Dynamic;
4 | using System.Linq;
5 | using System.Text;
6 | using NUnit.Framework;
7 |
8 | namespace Slapper.Tests
9 | {
10 | [TestFixture]
11 | public class ComplexMapsParentsAndChlidTest: TestBase
12 | {
13 | public class Hotel
14 | {
15 | public int Id { get; set; }
16 | public string Name { get; set; }
17 | }
18 |
19 | public class Tour
20 | {
21 | public int Id { get; set; }
22 | public string Name { get; set; }
23 | }
24 |
25 | public class Service
26 | {
27 | public int Id { get; set; }
28 | public IEnumerable Hotels { get; set; }
29 | public IEnumerable Tours { get; set; }
30 | }
31 |
32 | public class Booking
33 | {
34 | public int Id { get; set; }
35 | public IEnumerable Services { get; set; }
36 | }
37 |
38 | [Test]
39 | public void Can_Make_Cache_HashTypeEquals_With_Different_Parents()
40 | {
41 | var listOfDictionaries = new List>
42 | {
43 | new Dictionary
44 | {
45 | { "Id", 1 },
46 | { "Services_Id", 1 },
47 | { "Services_Hotels_Id", 1 },
48 | { "Services_Hotels_Name", "Hotel 1" }
49 | },
50 | new Dictionary
51 | {
52 | { "Id", 1 },
53 | { "Services_Id", 2 },
54 | { "Services_Hotels_Id", 2 },
55 | { "Services_Hotels_Name", "Hotel 2" }
56 | },
57 | new Dictionary
58 | {
59 | { "Id", 2 },
60 | { "Services_Id", 1 },
61 | { "Services_Hotels_Id", 3 },
62 | { "Services_Hotels_Name", "Hotel 3" }
63 | }
64 | };
65 |
66 | var bookings = AutoMapper.Map(listOfDictionaries).ToList();
67 |
68 | Assert.That(bookings.Count == 2);
69 | Assert.That(bookings[0].Services.Count() == 2);
70 |
71 | Assert.NotNull(bookings[0].Services.SingleOrDefault(s => s.Id == 1));
72 | Assert.That(bookings[0].Services.Single(s => s.Id == 1).Hotels.Count() == 1);
73 | Assert.That(bookings[0].Services.Single(s => s.Id == 2).Hotels.Count() == 1);
74 |
75 | Assert.That(bookings[1].Services.Count() == 1);
76 |
77 | Assert.NotNull(bookings[1].Services.SingleOrDefault(s => s.Id == 1));
78 | Assert.That(bookings[1].Services.Single(s => s.Id == 1).Hotels.Count() == 1);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/EmptyList.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Dynamic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Slapper.Tests
7 | {
8 | [TestFixture]
9 | public class EmptyListe : TestBase
10 | {
11 | public class Customer
12 | {
13 | public int Id;
14 | public string FirstName;
15 | public string LastName;
16 | public IList Orders;
17 | }
18 |
19 | public class Order
20 | {
21 | public int Id;
22 | public decimal OrderTotal;
23 | }
24 |
25 | [Test]
26 | public void Can_Handle_Mapping_An_Empty_List()
27 | {
28 | // Arrange
29 | dynamic dynamicCustomer = new ExpandoObject();
30 | dynamicCustomer.Id = 1;
31 | dynamicCustomer.FirstName = "Bob";
32 | dynamicCustomer.LastName = "Smith";
33 | dynamicCustomer.Orders_Id = null;
34 | dynamicCustomer.Orders_OrderTotal = null;
35 |
36 | // Act
37 | var customer = Slapper.AutoMapper.MapDynamic( dynamicCustomer ) as Customer;
38 |
39 | // Assert
40 | Assert.NotNull( customer );
41 |
42 | // Empty list
43 | Assert.That(customer.Orders != null);
44 | Assert.That(customer.Orders.Count == 0);
45 | }
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/Slapper.AutoMapper.Tests/ExceptionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Slapper.Tests
7 | {
8 | [TestFixture]
9 | public class ExceptionTests : TestBase
10 | {
11 | public class Person
12 | {
13 | public int PersonId;
14 | public string FirstName;
15 | public string LastName;
16 | }
17 |
18 | [Test]
19 | public void Will_Throw_An_Exception_If_The_Type_Is_Not_Dynamic()
20 | {
21 | // Arrange
22 | var someObject = new object();
23 |
24 | // Act
25 | TestDelegate test = () => Slapper.AutoMapper.MapDynamic( someObject );
26 |
27 | // Assert
28 | Assert.Throws( test );
29 | }
30 |
31 | [Test]
32 | public void Will_Not_Throw_An_Exception_If_The_List_Items_Are_Not_Dynamic()
33 | {
34 | // Arrange
35 | var someObjectList = new List