Skip to content

Commit f70b186

Browse files
committed
Version 2.0.3 release
- Caching bug fix and performance improvements yielded 57% mapping speed improvement - v2.0.2: Mapped 120,000 deeply nested complex objects in 30333 ms. - v2.0.3: Mapped 120,000 deeply nested complex objects in 13046 ms.
1 parent 1d3563d commit f70b186

File tree

10 files changed

+253
-287
lines changed

10 files changed

+253
-287
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2016, Randy Burden and contributors. All rights reserved.
3+
Copyright (c) 2016-2021, Randy Burden and contributors. All rights reserved.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Slapper.AutoMapper.Tests/PerformanceTests.cs

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Linq;
45
using NUnit.Framework;
@@ -43,6 +44,8 @@ public class OrderDetail
4344
/// v1.0.0.4: Mapped 50000 objects in 1683 ms.
4445
/// v1.0.0.5: Mapped 50000 objects in 1877 ms.
4546
/// 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.
4649
/// </remarks>
4750
[Test]
4851
public void Simple_Performance_Test()
@@ -73,7 +76,7 @@ public void Simple_Performance_Test()
7376
Assert.NotNull( customers );
7477
Assert.That( customers.Count() == iterations );
7578

76-
Trace.WriteLine( string.Format( "Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds ) );
79+
Console.WriteLine( string.Format( "Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds ) );
7780
}
7881

7982
/// <summary>
@@ -89,6 +92,8 @@ public void Simple_Performance_Test()
8992
/// v1.0.0.5: Mapped 50000 objects in 5896 ms.
9093
/// v1.0.0.6: Mapped 50000 objects in 5539 ms.
9194
/// 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.
9297
/// </remarks>
9398
[Test]
9499
public void Complex_Performance_Test()
@@ -125,7 +130,121 @@ public void Complex_Performance_Test()
125130
Assert.NotNull( customers );
126131
Assert.That( customers.Count() == iterations );
127132

128-
Trace.WriteLine( string.Format( "Mapped {0} objects in {1} ms.", iterations, stopwatch.ElapsedMilliseconds ) );
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<Order> 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<OrderDetail> 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+
/// <summary>
192+
/// Complex performance test mapping 120,000 objects with multiple nested child objects.
193+
/// </summary>
194+
/// <remarks>
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+
/// </remarks>
199+
[Test]
200+
public void Complex_Performance_Test()
201+
{
202+
// Arrange
203+
const int iterations = 120000;
204+
205+
var list = new List<Dictionary<string, object>>();
206+
var random = new Random();
207+
208+
for (int i = 0; i < iterations; i++)
209+
{
210+
var dictionary = new Dictionary<string, object>
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<Customer>(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));
129248
}
130249
}
131250
}

Slapper.AutoMapper.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=hashcodes/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=unicity/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Slapper.AutoMapper/CallContext.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections.Concurrent;
2-
using System.Collections.Generic;
32
using System.Threading;
43

54
namespace Slapper
@@ -10,24 +9,24 @@ namespace Slapper
109
/// </summary>
1110
internal static class CallContext
1211
{
13-
static ConcurrentDictionary<string, AsyncLocal<object>> _state = new ConcurrentDictionary<string, AsyncLocal<object>>();
12+
static readonly ConcurrentDictionary<string, AsyncLocal<object>> State = new ConcurrentDictionary<string, AsyncLocal<object>>();
1413

1514
/// <summary>
1615
/// Stores a given object and associates it with the specified name.
1716
/// </summary>
1817
/// <param name="name">The name with which to associate the new item in the call context.</param>
1918
/// <param name="data">The object to store in the call context.</param>
2019
public static void SetData(string name, object data) =>
21-
_state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
20+
State.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
2221

2322
/// <summary>
2423
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
2524
/// </summary>
2625
/// <param name="name">The name of the item in the call context.</param>
2726
/// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
2827
public static object GetData(string name) =>
29-
_state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
28+
State.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
3029

31-
public static bool FreeNamedDataSlot(string key) => _state.TryRemove(key, out AsyncLocal<object> value);
30+
public static bool FreeNamedDataSlot(string key) => State.TryRemove(key, out _);
3231
}
3332
}

Slapper.AutoMapper/Slapper.AutoMapper.Cache.cs

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,11 @@
1-
/* Slapper.AutoMapper v1.0.0.6 ( https://github.com/SlapperAutoMapper/Slapper.AutoMapper )
2-
3-
MIT License:
4-
5-
Copyright (c) 2016, Randy Burden ( http://randyburden.com ) and contributors. All rights reserved.
6-
All rights reserved.
7-
8-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9-
associated documentation files (the "Software"), to deal in the Software without restriction, including
10-
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11-
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
12-
following conditions:
13-
14-
The above copyright notice and this permission notice shall be included in all copies or substantial
15-
portions of the Software.
16-
17-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
18-
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19-
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22-
23-
Description:
24-
25-
Slapper.AutoMapper maps dynamic data to static types. Slap your data into submission!
26-
27-
Slapper.AutoMapper ( Pronounced Slapper-Dot-Automapper ) is a single file mapping library that can convert
28-
dynamic data into static types and populate complex nested child objects.
29-
It primarily converts C# dynamics and IDictionary<string, object> to strongly typed objects and supports
30-
populating an entire object graph by using underscore notation to underscore into nested objects.
31-
*/
32-
33-
using System;
1+
using System;
342
using System.Collections.Concurrent;
353
using System.Collections.Generic;
364

375
namespace Slapper
386
{
397
public static partial class AutoMapper
408
{
41-
#region Cache
42-
439
/// <summary>
4410
/// Contains the methods and members responsible for this libraries caching concerns.
4511
/// </summary>
@@ -111,7 +77,7 @@ public static void ClearInstanceCache()
11177
/// This cache exists for the lifetime of the current thread until manually cleared/purged.
11278
/// </summary>
11379
/// <remarks>
114-
/// Due to the nature of how the cache is persisted, each new thread will recieve it's own
80+
/// Due to the nature of how the cache is persisted, each new thread will receive it's own
11581
/// unique cache.
11682
/// </remarks>
11783
/// <returns>Instance Cache</returns>
@@ -129,7 +95,5 @@ public static void ClearInstanceCache()
12995
return instanceCache;
13096
}
13197
}
132-
133-
#endregion Cache
13498
}
13599
}

Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,10 @@
1-
/* Slapper.AutoMapper v1.0.0.6 ( https://github.com/SlapperAutoMapper/Slapper.AutoMapper )
2-
3-
MIT License:
4-
5-
Copyright (c) 2016, Randy Burden ( http://randyburden.com ) and contributors. All rights reserved.
6-
All rights reserved.
7-
8-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9-
associated documentation files (the "Software"), to deal in the Software without restriction, including
10-
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11-
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
12-
following conditions:
13-
14-
The above copyright notice and this permission notice shall be included in all copies or substantial
15-
portions of the Software.
16-
17-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
18-
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19-
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22-
23-
Description:
24-
25-
Slapper.AutoMapper maps dynamic data to static types. Slap your data into submission!
26-
27-
Slapper.AutoMapper ( Pronounced Slapper-Dot-Automapper ) is a single file mapping library that can convert
28-
dynamic data into static types and populate complex nested child objects.
29-
It primarily converts C# dynamics and IDictionary<string, object> to strongly typed objects and supports
30-
populating an entire object graph by using underscore notation to underscore into nested objects.
31-
*/
32-
33-
using System;
1+
using System;
342
using System.Collections.Generic;
353

364
namespace Slapper
375
{
386
public static partial class AutoMapper
397
{
40-
#region Configuration
41-
428
/// <summary>
439
/// Contains the methods and members responsible for this libraries configuration concerns.
4410
/// </summary>
@@ -55,7 +21,8 @@ static Configuration()
5521
/// <summary>
5622
/// Current version of Slapper.AutoMapper.
5723
/// </summary>
58-
public static readonly Version Version = new Version("1.0.0.6");
24+
[Obsolete("Will remove in a future version")]
25+
public static readonly Version Version = new Version("2.0.3.0");
5926

6027
/// <summary>
6128
/// The attribute Type specifying that a field or property is an identifier.
@@ -123,7 +90,11 @@ public static void AddIdentifier(Type type, string identifier)
12390
/// <param name="identifiers">Identifiers</param>
12491
public static void AddIdentifiers(Type type, IEnumerable<string> identifiers)
12592
{
126-
var typeMap = Cache.TypeMapCache.GetOrAdd(type, InternalHelpers.CreateTypeMap(type));
93+
var typeMap = Cache.TypeMapCache.GetOrAdd(type, (t) =>
94+
{
95+
// ReSharper disable once ConvertClosureToMethodGroup
96+
return InternalHelpers.CreateTypeMap(t);
97+
});
12798

12899
typeMap.Identifiers = identifiers;
129100
}
@@ -172,17 +143,17 @@ public object Convert(object value, Type type)
172143
{
173144
object convertedValue = null;
174145

175-
if (value is string)
146+
if (value is string str)
176147
{
177-
convertedValue = new Guid(value as string);
148+
convertedValue = new Guid(str);
178149
}
179-
if (value is byte[])
150+
if (value is byte[] bytes)
180151
{
181-
convertedValue = new Guid(value as byte[]);
152+
convertedValue = new Guid(bytes);
182153
}
183-
if (value is Guid)
154+
if (value is Guid guid)
184155
{
185-
convertedValue = (Guid) value;
156+
convertedValue = guid;
186157
}
187158

188159
return convertedValue;
@@ -203,7 +174,7 @@ public bool CanConvert(object value, Type type)
203174
/// <summary>
204175
/// Order to execute an <see cref="ITypeConverter"/> in.
205176
/// </summary>
206-
public int Order { get { return 100; } }
177+
public int Order => 100;
207178

208179
#endregion
209180
}
@@ -246,7 +217,7 @@ public bool CanConvert(object value, Type type)
246217
/// <summary>
247218
/// Order to execute an <see cref="ITypeConverter"/> in.
248219
/// </summary>
249-
public int Order { get { return 100; } }
220+
public int Order => 100;
250221

251222
#endregion
252223
}
@@ -288,7 +259,7 @@ public bool CanConvert(object value, Type type)
288259
/// <summary>
289260
/// Order to execute an <see cref="ITypeConverter"/> in.
290261
/// </summary>
291-
public int Order { get { return 1000; } }
262+
public int Order => 1000;
292263

293264
#endregion
294265
}
@@ -318,7 +289,5 @@ public interface ITypeActivator
318289
int Order { get; }
319290
}
320291
}
321-
322-
#endregion Configuration
323292
}
324293
}

0 commit comments

Comments
 (0)