├── .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 | NuGet Version 8 | NuGet Download Count 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 { null }; 36 | 37 | // Act 38 | TestDelegate test = () => Slapper.AutoMapper.MapDynamic( someObjectList ); 39 | 40 | // Assert 41 | Assert.DoesNotThrow( test ); 42 | } 43 | 44 | [Test] 45 | public void Will_Return_An_Empty_List_Of_The_Requested_Type_When_Passed_An_Empty_List() 46 | { 47 | // Arrange 48 | var someObjectList = new List(); 49 | 50 | // Act 51 | var list = Slapper.AutoMapper.MapDynamic( someObjectList ); 52 | 53 | // Assert 54 | Assert.NotNull( list ); 55 | Assert.That( list.Count() == 0 ); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/GuidConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using static Slapper.AutoMapper.Configuration; 4 | 5 | namespace Slapper.Tests.ConvertersTests 6 | { 7 | [TestFixture] 8 | public class GuidConverterTests 9 | { 10 | private readonly GuidConverter converter = new GuidConverter(); 11 | 12 | [TestCase(typeof(Guid))] 13 | [TestCase(typeof(Guid?))] 14 | public void Can_Convert_To_Type(Type targetType) 15 | { 16 | // Act + Assert 17 | Assert.True(this.converter.CanConvert(null, targetType)); // Input value does not matter, null is enough for the test. 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/HashCollisionTests.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 | public class HashCollisionTests 9 | { 10 | [Test] 11 | public void Avoids_Hash_Collisions() 12 | { 13 | // Arrange 14 | var id2 = typeof (Employee).GetHashCode() - typeof (Contract).GetHashCode(); 15 | 16 | var source = new List(); 17 | 18 | dynamic obj1 = new ExpandoObject(); 19 | 20 | obj1.Id = 1; 21 | obj1.Contracts_Id = id2; 22 | 23 | source.Add(obj1); 24 | 25 | dynamic obj2 = new ExpandoObject(); 26 | 27 | obj2.Id = 1; 28 | obj2.Contracts_Id = id2 + 1; 29 | 30 | source.Add(obj2); 31 | 32 | // Act/Assert 33 | var result = AutoMapper.MapDynamic(source).First(); 34 | } 35 | 36 | public class Employee 37 | { 38 | public int Id { get; set; } 39 | 40 | public List Contracts { get; set; } 41 | 42 | public override int GetHashCode() 43 | { 44 | return Id; 45 | } 46 | } 47 | 48 | public class Contract 49 | { 50 | public int Id { get; set; } 51 | 52 | public override int GetHashCode() 53 | { 54 | return Id; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/IdentifierTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | // ReSharper disable InconsistentNaming 6 | namespace Slapper.Tests 7 | { 8 | [TestFixture] 9 | public class IdentifierTests : TestBase 10 | { 11 | public class IdentifierTestModels 12 | { 13 | public class Customer 14 | { 15 | public int Id; 16 | 17 | public string FirstName; 18 | 19 | public string LastName; 20 | } 21 | 22 | public class Person 23 | { 24 | public int Person_Id; 25 | 26 | public string FirstName; 27 | 28 | public string LastName; 29 | } 30 | 31 | public class CustomerWithIdAttribute 32 | { 33 | [Slapper.AutoMapper.Id] 34 | public int CustomerId; 35 | 36 | public string FirstName; 37 | 38 | public string LastName; 39 | } 40 | 41 | public class CustomerWithMultipleIdAttributes 42 | { 43 | [Slapper.AutoMapper.Id] 44 | public int CustomerId; 45 | 46 | [Slapper.AutoMapper.Id] 47 | public int CustomerType; 48 | 49 | public string FirstName; 50 | 51 | public string LastName; 52 | } 53 | } 54 | 55 | [Test] 56 | public void Can_Add_An_Identifier() 57 | { 58 | // Arrange 59 | const string identifier = "FirstName"; 60 | 61 | // Act 62 | Slapper.AutoMapper.Configuration.AddIdentifier( typeof( IdentifierTestModels.Customer ), identifier ); 63 | 64 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.Customer ) ); 65 | 66 | // Assert 67 | Assert.That( identifiers.First() == identifier ); 68 | } 69 | 70 | [Test] 71 | public void Can_Add_Multiple_Identifiers() 72 | { 73 | // Arrange 74 | var identifierList = new List { "FirstName", "LastName" }; 75 | 76 | // Act 77 | Slapper.AutoMapper.Configuration.AddIdentifiers( typeof( IdentifierTestModels.Customer ), identifierList ); 78 | 79 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.Customer ) ); 80 | 81 | // Assert 82 | foreach ( var identifier in identifierList ) 83 | { 84 | Assert.That( identifiers.Contains( identifier ) ); 85 | } 86 | } 87 | 88 | [Test] 89 | public void Can_Use_Default_Conventions_To_Find_An_Identifier() 90 | { 91 | // Act 92 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.Customer ) ); 93 | 94 | //Assert 95 | Assert.That( identifiers.First() == "Id" ); 96 | } 97 | 98 | [Test] 99 | public void Can_Use_A_Custom_Convention_To_Find_An_Identifier() 100 | { 101 | // Act 102 | Slapper.AutoMapper.Configuration.IdentifierConventions.Add( type => type.Name + "_Id" ); 103 | 104 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.Person ) ); 105 | 106 | //Assert 107 | Assert.That( identifiers.First() == "Person_Id" ); 108 | } 109 | 110 | [Test] 111 | public void Can_Find_An_Identifier_When_A_Field_Or_Property_Has_An_Id_Attribute() 112 | { 113 | // Act 114 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.CustomerWithIdAttribute ) ); 115 | 116 | //Assert 117 | Assert.That( identifiers.First() == "CustomerId" ); 118 | } 119 | 120 | [Test] 121 | public void Can_Find_Identifiers_When_Multiple_Fields_Or_Properties_Have_An_Id_Attribute() 122 | { 123 | // Act 124 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( typeof( IdentifierTestModels.CustomerWithMultipleIdAttributes ) ); 125 | 126 | //Assert 127 | Assert.That( identifiers.Contains( "CustomerId" ) && identifiers.Contains( "CustomerType" ) ); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MapCollectionsTypedTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Slapper.Tests 7 | { 8 | using NUnit.Framework; 9 | 10 | [TestFixture] 11 | public class MapCollectionsTypedTest : TestBase 12 | { 13 | public class Customer 14 | { 15 | public int CustomerId; 16 | public IList OrdersIds; 17 | } 18 | 19 | public class CustomerNames 20 | { 21 | public int CustomerNamesId; 22 | public IList Names; 23 | } 24 | 25 | [Test] 26 | public void I_Can_Map_Value_PrimitiveTyped_Collection() 27 | { 28 | // Arrange 29 | var dictionary = new Dictionary 30 | { 31 | { "CustomerId", 1 }, 32 | { "OrdersIds_$", 3 }, 33 | }; 34 | 35 | var dictionary2 = new Dictionary 36 | { 37 | { "CustomerId", 1 }, 38 | { "OrdersIds_$", 5 } 39 | }; 40 | 41 | var list = new List> { dictionary, dictionary2 }; 42 | 43 | // Act 44 | var customers = Slapper.AutoMapper.MapDynamic(list).ToList(); 45 | 46 | // Assert 47 | 48 | // There should only be a single customer 49 | Assert.That(customers.Count == 1); 50 | 51 | // There should be two values in OrdersIds, with the correct values 52 | Assert.That(customers.Single().OrdersIds.Count == 2); 53 | Assert.That(customers.Single().OrdersIds[0] == 3); 54 | Assert.That(customers.Single().OrdersIds[1] == 5); 55 | } 56 | 57 | 58 | [Test] 59 | public void I_Can_Map_Value_SpecialStringTyped_Collection() 60 | { 61 | // Arrange 62 | var dictionary = new Dictionary 63 | { 64 | { "CustomerNamesId", 1 }, 65 | { "Names_$", "Name 1" }, 66 | }; 67 | 68 | var dictionary2 = new Dictionary 69 | { 70 | { "CustomerNamesId", 1 }, 71 | { "Names_$", "Name 2" } 72 | }; 73 | 74 | var list = new List> { dictionary, dictionary2 }; 75 | 76 | // Act 77 | var customers = Slapper.AutoMapper.MapDynamic(list).ToList(); 78 | 79 | // Assert 80 | 81 | // There should only be a single customer 82 | Assert.That(customers.Count == 1); 83 | 84 | // There should be two values in OrdersIds, with the correct values 85 | Assert.That(customers.Single().Names.Count == 2); 86 | Assert.That(customers.Single().Names[0] == "Name 1"); 87 | Assert.That(customers.Single().Names[1] == "Name 2"); 88 | } 89 | 90 | public class Merchant 91 | { 92 | public Merchant() 93 | { 94 | Addresses = new HashSet(); 95 | } 96 | 97 | public long Id { set; get; } 98 | public string Name { set; get; } 99 | 100 | public ICollection Addresses { set; get; } 101 | } 102 | 103 | public class MerchantAddress 104 | { 105 | public long Id { set; get; } 106 | public string AddressLine { set; get; } 107 | public long MerchantId { set; get; } 108 | } 109 | 110 | [Test] 111 | public void I_Can_Map_Any_Typed_ICollection() 112 | { 113 | // this strings was received from database (or another flat storage) 114 | List> flat = new List>() 115 | { 116 | new Dictionary() 117 | { 118 | { "Id", 1 } , 119 | {"Name", "Merchant name" } , 120 | { "Addresses_Id", 1} , 121 | { "Addresses_AddressLine", "Address line 1"} , 122 | { "Addresses_MerchantId", 1} 123 | }, 124 | new Dictionary() 125 | { 126 | { "Id", 1 } , 127 | {"Name", "Merchant name" } , 128 | { "Addresses_Id", 2} , 129 | { "Addresses_AddressLine", "Address line 2"} , 130 | { "Addresses_MerchantId", 1} 131 | }, 132 | new Dictionary() 133 | { 134 | { "Id", 1 } , 135 | {"Name", "Merchant name" } , 136 | { "Addresses_Id", 3} , 137 | { "Addresses_AddressLine", "Address line 3"} , 138 | { "Addresses_MerchantId", 1} 139 | }, 140 | }; 141 | Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Merchant), new [] { "Id" }); 142 | Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(MerchantAddress), new[] { "Id" }); 143 | var result = Slapper.AutoMapper.MapDynamic(flat); 144 | Assert.That(result.Count() == 1); 145 | var merchant = result.First(); 146 | Assert.That(merchant.Addresses.Count == 3); 147 | Assert.AreEqual("Address line 1", merchant.Addresses.First().AddressLine); 148 | Assert.AreEqual("Address line 2", merchant.Addresses.Skip(1).First().AddressLine); 149 | Assert.AreEqual("Address line 3", merchant.Addresses.Skip(2).First().AddressLine); 150 | } 151 | 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MapDynamicTests.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 MapDynamicTests : TestBase 10 | { 11 | public class Customer 12 | { 13 | public int Id; 14 | public string FirstName; 15 | public string LastName; 16 | public ICollection Orders; 17 | } 18 | 19 | public class Order 20 | { 21 | public int Id; 22 | public decimal OrderTotal; 23 | } 24 | 25 | public class CustomerSingleOrder 26 | { 27 | public int Id; 28 | public string FirstName; 29 | public string LastName; 30 | public SingleOrder Order; 31 | } 32 | 33 | public class SingleOrder 34 | { 35 | public int Id; 36 | public OrderDetails Details; 37 | } 38 | 39 | public class OrderDetails 40 | { 41 | public string Address; 42 | } 43 | 44 | [Test] 45 | public void Can_Handle_Mapping_A_Single_Dynamic_Object() 46 | { 47 | // Arrange 48 | dynamic dynamicCustomer = new ExpandoObject(); 49 | dynamicCustomer.Id = 1; 50 | dynamicCustomer.FirstName = "Bob"; 51 | dynamicCustomer.LastName = "Smith"; 52 | dynamicCustomer.Orders_Id = 1; 53 | dynamicCustomer.Orders_OrderTotal = 50.50m; 54 | 55 | // Act 56 | var customer = Slapper.AutoMapper.MapDynamic( dynamicCustomer ) as Customer; 57 | 58 | // Assert 59 | Assert.NotNull( customer ); 60 | Assert.That( customer.Orders.Count == 1 ); 61 | } 62 | 63 | [Test] 64 | public void Can_Handle_Mapping_Nested_Members_Using_Dynamic() 65 | { 66 | // Arrange 67 | var dynamicCustomers = new List(); 68 | 69 | for ( int i = 0; i < 5; i++ ) 70 | { 71 | dynamic customer = new ExpandoObject(); 72 | customer.Id = i; 73 | customer.FirstName = "FirstName" + i; 74 | customer.LastName = "LastName" + i; 75 | customer.Orders_Id = i; 76 | customer.Orders_OrderTotal = i + 0m; 77 | 78 | dynamicCustomers.Add( customer ); 79 | } 80 | 81 | // Act 82 | var customers = Slapper.AutoMapper.MapDynamic( dynamicCustomers ); 83 | 84 | // Assert 85 | Assert.That( customers.Count() == 5 ); 86 | Assert.That( customers.First().Orders.Count == 1 ); 87 | } 88 | 89 | [Test] 90 | public void Nested_Member_Should_Be_Null_If_All_Values_Are_Null() 91 | { 92 | // Arrange 93 | dynamic customer = new ExpandoObject(); 94 | customer.Id = 1; 95 | customer.FirstName = "FirstName"; 96 | customer.LastName = "LastName"; 97 | customer.Order_Id = null; 98 | customer.Order_OrderTotal = null; 99 | 100 | // Act 101 | var test = Slapper.AutoMapper.MapDynamic(customer); 102 | 103 | // Assert 104 | Assert.That(test != null); 105 | Assert.That(test.Order == null); 106 | } 107 | 108 | [Test] 109 | public void Nested_Member_Should_Be_Null_Only_If_All_Nested_Values_Are_Null() 110 | { 111 | // Arrange 112 | dynamic customer = new ExpandoObject(); 113 | customer.Id = 1; 114 | customer.FirstName = "FirstName"; 115 | customer.LastName = "LastName"; 116 | customer.Order_Id = null; 117 | customer.Order_OrderTotal = null; 118 | customer.Order_Details_Address = "123 Fake Ave."; 119 | 120 | // Act 121 | var test = Slapper.AutoMapper.MapDynamic(customer); 122 | 123 | // Assert 124 | Assert.That(test != null); 125 | Assert.That(test.Order != null); 126 | Assert.That(test.Order.Details != null); 127 | Assert.That(!string.IsNullOrWhiteSpace(test.Order.Details.Address)); 128 | Assert.That(test.Order.Details.Address == "123 Fake Ave."); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MapUniqueChildsIdTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Dynamic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Slapper.Tests 9 | { 10 | [TestFixture] 11 | class MapUniqueChildsIdTest 12 | { 13 | public class NameValue 14 | { 15 | public int Id { get; set; } 16 | public string Name { get; set; } 17 | 18 | public IEnumerable Phones { get; set; } 19 | public IEnumerable Emails { get; set; } 20 | } 21 | 22 | [Test] 23 | public void Can_Map_DifferentsRows_to_Same_object() 24 | { 25 | dynamic dynamicCustomer = new ExpandoObject(); 26 | dynamicCustomer.Id = 1; 27 | dynamicCustomer.Name = "Clark"; 28 | dynamicCustomer.Phones_Id = 1; 29 | dynamicCustomer.Phones_Name = "88888"; 30 | dynamicCustomer.Emails_Id = "1"; 31 | dynamicCustomer.Emails_Name = "a@b.com"; 32 | 33 | dynamic dynamicCustomer2 = new ExpandoObject(); 34 | dynamicCustomer2.Id = 1; 35 | dynamicCustomer2.Name = "Clark"; 36 | dynamicCustomer2.Phones_Id = 2; 37 | dynamicCustomer2.Phones_Name = "99999"; 38 | dynamicCustomer2.Emails_Id = "2"; 39 | dynamicCustomer2.Emails_Name = "c@c.com"; 40 | 41 | var list = new List { dynamicCustomer, dynamicCustomer2 }; 42 | var customer = Slapper.AutoMapper.MapDynamic(list).FirstOrDefault(); 43 | 44 | Assert.NotNull(customer); 45 | Assert.AreNotEqual(customer.Emails.FirstOrDefault(e => e.Id == 1).Name, customer.Phones.FirstOrDefault(p => p.Id == 1).Name); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MappingToEnumTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Dynamic; 3 | using NUnit.Framework; 4 | 5 | namespace Slapper.Tests 6 | { 7 | [TestFixture] 8 | public class MappingToEnumTests : TestBase 9 | { 10 | public enum Gender 11 | { 12 | Female = 1, 13 | Male = 2 14 | } 15 | 16 | public enum MaritalStatus 17 | { 18 | Married = 1, 19 | Single = 2 20 | } 21 | 22 | public class PersonWithFields 23 | { 24 | public int Id; 25 | public string FirstName; 26 | public string LastName; 27 | public Gender Gender; 28 | public MaritalStatus? MaritalStatus; 29 | } 30 | 31 | public class PersonWithProperties 32 | { 33 | public int Id { get; set; } 34 | public string FirstName { get; set; } 35 | public string LastName { get; set; } 36 | public Gender Gender { get; set; } 37 | public MaritalStatus? MaritalStatus { get; set; } 38 | } 39 | 40 | [Test] 41 | public void Can_Map_Enum_Values_To_Enum_Fields() 42 | { 43 | // Arrange 44 | const int id = 1; 45 | const string firstName = "Jimbo"; 46 | const string lastName = "Smith"; 47 | const Gender gender = Gender.Male; 48 | 49 | var dictionary = new Dictionary 50 | { 51 | { "Id", id }, 52 | { "FirstName", firstName }, 53 | { "LastName", lastName }, 54 | { "Gender", gender } 55 | }; 56 | 57 | // Act 58 | var customer = Slapper.AutoMapper.Map( dictionary ); 59 | 60 | // Assert 61 | Assert.NotNull( customer ); 62 | Assert.That( customer.Id == id ); 63 | Assert.That( customer.FirstName == firstName ); 64 | Assert.That( customer.LastName == lastName ); 65 | Assert.That( customer.Gender == gender ); 66 | } 67 | 68 | [Test] 69 | public void Can_Map_Enum_Values_To_Enum_Properties() 70 | { 71 | // Arrange 72 | const int id = 1; 73 | const string firstName = "Jimbo"; 74 | const string lastName = "Smith"; 75 | const Gender gender = Gender.Male; 76 | 77 | var dictionary = new Dictionary 78 | { 79 | { "Id", id }, 80 | { "FirstName", firstName }, 81 | { "LastName", lastName }, 82 | { "Gender", gender } 83 | }; 84 | 85 | // Act 86 | var customer = Slapper.AutoMapper.Map( dictionary ); 87 | 88 | // Assert 89 | Assert.NotNull( customer ); 90 | Assert.That( customer.Id == id ); 91 | Assert.That( customer.FirstName == firstName ); 92 | Assert.That( customer.LastName == lastName ); 93 | Assert.That( customer.Gender == gender ); 94 | } 95 | 96 | [Test] 97 | public void Can_Map_Integer_Values_To_Enum_Fields() 98 | { 99 | // Arrange 100 | const int id = 1; 101 | const string firstName = "Jimbo"; 102 | const string lastName = "Smith"; 103 | const Gender gender = Gender.Male; 104 | 105 | var dictionary = new Dictionary 106 | { 107 | { "Id", id }, 108 | { "FirstName", firstName }, 109 | { "LastName", lastName }, 110 | { "Gender", 2 } 111 | }; 112 | 113 | // Act 114 | var customer = Slapper.AutoMapper.Map( dictionary ); 115 | 116 | // Assert 117 | Assert.NotNull( customer ); 118 | Assert.That( customer.Id == id ); 119 | Assert.That( customer.FirstName == firstName ); 120 | Assert.That( customer.LastName == lastName ); 121 | Assert.That( customer.Gender == gender ); 122 | } 123 | 124 | [Test] 125 | public void Can_Map_Integer_Values_To_Enum_Properties() 126 | { 127 | // Arrange 128 | const int id = 1; 129 | const string firstName = "Jimbo"; 130 | const string lastName = "Smith"; 131 | const Gender gender = Gender.Male; 132 | 133 | var dictionary = new Dictionary 134 | { 135 | { "Id", id }, 136 | { "FirstName", firstName }, 137 | { "LastName", lastName }, 138 | { "Gender", 2 } 139 | }; 140 | 141 | // Act 142 | var customer = Slapper.AutoMapper.Map( dictionary ); 143 | 144 | // Assert 145 | Assert.NotNull( customer ); 146 | Assert.That( customer.Id == id ); 147 | Assert.That( customer.FirstName == firstName ); 148 | Assert.That( customer.LastName == lastName ); 149 | Assert.That( customer.Gender == gender ); 150 | } 151 | 152 | [Test] 153 | public void Can_Map_String_Values_To_Enum_Fields() 154 | { 155 | // Arrange 156 | const int id = 1; 157 | const string firstName = "Jimbo"; 158 | const string lastName = "Smith"; 159 | const Gender gender = Gender.Male; 160 | 161 | var dictionary = new Dictionary 162 | { 163 | { "Id", id }, 164 | { "FirstName", firstName }, 165 | { "LastName", lastName }, 166 | { "Gender", "2" } 167 | }; 168 | 169 | // Act 170 | var customer = Slapper.AutoMapper.Map( dictionary ); 171 | 172 | // Assert 173 | Assert.NotNull( customer ); 174 | Assert.That( customer.Id == id ); 175 | Assert.That( customer.FirstName == firstName ); 176 | Assert.That( customer.LastName == lastName ); 177 | Assert.That( customer.Gender == gender ); 178 | } 179 | 180 | [Test] 181 | public void Can_Map_String_Values_To_Enum_Properties() 182 | { 183 | // Arrange 184 | const int id = 1; 185 | const string firstName = "Jimbo"; 186 | const string lastName = "Smith"; 187 | const Gender gender = Gender.Male; 188 | 189 | var dictionary = new Dictionary 190 | { 191 | { "Id", id }, 192 | { "FirstName", firstName }, 193 | { "LastName", lastName }, 194 | { "Gender", "2" } 195 | }; 196 | 197 | // Act 198 | var customer = Slapper.AutoMapper.Map( dictionary ); 199 | 200 | // Assert 201 | Assert.NotNull( customer ); 202 | Assert.That( customer.Id == id ); 203 | Assert.That( customer.FirstName == firstName ); 204 | Assert.That( customer.LastName == lastName ); 205 | Assert.That( customer.Gender == gender ); 206 | } 207 | 208 | [Test] 209 | public void Can_Map_Null_Values_To_Nullable_Enum_Fields() 210 | { 211 | // Arrange 212 | const int id = 1; 213 | const string firstName = "Jimbo"; 214 | const string lastName = "Smith"; 215 | const Gender gender = Gender.Male; 216 | MaritalStatus? maritalStatus = null; 217 | 218 | dynamic person = new ExpandoObject(); 219 | person.Id = id; 220 | person.FirstName = firstName; 221 | person.LastName = lastName; 222 | person.Gender = gender; 223 | person.MaritalStatus = maritalStatus; 224 | 225 | // Act 226 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( person ); 227 | 228 | // Assert 229 | Assert.NotNull( customer ); 230 | Assert.That( customer.Id == id ); 231 | Assert.That( customer.FirstName == firstName ); 232 | Assert.That( customer.LastName == lastName ); 233 | Assert.That( customer.Gender == gender ); 234 | Assert.That( customer.MaritalStatus == maritalStatus ); 235 | } 236 | 237 | [Test] 238 | public void Can_Map_Null_Values_To_Nullable_Enum_Properties() 239 | { 240 | // Arrange 241 | const int id = 1; 242 | const string firstName = "Jimbo"; 243 | const string lastName = "Smith"; 244 | const Gender gender = Gender.Male; 245 | MaritalStatus? maritalStatus = null; 246 | 247 | dynamic person = new ExpandoObject(); 248 | person.Id = id; 249 | person.FirstName = firstName; 250 | person.LastName = lastName; 251 | person.Gender = gender; 252 | person.MaritalStatus = maritalStatus; 253 | 254 | // Act 255 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( person ); 256 | 257 | // Assert 258 | Assert.NotNull( customer ); 259 | Assert.That( customer.Id == id ); 260 | Assert.That( customer.FirstName == firstName ); 261 | Assert.That( customer.LastName == lastName ); 262 | Assert.That( customer.Gender == gender ); 263 | Assert.That( customer.MaritalStatus == maritalStatus ); 264 | } 265 | 266 | [Test] 267 | public void Can_Map_Int32_Values_To_NUllable_Enum_Properties() 268 | { 269 | dynamic person = new ExpandoObject(); 270 | 271 | person.Id = 1; 272 | person.FirstName = "FirstName"; 273 | person.LastName = "LastName"; 274 | person.MaritalStatus = 2; 275 | person.Gender = Gender.Male; 276 | 277 | // Act 278 | var customer = Slapper.AutoMapper.MapDynamic(person); 279 | 280 | // Assert 281 | Assert.NotNull(customer); 282 | Assert.That(customer.MaritalStatus == MaritalStatus.Single); 283 | } 284 | } 285 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MappingToGuidTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Dynamic; 3 | using NUnit.Framework; 4 | 5 | namespace Slapper.Tests 6 | { 7 | [TestFixture] 8 | public class MappingToGuidTests : TestBase 9 | { 10 | public class PersonWithFields 11 | { 12 | public int Id; 13 | public string FirstName; 14 | public string LastName; 15 | public Guid UniqueId; 16 | public Guid? ANullableUniqueId; 17 | } 18 | 19 | public class PersonWithProperties 20 | { 21 | public int Id { get; set; } 22 | public string FirstName { get; set; } 23 | public string LastName { get; set; } 24 | public Guid UniqueId { get; set; } 25 | public Guid? ANullableUniqueId { get; set; } 26 | } 27 | 28 | [Test] 29 | public void Can_Map_Guid_Values_To_Guid_Fields() 30 | { 31 | // Arrange 32 | const int id = 1; 33 | const string firstName = "Bob"; 34 | const string lastName = "Smith"; 35 | Guid uniqueId = Guid.NewGuid(); 36 | 37 | dynamic dynamicPerson = new ExpandoObject(); 38 | dynamicPerson.Id = id; 39 | dynamicPerson.FirstName = firstName; 40 | dynamicPerson.LastName = lastName; 41 | dynamicPerson.UniqueId = uniqueId; 42 | 43 | // Act 44 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 45 | 46 | // Assert 47 | Assert.NotNull( customer ); 48 | Assert.That( customer.Id == id ); 49 | Assert.That( customer.FirstName == firstName ); 50 | Assert.That( customer.LastName == lastName ); 51 | Assert.That( customer.UniqueId == uniqueId ); 52 | } 53 | 54 | [Test] 55 | public void Can_Map_Guid_Values_To_Guid_Properties() 56 | { 57 | // Arrange 58 | const int id = 1; 59 | const string firstName = "Bob"; 60 | const string lastName = "Smith"; 61 | Guid uniqueId = Guid.NewGuid(); 62 | 63 | dynamic dynamicPerson = new ExpandoObject(); 64 | dynamicPerson.Id = id; 65 | dynamicPerson.FirstName = firstName; 66 | dynamicPerson.LastName = lastName; 67 | dynamicPerson.UniqueId = uniqueId; 68 | 69 | // Act 70 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 71 | 72 | // Assert 73 | Assert.NotNull( customer ); 74 | Assert.That( customer.Id == id ); 75 | Assert.That( customer.FirstName == firstName ); 76 | Assert.That( customer.LastName == lastName ); 77 | Assert.That( customer.UniqueId == uniqueId ); 78 | } 79 | 80 | [Test] 81 | public void Can_Map_Guid_String_Values_To_Guid_Fields() 82 | { 83 | // Arrange 84 | const int id = 1; 85 | const string firstName = "Bob"; 86 | const string lastName = "Smith"; 87 | Guid uniqueId = Guid.NewGuid(); 88 | 89 | dynamic dynamicPerson = new ExpandoObject(); 90 | dynamicPerson.Id = id; 91 | dynamicPerson.FirstName = firstName; 92 | dynamicPerson.LastName = lastName; 93 | dynamicPerson.UniqueId = uniqueId.ToString(); // This is what we are testing 94 | 95 | // Act 96 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 97 | 98 | // Assert 99 | Assert.NotNull( customer ); 100 | Assert.That( customer.Id == id ); 101 | Assert.That( customer.FirstName == firstName ); 102 | Assert.That( customer.LastName == lastName ); 103 | Assert.That( Equals( customer.UniqueId, uniqueId ) ); // This is what we are testing 104 | } 105 | 106 | [Test] 107 | public void Can_Map_Guid_String_Values_To_Guid_Properties() 108 | { 109 | // Arrange 110 | const int id = 1; 111 | const string firstName = "Bob"; 112 | const string lastName = "Smith"; 113 | Guid uniqueId = Guid.NewGuid(); 114 | 115 | dynamic dynamicPerson = new ExpandoObject(); 116 | dynamicPerson.Id = id; 117 | dynamicPerson.FirstName = firstName; 118 | dynamicPerson.LastName = lastName; 119 | dynamicPerson.UniqueId = uniqueId.ToString(); // This is what we are testing 120 | 121 | // Act 122 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 123 | 124 | // Assert 125 | Assert.NotNull( customer ); 126 | Assert.That( customer.Id == id ); 127 | Assert.That( customer.FirstName == firstName ); 128 | Assert.That( customer.LastName == lastName ); 129 | Assert.That( Equals( customer.UniqueId, uniqueId ) ); // This is what we are testing 130 | } 131 | 132 | [Test] 133 | public void Can_Map_Null_Values_To_Guid_Fields() 134 | { 135 | // Arrange 136 | const int id = 1; 137 | const string firstName = "Bob"; 138 | const string lastName = "Smith"; 139 | Guid uniqueId = Guid.NewGuid(); 140 | Guid? aNullableUniqueId = null; 141 | 142 | dynamic dynamicPerson = new ExpandoObject(); 143 | dynamicPerson.Id = id; 144 | dynamicPerson.FirstName = firstName; 145 | dynamicPerson.LastName = lastName; 146 | dynamicPerson.UniqueId = uniqueId.ToString(); 147 | dynamicPerson.ANullableUniqueId = aNullableUniqueId; // This is what we are testing 148 | 149 | // Act 150 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 151 | 152 | // Assert 153 | Assert.NotNull( customer ); 154 | Assert.That( customer.Id == id ); 155 | Assert.That( customer.FirstName == firstName ); 156 | Assert.That( customer.LastName == lastName ); 157 | Assert.That( Equals( customer.UniqueId, uniqueId ) ); 158 | Assert.That( customer.ANullableUniqueId == aNullableUniqueId ); // This is what we are testing 159 | } 160 | 161 | [Test] 162 | public void Can_Map_Null_Values_To_Guid_Properties() 163 | { 164 | // Arrange 165 | const int id = 1; 166 | const string firstName = "Bob"; 167 | const string lastName = "Smith"; 168 | Guid uniqueId = Guid.NewGuid(); 169 | Guid? aNullableUniqueId = null; 170 | 171 | dynamic dynamicPerson = new ExpandoObject(); 172 | dynamicPerson.Id = id; 173 | dynamicPerson.FirstName = firstName; 174 | dynamicPerson.LastName = lastName; 175 | dynamicPerson.UniqueId = uniqueId.ToString(); 176 | dynamicPerson.ANullableUniqueId = aNullableUniqueId; // This is what we are testing 177 | 178 | // Act 179 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 180 | 181 | // Assert 182 | Assert.NotNull( customer ); 183 | Assert.That( customer.Id == id ); 184 | Assert.That( customer.FirstName == firstName ); 185 | Assert.That( customer.LastName == lastName ); 186 | Assert.That( Equals( customer.UniqueId, uniqueId ) ); 187 | Assert.That( customer.ANullableUniqueId == aNullableUniqueId ); // This is what we are testing 188 | } 189 | 190 | [Test] 191 | public void Can_Map_Values_To_Guid_Nullable_Properties() 192 | { 193 | // Arrange 194 | const int id = 1; 195 | const string firstName = "Bob"; 196 | const string lastName = "Smith"; 197 | Guid uniqueId = Guid.NewGuid(); 198 | Guid? aNullableUniqueId = Guid.NewGuid(); 199 | 200 | dynamic dynamicPerson = new ExpandoObject(); 201 | dynamicPerson.Id = id; 202 | dynamicPerson.FirstName = firstName; 203 | dynamicPerson.LastName = lastName; 204 | dynamicPerson.UniqueId = uniqueId.ToString(); 205 | dynamicPerson.ANullableUniqueId = aNullableUniqueId; // This is what we are testing 206 | 207 | // Act 208 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 209 | 210 | // Assert 211 | Assert.NotNull( customer ); 212 | Assert.That( customer.Id == id ); 213 | Assert.That( customer.FirstName == firstName ); 214 | Assert.That( customer.LastName == lastName ); 215 | Assert.That( Equals( customer.UniqueId, uniqueId ) ); 216 | Assert.That( customer.ANullableUniqueId == aNullableUniqueId ); // This is what we are testing 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MappingToNullableTypesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Dynamic; 3 | using NUnit.Framework; 4 | 5 | namespace Slapper.Tests 6 | { 7 | [TestFixture] 8 | public class MappingToDateTimeTests : TestBase 9 | { 10 | public class PersonWithFields 11 | { 12 | public int Id; 13 | public string FirstName; 14 | public string LastName; 15 | public DateTime StartDate; 16 | public DateTime? EndDate; 17 | } 18 | 19 | public class PersonWithProperties 20 | { 21 | public int Id { get; set; } 22 | public string FirstName { get; set; } 23 | public string LastName { get; set; } 24 | public DateTime StartDate { get; set; } 25 | public DateTime? EndDate { get; set; } 26 | } 27 | 28 | [Test] 29 | public void Can_Map_DateTime_Values_To_Nullable_DateTime_Fields() 30 | { 31 | // Arrange 32 | const int id = 1; 33 | const string firstName = "Bob"; 34 | const string lastName = "Smith"; 35 | DateTime startDate = DateTime.Now.AddDays( - 2 ); 36 | DateTime endDate = DateTime.Now; 37 | 38 | dynamic dynamicPerson = new ExpandoObject(); 39 | dynamicPerson.Id = id; 40 | dynamicPerson.FirstName = firstName; 41 | dynamicPerson.LastName = lastName; 42 | dynamicPerson.StartDate = startDate; 43 | dynamicPerson.EndDate = endDate; 44 | 45 | // Act 46 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 47 | 48 | // Assert 49 | Assert.NotNull( customer ); 50 | Assert.That( customer.Id == id ); 51 | Assert.That( customer.FirstName == firstName ); 52 | Assert.That( customer.LastName == lastName ); 53 | Assert.That( customer.StartDate == startDate ); 54 | Assert.That( customer.EndDate == endDate ); 55 | } 56 | 57 | [Test] 58 | public void Can_Map_DateTime_Values_To_Nullable_DateTime_Properties() 59 | { 60 | // Arrange 61 | const int id = 1; 62 | const string firstName = "Bob"; 63 | const string lastName = "Smith"; 64 | DateTime startDate = DateTime.Now.AddDays( -2 ); 65 | DateTime endDate = DateTime.Now; // This is what we are testing 66 | 67 | dynamic dynamicPerson = new ExpandoObject(); 68 | dynamicPerson.Id = id; 69 | dynamicPerson.FirstName = firstName; 70 | dynamicPerson.LastName = lastName; 71 | dynamicPerson.StartDate = startDate; 72 | dynamicPerson.EndDate = endDate; 73 | 74 | // Act 75 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 76 | 77 | // Assert 78 | Assert.NotNull( customer ); 79 | Assert.That( customer.Id == id ); 80 | Assert.That( customer.FirstName == firstName ); 81 | Assert.That( customer.LastName == lastName ); 82 | Assert.That( customer.StartDate == startDate ); 83 | Assert.That( customer.EndDate == endDate ); // This is what we are testing 84 | } 85 | 86 | [Test] 87 | public void Can_Map_Null_Values_To_Nullable_DateTime_Fields() 88 | { 89 | // Arrange 90 | const int id = 1; 91 | const string firstName = "Bob"; 92 | const string lastName = "Smith"; 93 | DateTime startDate = DateTime.Now.AddDays( -2 ); 94 | DateTime? endDate = null; // This is what we are testing 95 | 96 | dynamic dynamicPerson = new ExpandoObject(); 97 | dynamicPerson.Id = id; 98 | dynamicPerson.FirstName = firstName; 99 | dynamicPerson.LastName = lastName; 100 | dynamicPerson.StartDate = startDate; 101 | dynamicPerson.EndDate = endDate; 102 | 103 | // Act 104 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 105 | 106 | // Assert 107 | Assert.NotNull( customer ); 108 | Assert.That( customer.Id == id ); 109 | Assert.That( customer.FirstName == firstName ); 110 | Assert.That( customer.LastName == lastName ); 111 | Assert.That( customer.StartDate == startDate ); 112 | Assert.That( customer.EndDate == endDate ); // This is what we are testing 113 | } 114 | 115 | [Test] 116 | public void Can_Map_Null_Values_To_Nullable_DateTime_Properties() 117 | { 118 | // Arrange 119 | const int id = 1; 120 | const string firstName = "Bob"; 121 | const string lastName = "Smith"; 122 | DateTime startDate = DateTime.Now.AddDays( -2 ); 123 | DateTime? endDate = null; // This is what we are testing 124 | 125 | dynamic dynamicPerson = new ExpandoObject(); 126 | dynamicPerson.Id = id; 127 | dynamicPerson.FirstName = firstName; 128 | dynamicPerson.LastName = lastName; 129 | dynamicPerson.StartDate = startDate; 130 | dynamicPerson.EndDate = endDate; 131 | 132 | // Act 133 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 134 | 135 | // Assert 136 | Assert.NotNull( customer ); 137 | Assert.That( customer.Id == id ); 138 | Assert.That( customer.FirstName == firstName ); 139 | Assert.That( customer.LastName == lastName ); 140 | Assert.That( customer.StartDate == startDate ); 141 | Assert.That( customer.EndDate == endDate ); // This is what we are testing 142 | } 143 | 144 | [Test] 145 | public void Can_Map_DateTime_String_Values_To_Nullable_DateTime_Fields() 146 | { 147 | // Arrange 148 | const int id = 1; 149 | const string firstName = "Bob"; 150 | const string lastName = "Smith"; 151 | DateTime startDate = DateTime.Now.AddDays( -2 ); 152 | DateTime? endDate = DateTime.Now; 153 | 154 | dynamic dynamicPerson = new ExpandoObject(); 155 | dynamicPerson.Id = id; 156 | dynamicPerson.FirstName = firstName; 157 | dynamicPerson.LastName = lastName; 158 | dynamicPerson.StartDate = startDate.ToString(); 159 | dynamicPerson.EndDate = endDate.ToString(); 160 | 161 | // Act 162 | PersonWithFields customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 163 | 164 | // Assert 165 | Assert.NotNull( customer ); 166 | Assert.That( customer.Id == id ); 167 | Assert.That( customer.FirstName == firstName ); 168 | Assert.That( customer.LastName == lastName ); 169 | Assert.That( customer.StartDate.ToString() == startDate.ToString() ); 170 | Assert.That( customer.EndDate.ToString() == endDate.ToString() ); 171 | } 172 | 173 | [Test] 174 | public void Can_Map_DateTime_String_Values_To_Nullable_DateTime_Properties() 175 | { 176 | // Arrange 177 | const int id = 1; 178 | const string firstName = "Bob"; 179 | const string lastName = "Smith"; 180 | DateTime startDate = DateTime.Now.AddDays( -2 ); 181 | DateTime? endDate = DateTime.Now; 182 | 183 | dynamic dynamicPerson = new ExpandoObject(); 184 | dynamicPerson.Id = id; 185 | dynamicPerson.FirstName = firstName; 186 | dynamicPerson.LastName = lastName; 187 | dynamicPerson.StartDate = startDate.ToString(); 188 | dynamicPerson.EndDate = endDate.ToString(); 189 | 190 | // Act 191 | PersonWithProperties customer = Slapper.AutoMapper.MapDynamic( dynamicPerson ); 192 | 193 | // Assert 194 | Assert.NotNull( customer ); 195 | Assert.That( customer.Id == id ); 196 | Assert.That( customer.FirstName == firstName ); 197 | Assert.That( customer.LastName == lastName ); 198 | Assert.That( customer.StartDate.ToString() == startDate.ToString() ); 199 | Assert.That( customer.EndDate.ToString() == endDate.ToString() ); 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/MatchingChildNameTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Slapper.Tests 6 | { 7 | public class MatchingChildNameTests 8 | { 9 | public class SubMember 10 | { 11 | public int Id { get; set; } 12 | } 13 | 14 | public class Member 15 | { 16 | public int Id { get; set; } 17 | public IList SubMembers { get; set; } 18 | } 19 | 20 | public class Club 21 | { 22 | public int Id { get; set; } 23 | public IList Members { get; set; } 24 | } 25 | 26 | [Test] 27 | public void Can_map_grandchild_with_parts_of_same_name_as_child() 28 | { 29 | var data = new List> { 30 | new Dictionary { 31 | {"Id", 1}, 32 | {"Members_Id", 1}, 33 | {"Members_SubMembers_Id", 1} 34 | } 35 | }; 36 | 37 | var club = AutoMapper.MapDynamic(data).Single(); 38 | 39 | Assert.That(club.Members.Count == 1); 40 | Assert.NotNull(club.Members[0].SubMembers); 41 | Assert.That(club.Members[0].SubMembers.Count == 1); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/NoIdentifierTests.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 NoIdentifierTests : TestBase 10 | { 11 | public class PersonWithFields 12 | { 13 | public string FirstName; 14 | public string LastName; 15 | } 16 | 17 | [Test] 18 | public void Can_Map_To_Types_With_No_Identifiers() 19 | { 20 | // Arrange 21 | const string person1FirstName = "Bob"; 22 | const string person1LastName = "Smith"; 23 | const string person2FirstName = "Nancy"; 24 | const string person2LastName = "Sue"; 25 | 26 | dynamic person1 = new ExpandoObject(); 27 | person1.FirstName = person1FirstName; 28 | person1.LastName = person1LastName; 29 | 30 | dynamic person2 = new ExpandoObject(); 31 | person2.FirstName = person2FirstName; 32 | person2.LastName = person2LastName; 33 | 34 | var list = new List { person1, person2 }; 35 | 36 | // Act 37 | var persons = Slapper.AutoMapper.MapDynamic( list ).ToList(); 38 | 39 | // Assert 40 | Assert.NotNull( persons ); 41 | Assert.That( persons.Count == 2 ); 42 | Assert.That( persons[ 0 ].FirstName == person1FirstName ); 43 | Assert.That( persons[ 0 ].LastName == person1LastName ); 44 | Assert.That( persons[ 1 ].FirstName == person2FirstName ); 45 | Assert.That( persons[ 1 ].LastName == person2LastName ); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/NullTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | 4 | namespace Slapper.Tests 5 | { 6 | [TestFixture] 7 | public class NullTests : TestBase 8 | { 9 | public class PersonWithFields 10 | { 11 | public int Id; 12 | public string FirstName; 13 | public string LastName; 14 | } 15 | 16 | public class PersonWithProperties 17 | { 18 | public int Id { get; set; } 19 | public string FirstName { get; set; } 20 | public string LastName { get; set; } 21 | } 22 | 23 | [Test] 24 | public void Can_Map_Null_Values() 25 | { 26 | // Arrange 27 | const int id = 1; 28 | const string firstName = null; 29 | const string lastName = "Smith"; 30 | 31 | var dictionary = new Dictionary 32 | { 33 | { "Id", id }, 34 | { "FirstName", null }, 35 | { "LastName", lastName } 36 | }; 37 | 38 | // Act 39 | var customer = Slapper.AutoMapper.Map( dictionary ); 40 | 41 | // Assert 42 | Assert.NotNull( customer ); 43 | Assert.That( customer.Id == id ); 44 | Assert.That( customer.FirstName == firstName ); 45 | Assert.That( customer.LastName == lastName ); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/ParentMappingTests.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 ParentMappingTests : TestBase 10 | { 11 | public class Customer 12 | { 13 | public int CustomerId; 14 | public string FirstName; 15 | public string LastName; 16 | public IList
Addresses; 17 | } 18 | 19 | public class Address 20 | { 21 | public int AddressId; 22 | public string Line1; 23 | public string Line2; 24 | public string City; 25 | public string State; 26 | public string ZipCode; 27 | public Customer Customer; 28 | } 29 | 30 | [Test] 31 | public void Can_Populate_Parent_Objects_Referenced_In_Child_Objects() 32 | { 33 | dynamic dynamicCustomer = new ExpandoObject(); 34 | dynamicCustomer.CustomerId = 1; 35 | dynamicCustomer.FirstName = "Clark"; 36 | dynamicCustomer.LastName = "Kent"; 37 | dynamicCustomer.Addresses_AddressId = 1; 38 | dynamicCustomer.Addresses_Line1 = "Kent Farm"; 39 | dynamicCustomer.Addresses_Line2 = "Hickory Lane"; 40 | dynamicCustomer.Addresses_City = "Smallville"; 41 | dynamicCustomer.Addresses_State = "Kansas"; 42 | dynamicCustomer.Addresses_ZipCode = "66605"; 43 | 44 | dynamic dynamicCustomer2 = new ExpandoObject(); 45 | dynamicCustomer2.CustomerId = 1; 46 | dynamicCustomer2.FirstName = "Clark"; 47 | dynamicCustomer2.LastName = "Kent"; 48 | dynamicCustomer2.Addresses_AddressId = 2; 49 | dynamicCustomer2.Addresses_Line1 = "1938 Sullivan Place"; 50 | dynamicCustomer2.Addresses_Line2 = ""; 51 | dynamicCustomer2.Addresses_City = "Metropolis"; 52 | dynamicCustomer2.Addresses_State = "New York "; 53 | dynamicCustomer2.Addresses_ZipCode = "10012"; 54 | 55 | var list = new List { dynamicCustomer, dynamicCustomer2 }; 56 | 57 | var customer = Slapper.AutoMapper.MapDynamic( list ).FirstOrDefault(); 58 | 59 | Assert.NotNull( customer ); 60 | Assert.NotNull( customer.Addresses ); 61 | Assert.NotNull( customer.Addresses.FirstOrDefault().Customer ); 62 | Assert.That( customer.Addresses.FirstOrDefault().Customer == customer ); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/PerformanceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | 7 | // ReSharper disable InconsistentNaming 8 | namespace Slapper.Tests 9 | { 10 | [TestFixture] 11 | [Explicit] 12 | public class PerformanceTests : TestBase 13 | { 14 | public class Customer 15 | { 16 | public int CustomerId; 17 | public string FirstName; 18 | public string LastName; 19 | public IList Orders; 20 | } 21 | 22 | public class Order 23 | { 24 | public int OrderId; 25 | public decimal OrderTotal; 26 | public IList OrderDetails; 27 | } 28 | 29 | public class OrderDetail 30 | { 31 | public int OrderDetailId; 32 | public decimal OrderDetailTotal; 33 | } 34 | 35 | /// 36 | /// Simple performance test mapping 50,000 objects. 37 | /// 38 | /// 39 | /// Historical Test Results 40 | /// v1.0.0.1: Mapped 50000 objects in 1755 ms. 41 | /// v1.0.0.2: Mapped 50000 objects in 1918 ms. 42 | /// v1.0.0.3: Mapped 50000 objects in 1819 ms. 43 | /// v1.0.0.4: Mapped 50000 objects in 1683 ms. 44 | /// v1.0.0.4: Mapped 50000 objects in 1683 ms. 45 | /// v1.0.0.5: Mapped 50000 objects in 1877 ms. 46 | /// v1.0.0.6: Mapped 50000 objects in 1642 ms. 47 | /// v2.0.2 : Mapped 50000 objects in 1171 ms. 48 | /// v2.0.3 : Mapped 50000 objects in 472 ms. 49 | /// 50 | [Test] 51 | public void Simple_Performance_Test() 52 | { 53 | // Arrange 54 | const int iterations = 50000; 55 | 56 | var list = new List>(); 57 | 58 | for ( int i = 0; i < iterations; i++ ) 59 | { 60 | var dictionary = new Dictionary 61 | { 62 | { "CustomerId", i }, 63 | { "FirstName", "Bob" }, 64 | { "LastName", "Smith" } 65 | }; 66 | 67 | list.Add( dictionary ); 68 | } 69 | 70 | // Act 71 | Stopwatch stopwatch = Stopwatch.StartNew(); 72 | var customers = AutoMapper.Map( list ); 73 | stopwatch.Stop(); 74 | 75 | // Assert 76 | Assert.NotNull( customers ); 77 | Assert.That( customers.Count() == iterations ); 78 | 79 | Console.WriteLine( string.Format( "Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds ) ); 80 | } 81 | 82 | /// 83 | /// Complex performance test mapping 50,000 objects with with nested child objects. 84 | /// 85 | /// 86 | /// Historical Test Results 87 | /// v1.0.0.1: Mapped 50000 objects in 5913 ms. 88 | /// v1.0.0.2: Mapped 50000 objects in 5911 ms. 89 | /// v1.0.0.3: Mapped 50000 objects in 5327 ms. 90 | /// v1.0.0.4: Mapped 50000 objects in 5349 ms. 91 | /// v1.0.0.4: Mapped 50000 objects in 5349 ms. 92 | /// v1.0.0.5: Mapped 50000 objects in 5896 ms. 93 | /// v1.0.0.6: Mapped 50000 objects in 5539 ms. 94 | /// v1.0.0.8: Mapped 50000 objects in 4185 ms. 95 | /// v2.0.2 : Mapped 50000 objects in 5348 ms. 96 | /// v2.0.3 : Mapped 50000 objects in 3527 ms. 97 | /// 98 | [Test] 99 | public void Complex_Performance_Test() 100 | { 101 | // Arrange 102 | const int iterations = 50000; 103 | 104 | var list = new List>(); 105 | 106 | for ( int i = 0; i < iterations; i++ ) 107 | { 108 | var dictionary = new Dictionary 109 | { 110 | { "CustomerId", i }, 111 | { "FirstName", "Bob" }, 112 | { "LastName", "Smith" }, 113 | { "Orders_OrderId", i }, 114 | { "Orders_OrderTotal", 50.50m }, 115 | { "Orders_OrderDetails_OrderDetailId", i }, 116 | { "Orders_OrderDetails_OrderDetailTotal", 50.50m }, 117 | { "Orders_OrderDetails_Product_Id", 546 }, 118 | { "Orders_OrderDetails_Product_ProductName", "Black Bookshelf" } 119 | }; 120 | 121 | list.Add( dictionary ); 122 | } 123 | 124 | // Act 125 | Stopwatch stopwatch = Stopwatch.StartNew(); 126 | var customers = AutoMapper.Map( list ); 127 | stopwatch.Stop(); 128 | 129 | // Assert 130 | Assert.NotNull( customers ); 131 | Assert.That( customers.Count() == iterations ); 132 | 133 | Console.WriteLine( string.Format( "Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds ) ); 134 | } 135 | } 136 | 137 | [TestFixture] 138 | [Explicit] 139 | public class PerformanceTests2 : TestBase 140 | { 141 | public enum CustomerStatus 142 | { 143 | Active = 1, 144 | Inactive = 2, 145 | Suspended = 3, 146 | Terminated = 4, 147 | Closed = 5, 148 | Deleted = 6 149 | } 150 | 151 | public class Customer 152 | { 153 | public int CustomerId { get; set; } 154 | public string FirstName { get; set; } 155 | public string MiddleName { get; set; } 156 | public string LastName { get; set; } 157 | public string Suffix { get; set; } 158 | public DateTime? DateOfBirth { get; set; } 159 | public CustomerStatus Status { get; set; } 160 | public int IntProperty { get; set; } 161 | public int? NullableIntProperty { get; set; } 162 | public long LongProperty { get; set; } 163 | public long? NullableLongProperty { get; set; } 164 | public decimal DecimalProperty { get; set; } 165 | public Decimal? NullableDecimalProperty { get; set; } 166 | public Guid GuidProperty { get; set; } 167 | public Guid? NullableGuidProperty { get; set; } 168 | public IList Orders { get; set; } 169 | } 170 | 171 | public class Order 172 | { 173 | public int OrderId { get; set; } 174 | public decimal OrderTotal { get; set; } 175 | public IList OrderDetails { get; set; } 176 | } 177 | 178 | public class OrderDetail 179 | { 180 | public int OrderDetailId { get; set; } 181 | public decimal OrderDetailTotal { get; set; } 182 | public Product Product { get; set; } 183 | } 184 | 185 | public class Product 186 | { 187 | public int Id { get; set; } 188 | public string ProductName { get; set; } 189 | } 190 | 191 | /// 192 | /// Complex performance test mapping 120,000 objects with multiple nested child objects. 193 | /// 194 | /// 195 | /// Historical Test Results 196 | /// v2.0.2: Mapped 120000 objects in 30333 ms. 197 | /// v2.0.3: Mapped 120000 objects in 13046 ms. 198 | /// 199 | [Test] 200 | public void Complex_Performance_Test() 201 | { 202 | // Arrange 203 | const int iterations = 120000; 204 | 205 | var list = new List>(); 206 | var random = new Random(); 207 | 208 | for (int i = 0; i < iterations; i++) 209 | { 210 | var dictionary = new Dictionary 211 | { 212 | { "CustomerId", i }, 213 | { "FirstName", "Bob" }, 214 | { "MiddleName", "Lee" }, 215 | { "LastName", "Smith" }, 216 | { "Suffix", "Jr" }, 217 | { "DateOfBirth", "01/01/1980" }, 218 | { "Status", 1 }, 219 | { "IntProperty", random.Next(1,10000) }, 220 | { "NullableIntProperty", random.Next(0,2) == 1 ? null : (int?)random.Next(1,10000) }, 221 | { "LongProperty", random.Next(1,10000) }, 222 | { "NullableLongProperty",random.Next(0,2) == 1 ? null : (long?)random.Next(1,10000) }, 223 | { "DecimalProperty", (decimal?)random.NextDouble() }, 224 | { "NullableDecimalProperty", random.Next(0,2) == 1 ? null : (decimal?)random.NextDouble() }, 225 | { "GuidProperty", Guid.NewGuid() }, 226 | { "NullableGuidProperty", random.Next(0,2) == 1 ? null : (Guid?)Guid.NewGuid() }, 227 | { "Orders_OrderId", i }, 228 | { "Orders_OrderTotal", 50.50m }, 229 | { "Orders_OrderDetails_OrderDetailId", i }, 230 | { "Orders_OrderDetails_OrderDetailTotal", 50.50m }, 231 | { "Orders_OrderDetails_Product_Id", random.Next(1,10000) }, 232 | { "Orders_OrderDetails_Product_ProductName", "Black Bookshelf" } 233 | }; 234 | 235 | list.Add(dictionary); 236 | } 237 | 238 | // Act 239 | Stopwatch stopwatch = Stopwatch.StartNew(); 240 | var customers = AutoMapper.Map(list).ToList(); 241 | stopwatch.Stop(); 242 | 243 | // Assert 244 | Assert.NotNull(customers); 245 | Assert.That(customers.Count == iterations); 246 | 247 | Console.WriteLine(string.Format("Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds)); 248 | } 249 | } 250 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/ReadMeTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Dynamic; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | // ReSharper disable InconsistentNaming 7 | namespace Slapper.Tests 8 | { 9 | [TestFixture] 10 | public class ReadMeTests : TestBase 11 | { 12 | public class Person 13 | { 14 | public int Id; 15 | public string FirstName; 16 | public string LastName; 17 | } 18 | 19 | public class Customer 20 | { 21 | public int CustomerId; 22 | public string FirstName; 23 | public string LastName; 24 | public IList Orders; 25 | } 26 | 27 | public class Order 28 | { 29 | public int OrderId; 30 | public decimal OrderTotal; 31 | public IList OrderDetails; 32 | } 33 | 34 | public class OrderDetail 35 | { 36 | public int OrderDetailId; 37 | public decimal OrderDetailTotal; 38 | } 39 | 40 | [Test] 41 | public void I_Can_Map_Nested_Types_And_Resolve_Duplicate_Entries_Properly() 42 | { 43 | // Arrange 44 | var dictionary = new Dictionary 45 | { 46 | { "CustomerId", 1 }, 47 | { "FirstName", "Bob" }, 48 | { "LastName", "Smith" }, 49 | { "Orders_OrderId", 1 }, 50 | { "Orders_OrderTotal", 50.50m }, 51 | { "Orders_OrderDetails_OrderDetailId", 1 }, 52 | { "Orders_OrderDetails_OrderDetailTotal", 25.00m } 53 | }; 54 | 55 | var dictionary2 = new Dictionary 56 | { 57 | { "CustomerId", 1 }, 58 | { "FirstName", "Bob" }, 59 | { "LastName", "Smith" }, 60 | { "Orders_OrderId", 1 }, 61 | { "Orders_OrderTotal", 50.50m }, 62 | { "Orders_OrderDetails_OrderDetailId", 2 }, 63 | { "Orders_OrderDetails_OrderDetailTotal", 25.50m } 64 | }; 65 | 66 | var list = new List> { dictionary, dictionary2 }; 67 | 68 | // Act 69 | var customers = Slapper.AutoMapper.Map( list ); 70 | 71 | // Assert 72 | 73 | // There should only be a single customer 74 | Assert.That( customers.Count() == 1 ); 75 | 76 | // There should only be a single Order 77 | Assert.That( customers.FirstOrDefault().Orders.Count == 1 ); 78 | 79 | // There should be two OrderDetails 80 | Assert.That( customers.FirstOrDefault().Orders.FirstOrDefault().OrderDetails.Count == 2 ); 81 | } 82 | 83 | [Test] 84 | public void I_Can_Map_Nested_Types_And_Resolve_Duplicate_Entries_Properly_Using_Dynamics() 85 | { 86 | // Arrange 87 | dynamic customer1 = new ExpandoObject(); 88 | customer1.CustomerId = 1; 89 | customer1.FirstName = "Bob"; 90 | customer1.LastName = "Smith"; 91 | customer1.Orders_OrderId = 1; 92 | customer1.Orders_OrderTotal = 50.50m; 93 | customer1.Orders_OrderDetails_OrderDetailId = 1; 94 | customer1.Orders_OrderDetails_OrderDetailTotal = 25.00m; 95 | 96 | dynamic customer2 = new ExpandoObject(); 97 | customer2.CustomerId = 1; 98 | customer2.FirstName = "Bob"; 99 | customer2.LastName = "Smith"; 100 | customer2.Orders_OrderId = 1; 101 | customer2.Orders_OrderTotal = 50.50m; 102 | customer2.Orders_OrderDetails_OrderDetailId = 2; 103 | customer2.Orders_OrderDetails_OrderDetailTotal = 25.50m; 104 | 105 | var customerList = new List { customer1, customer2 }; 106 | 107 | // Act 108 | var customers = Slapper.AutoMapper.MapDynamic( customerList ); 109 | 110 | // Assert 111 | 112 | // There should only be a single customer 113 | Assert.That( customers.Count() == 1 ); 114 | 115 | // There should only be a single Order 116 | Assert.That( customers.FirstOrDefault().Orders.Count == 1 ); 117 | 118 | // There should be two OrderDetails 119 | Assert.That( customers.FirstOrDefault().Orders.FirstOrDefault().OrderDetails.Count == 2 ); 120 | } 121 | 122 | [Test] 123 | public void Can_Map_Matching_Field_Names_With_Ease() 124 | { 125 | // Arrange 126 | var dictionary = new Dictionary 127 | { 128 | { "Id", 1 }, 129 | { "FirstName", "Clark" }, 130 | { "LastName", "Kent" } 131 | }; 132 | 133 | // Act 134 | var person = Slapper.AutoMapper.Map( dictionary ); 135 | 136 | // Assert 137 | Assert.NotNull( person ); 138 | Assert.That( person.Id == 1 ); 139 | Assert.That( person.FirstName == "Clark" ); 140 | Assert.That( person.LastName == "Kent" ); 141 | } 142 | 143 | [Test] 144 | public void Can_Map_Matching_Field_Names_Using_Dynamic() 145 | { 146 | // Arrange 147 | dynamic dynamicPerson = new ExpandoObject(); 148 | dynamicPerson.Id = 1; 149 | dynamicPerson.FirstName = "Clark"; 150 | dynamicPerson.LastName = "Kent"; 151 | 152 | // Act 153 | var person = Slapper.AutoMapper.MapDynamic( dynamicPerson ) as Person; 154 | 155 | // Assert 156 | Assert.NotNull( person ); 157 | Assert.That( person.Id == 1 ); 158 | Assert.That( person.FirstName == "Clark" ); 159 | Assert.That( person.LastName == "Kent" ); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/SimpleMapTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | 4 | namespace Slapper.Tests 5 | { 6 | [TestFixture] 7 | public class SimplyMapTests : TestBase 8 | { 9 | public class PersonWithFields 10 | { 11 | public int Id; 12 | public string FirstName; 13 | public string LastName; 14 | } 15 | 16 | public class PersonWithProperties 17 | { 18 | public int Id { get; set; } 19 | public string FirstName { get; set; } 20 | public string LastName { get; set; } 21 | } 22 | 23 | [Test] 24 | public void Can_Map_Matching_Field_Names() 25 | { 26 | // Arrange 27 | const int id = 1; 28 | const string firstName = "Bob"; 29 | const string lastName = "Smith"; 30 | 31 | var dictionary = new Dictionary 32 | { 33 | { "Id", id }, 34 | { "FirstName", firstName }, 35 | { "LastName", lastName } 36 | }; 37 | 38 | // Act 39 | var customer = Slapper.AutoMapper.Map( dictionary ); 40 | 41 | // Assert 42 | Assert.NotNull( customer ); 43 | Assert.That( customer.Id == id ); 44 | Assert.That( customer.FirstName == firstName ); 45 | Assert.That( customer.LastName == lastName ); 46 | } 47 | 48 | [Test] 49 | public void Can_Map_Matching_Property_Names() 50 | { 51 | // Arrange 52 | const int id = 1; 53 | const string firstName = "Bob"; 54 | const string lastName = "Smith"; 55 | 56 | var dictionary = new Dictionary 57 | { 58 | { "Id", id }, 59 | { "FirstName", firstName }, 60 | { "LastName", lastName } 61 | }; 62 | 63 | // Act 64 | var customer = Slapper.AutoMapper.Map( dictionary ); 65 | 66 | // Assert 67 | Assert.NotNull( customer ); 68 | Assert.That( customer.Id == id ); 69 | Assert.That( customer.FirstName == firstName ); 70 | Assert.That( customer.LastName == lastName ); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/SimpleTypeConversionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using NUnit.Framework; 5 | 6 | namespace Slapper.Tests 7 | { 8 | [TestFixture] 9 | public class SimpleTypeConversionTests : TestBase 10 | { 11 | public class PersonWithFields 12 | { 13 | public int Id; 14 | public string FirstName; 15 | public string LastName; 16 | } 17 | 18 | public class PersonWithProperties 19 | { 20 | public int Id { get; set; } 21 | public string FirstName { get; set; } 22 | public string LastName { get; set; } 23 | } 24 | 25 | [Test] 26 | public void Can_Map_Matching_Field_Names_With_Different_Types() 27 | { 28 | // Arrange 29 | const int id = 1; 30 | const string firstName = "Bob"; 31 | const string lastName = "Smith"; 32 | 33 | var dictionary = new Dictionary 34 | { 35 | { "Id", double.Parse( "1.245698", CultureInfo.InvariantCulture)}, 36 | { "FirstName", firstName }, 37 | { "LastName", lastName } 38 | }; 39 | 40 | // Act 41 | var customer = Slapper.AutoMapper.Map( dictionary ); 42 | 43 | // Assert 44 | Assert.NotNull( customer ); 45 | Assert.That( customer.Id == id ); 46 | Assert.That( customer.FirstName == firstName ); 47 | Assert.That( customer.LastName == lastName ); 48 | } 49 | 50 | [Test] 51 | public void Can_Map_Matching_Property_Names_With_Different_Types() 52 | { 53 | // Arrange 54 | const int id = 1; 55 | const string firstName = "Bob"; 56 | const string lastName = "Smith"; 57 | 58 | var dictionary = new Dictionary 59 | { 60 | { "Id", Double.Parse( "1.245698", CultureInfo.InvariantCulture ) }, 61 | { "FirstName", firstName }, 62 | { "LastName", lastName } 63 | }; 64 | 65 | // Act 66 | var customer = Slapper.AutoMapper.Map( dictionary ); 67 | 68 | // Assert 69 | Assert.NotNull( customer ); 70 | Assert.That( customer.Id == id ); 71 | Assert.That( customer.FirstName == firstName ); 72 | Assert.That( customer.LastName == lastName ); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/Slapper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | Slapper.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | // ReSharper disable InconsistentNaming 4 | namespace Slapper.Tests 5 | { 6 | public class TestBase 7 | { 8 | [TearDown] 9 | public void TearDown() 10 | { 11 | Slapper.AutoMapper.Cache.ClearAllCaches(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | 4 | namespace Slapper.Tests 5 | { 6 | public static class TestHelpers 7 | { 8 | /// 9 | /// Gets the hash code slapper would create for a particular object. 10 | /// 11 | /// 12 | /// 13 | public static int GetHashCode( object instance ) 14 | { 15 | var instanceType = instance.GetType(); 16 | 17 | var identifiers = Slapper.AutoMapper.InternalHelpers.GetIdentifiers( instanceType ); 18 | 19 | int identifierHash = 0; 20 | 21 | if ( identifiers != null ) 22 | { 23 | var fieldsAndProperties = Slapper.AutoMapper.InternalHelpers.GetFieldsAndProperties( instanceType ); 24 | 25 | foreach ( var fieldOrProperty in fieldsAndProperties ) 26 | { 27 | var memberName = fieldOrProperty.Key; 28 | 29 | if ( identifiers.Contains( memberName ) ) 30 | { 31 | var member = fieldOrProperty.Value; 32 | 33 | object value = null; 34 | 35 | if ( member is FieldInfo ) 36 | { 37 | value = ( ( FieldInfo ) member ).GetValue( instance ); 38 | } 39 | else if ( member is PropertyInfo ) 40 | { 41 | value = ( ( PropertyInfo ) member ).GetValue( instance, null ); 42 | } 43 | 44 | if ( value != null ) 45 | { 46 | identifierHash += value.GetHashCode() + instanceType.GetHashCode(); 47 | } 48 | } 49 | } 50 | } 51 | 52 | return identifierHash; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests/TypeActivatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | 5 | namespace Slapper.Tests 6 | { 7 | public class TypeActivatorTests 8 | { 9 | public interface IClass 10 | { 11 | string Id { get; set; } 12 | 13 | INestedClass Nested { get; set; } 14 | } 15 | 16 | public class Class : IClass 17 | { 18 | public string Id { get; set; } 19 | 20 | public INestedClass Nested { get; set; } 21 | } 22 | 23 | public interface INestedClass 24 | { 25 | int Count { get; set; } 26 | 27 | Enum Enum { get; set; } 28 | } 29 | 30 | public class NestedClass : INestedClass 31 | { 32 | public int Count { get; set; } 33 | 34 | public Enum Enum { get; set; } 35 | } 36 | 37 | public enum Enum 38 | { 39 | One, 40 | Two 41 | } 42 | 43 | public class TypeActivator : AutoMapper.Configuration.ITypeActivator 44 | { 45 | private readonly IDictionary supportedTypes = new Dictionary 46 | { 47 | { typeof(IClass), typeof(Class) }, 48 | { typeof(INestedClass), typeof(NestedClass) } 49 | }; 50 | 51 | public object Create(Type type) 52 | { 53 | var concreteType = this.supportedTypes[type]; 54 | return Activator.CreateInstance(concreteType); 55 | } 56 | 57 | public bool CanCreate(Type type) 58 | { 59 | return this.supportedTypes.ContainsKey(type); 60 | } 61 | 62 | public int Order { get; } = 1; 63 | } 64 | 65 | [Test] 66 | public void Can_Use_Registered_TypeActivators_WithInterfaces() 67 | { 68 | // Arrange 69 | const string id = "1"; 70 | const int count = 2; 71 | const Enum enu = Enum.Two; 72 | 73 | var dictionary = new Dictionary 74 | { 75 | { "Id", id }, 76 | { "Nested_Count", count }, 77 | { "Nested_Enum", enu } 78 | }; 79 | 80 | AutoMapper.Configuration.TypeActivators.Add(new TypeActivator()); 81 | 82 | // Act 83 | var result = Slapper.AutoMapper.Map(dictionary); 84 | 85 | // Assert 86 | Assert.NotNull(result); 87 | Assert.That(result.Id == id); 88 | Assert.That(result.Nested.Enum == enu); 89 | Assert.That(result.Nested.Count == count); 90 | } 91 | 92 | public interface IClass2 93 | { 94 | string Id { get; set; } 95 | } 96 | 97 | public class Class2 : IClass2 98 | { 99 | public string Id { get; set; } 100 | } 101 | 102 | public class Class2Copy : IClass2 103 | { 104 | public string Id { get; set; } 105 | } 106 | 107 | public class Class2TypeActivator : AutoMapper.Configuration.ITypeActivator 108 | { 109 | public object Create(Type type) 110 | { 111 | return new Class2(); 112 | } 113 | 114 | public bool CanCreate(Type type) 115 | { 116 | return type.IsAssignableFrom(typeof(IClass2)); 117 | } 118 | 119 | public int Order { get; } = 2; 120 | } 121 | 122 | public class Class2CopyTypeActivator : AutoMapper.Configuration.ITypeActivator 123 | { 124 | public object Create(Type type) 125 | { 126 | return new Class2Copy(); 127 | } 128 | 129 | public bool CanCreate(Type type) 130 | { 131 | return type.IsAssignableFrom(typeof(IClass2)); 132 | } 133 | 134 | public int Order { get; } = 1; 135 | } 136 | 137 | [Test] 138 | public void TypeActivator_Order_Is_Adhered_To() 139 | { 140 | // Arrange 141 | const string id = "1"; 142 | const int count = 2; 143 | const Enum enu = Enum.Two; 144 | 145 | var dictionary = new Dictionary 146 | { 147 | { "Id", id }, 148 | { "Nested_Count", count }, 149 | { "Nested_Enum", enu } 150 | }; 151 | 152 | AutoMapper.Configuration.TypeActivators.Add(new Class2TypeActivator()); 153 | AutoMapper.Configuration.TypeActivators.Add(new Class2CopyTypeActivator()); 154 | 155 | // Act 156 | var result = Slapper.AutoMapper.Map(dictionary); 157 | 158 | // Assert 159 | Assert.NotNull(result); 160 | Assert.That(result.Id == id); 161 | Assert.That(result.GetType() == typeof(Class2Copy)); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests47/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ClassLibrary1")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("LG")] 12 | [assembly: AssemblyProduct("ClassLibrary1")] 13 | [assembly: AssemblyCopyright("Copyright © LG 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("de044332-9d9d-48ba-b63d-72bb64e5dee6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests47/Slapper.Tests47.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | {DE044332-9D9D-48BA-B63D-72BB64E5DEE6} 12 | Library 13 | Properties 14 | Slapper.Tests 15 | Slapper.Tests47 16 | v4.7 17 | 512 18 | true 19 | 20 | 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | ..\packages\Microsoft.CodeCoverage.16.11.0\lib\net45\Microsoft.VisualStudio.CodeCoverage.Shim.dll 42 | 43 | 44 | ..\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll 45 | 46 | 47 | False 48 | ..\Slapper.AutoMapper\bin\Debug\net47\Slapper.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ArrayTests.cs 62 | 63 | 64 | CachingBehaviorTests.cs 65 | 66 | 67 | ComplexMapsParentsAndChlidTest.cs 68 | 69 | 70 | ComplexMapTests.cs 71 | 72 | 73 | EmptyList.cs 74 | 75 | 76 | ExceptionTests.cs 77 | 78 | 79 | GuidConverterTests.cs 80 | 81 | 82 | HashCollisionTests.cs 83 | 84 | 85 | IdentifierTests.cs 86 | 87 | 88 | MapCollectionsTypedTest.cs 89 | 90 | 91 | MapDynamicTests.cs 92 | 93 | 94 | MappingToEnumTests.cs 95 | 96 | 97 | MappingToGuidTests.cs 98 | 99 | 100 | MappingToNullableTypesTests.cs 101 | 102 | 103 | MapUniqueChildsIdTest.cs 104 | 105 | 106 | MatchingChildNameTests.cs 107 | 108 | 109 | NoIdentifierTests.cs 110 | 111 | 112 | NullTests.cs 113 | 114 | 115 | ParentMappingTests.cs 116 | 117 | 118 | PerformanceTests.cs 119 | 120 | 121 | ReadMeTests.cs 122 | 123 | 124 | SimpleMapTests.cs 125 | 126 | 127 | SimpleTypeConversionTests.cs 128 | 129 | 130 | TestBase.cs 131 | 132 | 133 | TestHelpers.cs 134 | 135 | 136 | TypeActivatorTests.cs 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.Tests47/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31005.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Slapper", "Slapper.AutoMapper\Slapper.csproj", "{A3A6ABBF-8C42-4945-BB8B-804123E45B2D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Slapper.Tests", "Slapper.AutoMapper.Tests\Slapper.Tests.csproj", "{DB57364B-6EE2-46C0-837E-B43F53CEEFA5}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Slapper.Tests47", "Slapper.AutoMapper.Tests47\Slapper.Tests47.csproj", "{DE044332-9D9D-48BA-B63D-72BB64E5DEE6}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A3A6ABBF-8C42-4945-BB8B-804123E45B2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A3A6ABBF-8C42-4945-BB8B-804123E45B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A3A6ABBF-8C42-4945-BB8B-804123E45B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A3A6ABBF-8C42-4945-BB8B-804123E45B2D}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {DB57364B-6EE2-46C0-837E-B43F53CEEFA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {DB57364B-6EE2-46C0-837E-B43F53CEEFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {DB57364B-6EE2-46C0-837E-B43F53CEEFA5}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {DB57364B-6EE2-46C0-837E-B43F53CEEFA5}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {DE044332-9D9D-48BA-B63D-72BB64E5DEE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {DE044332-9D9D-48BA-B63D-72BB64E5DEE6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {DE044332-9D9D-48BA-B63D-72BB64E5DEE6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {DE044332-9D9D-48BA-B63D-72BB64E5DEE6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {788C0E0D-1CE3-400B-B65A-068DC8DF00EA} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Slapper.AutoMapper.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /Slapper.AutoMapper/CallContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading; 3 | 4 | namespace Slapper 5 | { 6 | /// 7 | /// Provides a way to set contextual data that flows with the call and 8 | /// async context of a test or invocation. 9 | /// 10 | internal static class CallContext 11 | { 12 | static readonly ConcurrentDictionary> State = new ConcurrentDictionary>(); 13 | 14 | /// 15 | /// Stores a given object and associates it with the specified name. 16 | /// 17 | /// The name with which to associate the new item in the call context. 18 | /// The object to store in the call context. 19 | public static void SetData(string name, object data) => 20 | State.GetOrAdd(name, _ => new AsyncLocal()).Value = data; 21 | 22 | /// 23 | /// Retrieves an object with the specified name from the . 24 | /// 25 | /// The name of the item in the call context. 26 | /// The object in the call context associated with the specified name, or if not found. 27 | public static object GetData(string name) => 28 | State.TryGetValue(name, out AsyncLocal data) ? data.Value : null; 29 | 30 | public static bool FreeNamedDataSlot(string key) => State.TryRemove(key, out _); 31 | } 32 | } -------------------------------------------------------------------------------- /Slapper.AutoMapper/Slapper.AutoMapper.Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | 5 | namespace Slapper 6 | { 7 | public static partial class AutoMapper 8 | { 9 | /// 10 | /// Contains the methods and members responsible for this libraries caching concerns. 11 | /// 12 | public static class Cache 13 | { 14 | /// 15 | /// The name of the instance cache stored in the logical call context. 16 | /// 17 | internal const string InstanceCacheContextStorageKey = "Slapper.AutoMapper.InstanceCache"; 18 | 19 | /// 20 | /// Cache of TypeMaps containing the types identifiers and PropertyInfo/FieldInfo objects. 21 | /// 22 | internal static readonly ConcurrentDictionary TypeMapCache = new ConcurrentDictionary(); 23 | 24 | /// 25 | /// A TypeMap holds data relevant for a particular Type. 26 | /// 27 | internal class TypeMap 28 | { 29 | /// 30 | /// Creates a new . 31 | /// 32 | /// Type to map. 33 | /// The s identifiers. 34 | /// The s properties and fields. 35 | public TypeMap(Type type, IEnumerable identifiers, Dictionary propertiesAndFields) 36 | { 37 | Type = type; 38 | Identifiers = identifiers; 39 | PropertiesAndFieldsInfo = propertiesAndFields; 40 | } 41 | 42 | /// 43 | /// Type for this TypeMap 44 | /// 45 | public readonly Type Type; 46 | 47 | /// 48 | /// List of identifiers 49 | /// 50 | public IEnumerable Identifiers; 51 | 52 | /// 53 | /// Property/field names and their corresponding PropertyInfo/FieldInfo objects 54 | /// 55 | public Dictionary PropertiesAndFieldsInfo; 56 | } 57 | 58 | /// 59 | /// Clears all internal caches. 60 | /// 61 | public static void ClearAllCaches() 62 | { 63 | TypeMapCache.Clear(); 64 | ClearInstanceCache(); 65 | } 66 | 67 | /// 68 | /// Clears the instance cache. This cache contains all objects created by Slapper.AutoMapper. 69 | /// 70 | public static void ClearInstanceCache() 71 | { 72 | var instanceCache = InternalHelpers.ContextStorage.Get>(InstanceCacheContextStorageKey); 73 | if (instanceCache != null) 74 | { 75 | instanceCache.Clear(); 76 | InternalHelpers.ContextStorage.Remove(InstanceCacheContextStorageKey); 77 | } 78 | } 79 | 80 | /// 81 | /// Gets the instance cache containing all objects created by Slapper.AutoMapper. 82 | /// This cache exists for the lifetime of the current thread until manually cleared/purged. 83 | /// 84 | /// 85 | /// Due to the nature of how the cache is persisted, each new thread will receive it's own 86 | /// unique cache. 87 | /// 88 | /// Instance Cache 89 | internal static Dictionary GetInstanceCache() 90 | { 91 | var instanceCache = InternalHelpers.ContextStorage.Get>(InstanceCacheContextStorageKey); 92 | 93 | if (instanceCache == null) 94 | { 95 | instanceCache = new Dictionary(); 96 | 97 | InternalHelpers.ContextStorage.Store(InstanceCacheContextStorageKey, instanceCache); 98 | } 99 | 100 | return instanceCache; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Slapper 5 | { 6 | public static partial class AutoMapper 7 | { 8 | /// 9 | /// Contains the methods and members responsible for this libraries configuration concerns. 10 | /// 11 | public static class Configuration 12 | { 13 | static Configuration() 14 | { 15 | IdentifierAttributeType = typeof(Id); 16 | 17 | ApplyDefaultIdentifierConventions(); 18 | ApplyDefaultTypeConverters(); 19 | } 20 | 21 | /// 22 | /// Current version of Slapper.AutoMapper. 23 | /// 24 | [Obsolete("Will remove in a future version")] 25 | public static readonly Version Version = new Version("2.0.3.0"); 26 | 27 | /// 28 | /// The attribute Type specifying that a field or property is an identifier. 29 | /// 30 | public static Type IdentifierAttributeType; 31 | 32 | /// 33 | /// Convention for finding an identifier. 34 | /// 35 | /// 36 | /// 37 | public delegate string ApplyIdentifierConvention(Type type); 38 | 39 | /// 40 | /// Conventions for finding an identifier. 41 | /// 42 | public static readonly List IdentifierConventions = new List(); 43 | 44 | /// 45 | /// Type converters used to convert values from one type to another. 46 | /// 47 | public static readonly List TypeConverters = new List(); 48 | 49 | /// 50 | /// Activators to instantiate types. 51 | /// 52 | public static readonly List TypeActivators = new List(); 53 | 54 | /// 55 | /// Applies default conventions for finding identifiers. 56 | /// 57 | public static void ApplyDefaultIdentifierConventions() 58 | { 59 | IdentifierConventions.Add(type => "Id"); 60 | IdentifierConventions.Add(type => type.Name + "Id"); 61 | IdentifierConventions.Add(type => type.Name + "Nbr"); 62 | } 63 | 64 | /// 65 | /// Applies the default ITypeConverters for converting values to different types. 66 | /// 67 | public static void ApplyDefaultTypeConverters() 68 | { 69 | TypeConverters.Add(new GuidConverter()); 70 | TypeConverters.Add(new EnumConverter()); 71 | TypeConverters.Add(new ValueTypeConverter()); 72 | } 73 | 74 | /// 75 | /// Adds an identifier for the specified type. 76 | /// Replaces any identifiers previously specified. 77 | /// 78 | /// Type 79 | /// Identifier 80 | public static void AddIdentifier(Type type, string identifier) 81 | { 82 | AddIdentifiers(type, new List { identifier }); 83 | } 84 | 85 | /// 86 | /// Adds identifiers for the specified type. 87 | /// Replaces any identifiers previously specified. 88 | /// 89 | /// Type 90 | /// Identifiers 91 | public static void AddIdentifiers(Type type, IEnumerable identifiers) 92 | { 93 | var typeMap = Cache.TypeMapCache.GetOrAdd(type, (t) => 94 | { 95 | // ReSharper disable once ConvertClosureToMethodGroup 96 | return InternalHelpers.CreateTypeMap(t); 97 | }); 98 | 99 | typeMap.Identifiers = identifiers; 100 | } 101 | 102 | /// 103 | /// Defines methods that can convert values from one type to another. 104 | /// 105 | public interface ITypeConverter 106 | { 107 | /// 108 | /// Converts the given value to the requested type. 109 | /// 110 | /// Value to convert. 111 | /// Type the value is to be converted to. 112 | /// Converted value. 113 | object Convert(object value, Type type); 114 | 115 | /// 116 | /// Indicates whether it can convert the given value to the requested type. 117 | /// 118 | /// Value to convert. 119 | /// Type the value needs to be converted to. 120 | /// Boolean response. 121 | bool CanConvert(object value, Type type); 122 | 123 | /// 124 | /// Order to execute an in. 125 | /// 126 | int Order { get; } 127 | } 128 | 129 | /// 130 | /// Converts values to Guids. 131 | /// 132 | public class GuidConverter : ITypeConverter 133 | { 134 | #region Implementation of ITypeConverter 135 | 136 | /// 137 | /// Converts the given value to the requested type. 138 | /// 139 | /// Value to convert. 140 | /// Type the value is to be converted to. 141 | /// Converted value. 142 | public object Convert(object value, Type type) 143 | { 144 | object convertedValue = null; 145 | 146 | if (value is string str) 147 | { 148 | convertedValue = new Guid(str); 149 | } 150 | if (value is byte[] bytes) 151 | { 152 | convertedValue = new Guid(bytes); 153 | } 154 | if (value is Guid guid) 155 | { 156 | convertedValue = guid; 157 | } 158 | 159 | return convertedValue; 160 | } 161 | 162 | /// 163 | /// Indicates whether it can convert the given value to the requested type. 164 | /// 165 | /// Value to convert. 166 | /// Type the value needs to be converted to. 167 | /// Boolean response. 168 | public bool CanConvert(object value, Type type) 169 | { 170 | var conversionType = Nullable.GetUnderlyingType(type) ?? type; 171 | return conversionType == typeof(Guid); 172 | } 173 | 174 | /// 175 | /// Order to execute an in. 176 | /// 177 | public int Order => 100; 178 | 179 | #endregion 180 | } 181 | 182 | /// 183 | /// Converts values to Enums. 184 | /// 185 | public class EnumConverter : ITypeConverter 186 | { 187 | #region Implementation of ITypeConverter 188 | 189 | /// 190 | /// Converts the given value to the requested type. 191 | /// 192 | /// Value to convert. 193 | /// Type the value is to be converted to. 194 | /// Converted value. 195 | public object Convert(object value, Type type) 196 | { 197 | // Handle Nullable types 198 | var conversionType = Nullable.GetUnderlyingType(type) ?? type; 199 | 200 | object convertedValue = Enum.Parse(conversionType, value.ToString()); 201 | 202 | return convertedValue; 203 | } 204 | 205 | /// 206 | /// Indicates whether it can convert the given value to the requested type. 207 | /// 208 | /// Value to convert. 209 | /// Type the value needs to be converted to. 210 | /// Boolean response. 211 | public bool CanConvert(object value, Type type) 212 | { 213 | var conversionType = Nullable.GetUnderlyingType(type) ?? type; 214 | return conversionType.IsEnum; 215 | } 216 | 217 | /// 218 | /// Order to execute an in. 219 | /// 220 | public int Order => 100; 221 | 222 | #endregion 223 | } 224 | 225 | /// 226 | /// Converts values types. 227 | /// 228 | public class ValueTypeConverter : ITypeConverter 229 | { 230 | #region Implementation of ITypeConverter 231 | 232 | /// 233 | /// Converts the given value to the requested type. 234 | /// 235 | /// Value to convert. 236 | /// Type the value is to be converted to. 237 | /// Converted value. 238 | public object Convert(object value, Type type) 239 | { 240 | // Handle Nullable types 241 | var conversionType = Nullable.GetUnderlyingType(type) ?? type; 242 | 243 | var convertedValue = System.Convert.ChangeType(value, conversionType); 244 | 245 | return convertedValue; 246 | } 247 | 248 | /// 249 | /// Indicates whether it can convert the given value to the requested type. 250 | /// 251 | /// Value to convert. 252 | /// Type the value needs to be converted to. 253 | /// Boolean response. 254 | public bool CanConvert(object value, Type type) 255 | { 256 | return type.IsValueType && !type.IsEnum && type != typeof(Guid); 257 | } 258 | 259 | /// 260 | /// Order to execute an in. 261 | /// 262 | public int Order => 1000; 263 | 264 | #endregion 265 | } 266 | 267 | /// 268 | /// Defines an interface for an activator for a specific type. 269 | /// 270 | public interface ITypeActivator 271 | { 272 | /// 273 | /// Creates the type. 274 | /// 275 | /// The type to create. 276 | /// The created type. 277 | object Create(Type type); 278 | 279 | /// 280 | /// Indicates whether it can create the type. 281 | /// 282 | /// The type to create. 283 | /// Boolean response. 284 | bool CanCreate(Type type); 285 | 286 | /// 287 | /// The order to try the activator in. 288 | /// 289 | int Order { get; } 290 | } 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /Slapper.AutoMapper/Slapper.AutoMapper.Logging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Slapper 5 | { 6 | public static partial class AutoMapper 7 | { 8 | /// 9 | /// Contains the methods and members responsible for this libraries logging concerns. 10 | /// 11 | public class Logging 12 | { 13 | /// 14 | /// Logger for this library. 15 | /// 16 | public static ILogger Logger = new TraceLogger(); 17 | 18 | /// 19 | /// Log levels. 20 | /// 21 | public enum LogLevel 22 | { 23 | /// 24 | /// Logs debug level messages. 25 | /// 26 | Debug, 27 | 28 | /// 29 | /// Logs info level messages. 30 | /// 31 | Info, 32 | 33 | /// 34 | /// Logs warning level messages. 35 | /// 36 | Warn, 37 | 38 | /// 39 | /// Logs error level messages. 40 | /// 41 | Error, 42 | 43 | /// 44 | /// Logs fatal level messages. 45 | /// 46 | Fatal 47 | } 48 | 49 | /// 50 | /// Contains methods for logging messages. 51 | /// 52 | public interface ILogger 53 | { 54 | /// 55 | /// Logs messages. 56 | /// 57 | /// Log level. 58 | /// Log message format. 59 | /// Log message arguments. 60 | void Log(LogLevel logLevel, string format, params object[] args); 61 | 62 | /// 63 | /// Logs exception messages. 64 | /// 65 | /// Log level. 66 | /// Exception being logged. 67 | /// Log message format. 68 | /// Log message arguments. 69 | void Log(LogLevel logLevel, Exception exception, string format, params object[] args); 70 | } 71 | 72 | /// 73 | /// Logs messages to any trace listeners. 74 | /// 75 | public class TraceLogger : ILogger 76 | { 77 | /// 78 | /// The minimum log level that this class will log messages for. 79 | /// 80 | public static LogLevel MinimumLogLevel = LogLevel.Error; 81 | 82 | #region Implementation of ILogger 83 | 84 | /// 85 | /// Logs messages to any trace listeners. 86 | /// 87 | /// Log level. 88 | /// Log message format. 89 | /// Log message arguments. 90 | public void Log(LogLevel logLevel, string format, params object[] args) 91 | { 92 | if (logLevel >= MinimumLogLevel) 93 | { 94 | Trace.WriteLine(args == null ? format : string.Format(format, args)); 95 | } 96 | } 97 | 98 | /// 99 | /// Logs messages to any trace listeners. 100 | /// 101 | /// Log level. 102 | /// Exception being logged. 103 | /// Log message format. 104 | /// Log message arguments. 105 | public void Log(LogLevel logLevel, Exception exception, string format, params object[] args) 106 | { 107 | if (logLevel >= MinimumLogLevel) 108 | { 109 | string output = args == null ? format : string.Format(format, args); 110 | 111 | output += ". Exception: " + exception.Message; 112 | 113 | Trace.WriteLine(output); 114 | } 115 | } 116 | 117 | #endregion Implementation of ILogger 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Slapper.AutoMapper/Slapper.AutoMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Slapper 6 | { 7 | /// 8 | /// Provides auto-mapping to static type capabilities for ORMs. Slap your ORM into submission. 9 | /// 10 | public static partial class AutoMapper 11 | { 12 | /// 13 | /// Attribute for specifying that a field or property is an identifier. 14 | /// 15 | [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property)] 16 | public class Id : Attribute 17 | { 18 | } 19 | 20 | /// 21 | /// Converts a dynamic object to a type . 22 | /// 23 | /// Population of complex nested child properties is supported by underscoring "_" into the 24 | /// nested child properties in the property name. 25 | /// 26 | /// Type to instantiate and auto-map to 27 | /// Dynamic list of property names and values 28 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 29 | /// The type 30 | /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. 31 | public static T MapDynamic( object dynamicObject, bool keepCache = true) 32 | { 33 | return (T)MapDynamic(typeof(T), dynamicObject, keepCache); 34 | } 35 | 36 | /// 37 | /// Converts a dynamic object to a specified Type. 38 | /// 39 | /// Population of complex nested child properties is supported by underscoring "_" into the 40 | /// nested child properties in the property name. 41 | /// 42 | /// Type to instantiate and auto-map to 43 | /// Dynamic list of property names and values 44 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 45 | /// The specified Type 46 | /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. 47 | public static object MapDynamic(Type type, object dynamicObject, bool keepCache = true) 48 | { 49 | if (dynamicObject == null) 50 | { 51 | return type.IsValueType ? Activator.CreateInstance(type) : null; 52 | } 53 | 54 | var dictionary = dynamicObject as IDictionary; 55 | 56 | if (dictionary == null) 57 | throw new ArgumentException("Object type cannot be converted to an IDictionary", nameof(dynamicObject)); 58 | 59 | var propertiesList = new List> { dictionary }; 60 | 61 | return Map(type, propertiesList, keepCache).FirstOrDefault(); 62 | } 63 | 64 | /// 65 | /// Converts a list of dynamic objects to a list of type of . 66 | /// 67 | /// Population of complex nested child properties is supported by underscoring "_" into the 68 | /// nested child properties in the property name. 69 | /// 70 | /// Type to instantiate and auto-map to 71 | /// Dynamic list of property names and values 72 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 73 | /// List of type 74 | /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. 75 | public static IEnumerable MapDynamic( IEnumerable dynamicListOfProperties, bool keepCache = true) 76 | { 77 | return MapDynamic(typeof(T), dynamicListOfProperties, keepCache).Cast(); 78 | } 79 | 80 | /// 81 | /// Converts a list of dynamic objects to a list of specified Type. 82 | /// 83 | /// Population of complex nested child properties is supported by underscoring "_" into the 84 | /// nested child properties in the property name. 85 | /// 86 | /// Type to instantiate and auto-map to 87 | /// Dynamic list of property names and values 88 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 89 | /// List of specified Type 90 | /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. 91 | public static IEnumerable MapDynamic(Type type, IEnumerable dynamicListOfProperties, bool keepCache = true) 92 | { 93 | if (dynamicListOfProperties == null) 94 | return new List(); 95 | 96 | var dictionary = dynamicListOfProperties.Select(dynamicItem => dynamicItem as IDictionary).ToList(); 97 | 98 | if (dictionary == null) 99 | throw new ArgumentException("Object types cannot be converted to an IDictionary", nameof(dynamicListOfProperties)); 100 | 101 | if (dictionary.Count == 0 || dictionary[0] == null) 102 | return new List(); 103 | 104 | return Map(type, dictionary, keepCache); 105 | } 106 | 107 | /// 108 | /// Converts a dictionary of property names and values to a type . 109 | /// 110 | /// Population of complex nested child properties is supported by underscoring "_" into the 111 | /// nested child properties in the property name. 112 | /// 113 | /// Type to instantiate and auto-map to 114 | /// List of property names and values 115 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 116 | /// The type 117 | public static T Map( IDictionary listOfProperties, bool keepCache = true) 118 | { 119 | return (T)Map(typeof(T), listOfProperties, keepCache); 120 | } 121 | 122 | /// 123 | /// Converts a dictionary of property names and values to a specified Type. 124 | /// 125 | /// Population of complex nested child properties is supported by underscoring "_" into the 126 | /// nested child properties in the property name. 127 | /// 128 | /// Type to instantiate and auto-map to 129 | /// List of property names and values 130 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 131 | /// The specified Type 132 | public static object Map(Type type, IDictionary listOfProperties, bool keepCache = true) 133 | { 134 | var propertiesList = new List> { listOfProperties }; 135 | 136 | return Map(type, propertiesList, keepCache).FirstOrDefault(); 137 | } 138 | 139 | /// 140 | /// Converts a list of dictionaries of property names and values to a list of type of . 141 | /// 142 | /// Population of complex nested child properties is supported by underscoring "_" into the 143 | /// nested child properties in the property name. 144 | /// 145 | /// Type to instantiate and auto-map to 146 | /// List of property names and values 147 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 148 | /// List of type 149 | public static IEnumerable Map( IEnumerable> listOfProperties, bool keepCache = true) 150 | { 151 | return Map(typeof(T), listOfProperties, keepCache).Cast(); 152 | } 153 | 154 | /// 155 | /// Converts a list of dictionaries of property names and values to a list of specified Type. 156 | /// 157 | /// Population of complex nested child properties is supported by underscoring "_" into the 158 | /// nested child properties in the property name. 159 | /// 160 | /// Type to instantiate and auto-map to 161 | /// List of property names and values 162 | /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. 163 | /// List of specified Type 164 | public static IEnumerable Map(Type type, IEnumerable> listOfProperties, bool keepCache = true) 165 | { 166 | var instanceCache = new Dictionary(); 167 | 168 | foreach (var properties in listOfProperties) 169 | { 170 | var getInstanceResult = InternalHelpers.GetInstance(type, properties); 171 | 172 | object instance = getInstanceResult.Item2; 173 | 174 | var key = getInstanceResult.Item3; 175 | 176 | if (instanceCache.ContainsKey(key) == false) 177 | { 178 | instanceCache.Add(key, instance); 179 | } 180 | 181 | var caseInsensitiveDictionary = new Dictionary(properties, StringComparer.OrdinalIgnoreCase); 182 | 183 | InternalHelpers.Map(caseInsensitiveDictionary, instance); 184 | } 185 | 186 | if (!keepCache) 187 | Cache.ClearInstanceCache(); 188 | 189 | return instanceCache.Select(pair => pair.Value); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Slapper.AutoMapper/Slapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | true 6 | Slapper.AutoMapper 7 | Randy Burden & contributors 8 | https://github.com/SlapperAutoMapper/Slapper.AutoMapper 9 | 10 | https://github.com/SlapperAutoMapper/Slapper.AutoMapper 11 | 2.0.5 12 | netstandard2.1;net47 13 | Slapper.AutoMapper.Core 14 | Copyright (c) 2016, Randy Burden and contributors. All rights reserved. 15 | Slapper.AutoMapper is a mapping library that can convert dynamic data into static types and populate complex nested child objects. 16 | Slapper.AutoMapper slapper automapper mapper map dynamic 17 | Version 2.0.5 release 18 | -Memory improvement (See Issue #83 - Thanks wegnerb) 19 | 20 | false 21 | LICENSE 22 | git 23 | 24 | 25 | 26 | 27 | True 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------