diff --git a/Mapper/CommandExtension.cs b/Mapper/CommandExtension.cs index 504b6fc..6e7b174 100644 --- a/Mapper/CommandExtension.cs +++ b/Mapper/CommandExtension.cs @@ -109,11 +109,11 @@ public static async Task> ToDictionaryAsyncExecutes the and reads all the records in a lookup, grouped by key, using the supplied to generate the key - public static Lookup ToLookup(this IDbCommand cmd, Func keyFunc) + public static HashLookup ToLookup(this IDbCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); - Contract.Ensures(Contract.Result>() != null); + Contract.Ensures(Contract.Result>() != null); using (var reader = cmd.ExecuteReader()) { return reader.ToLookup(keyFunc); @@ -121,11 +121,11 @@ public static Lookup ToLookup(this IDbCommand cmd, F } /// Executes the and reads all the records in a lookup, grouped by key, using the supplied to generate the key - public static async Task> ToLookupAsync(this SqlCommand cmd, Func keyFunc) + public static async Task> ToLookupAsync(this SqlCommand cmd, Func keyFunc) { Contract.Requires(cmd != null); Contract.Requires(keyFunc != null); - Contract.Ensures(Contract.Result>() != null); + Contract.Ensures(Contract.Result>() != null); using (var reader = await cmd.ExecuteReaderAsync()) { return await reader.ToLookupAsync(keyFunc); diff --git a/Mapper/ConnectionExtension.cs b/Mapper/ConnectionExtension.cs index 26ecb8b..0d9a6ab 100644 --- a/Mapper/ConnectionExtension.cs +++ b/Mapper/ConnectionExtension.cs @@ -185,13 +185,13 @@ public static ILookup QueryLookup(this IDbConnection } /// Executes a command using the and reads all the records into a lookup - public static async Task> QueryLookupAsync(this SqlConnection cnn, string sql, Func keyFunc) + public static async Task> QueryLookupAsync(this SqlConnection cnn, string sql, Func keyFunc) { Contract.Requires(cnn != null); Contract.Requires(cnn.State == ConnectionState.Open); Contract.Requires(!string.IsNullOrWhiteSpace(sql)); Contract.Requires(keyFunc != null); - Contract.Ensures(Contract.Result>() != null); + Contract.Ensures(Contract.Result>() != null); using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, null); @@ -200,14 +200,14 @@ public static async Task> QueryLookupAsync(th } /// Executes a command using the and and reads all the records into a lookup - public static async Task> QueryLookupAsync(this SqlConnection cnn, string sql, object parameters, Func keyFunc) + public static async Task> QueryLookupAsync(this SqlConnection cnn, string sql, object parameters, Func keyFunc) { Contract.Requires(cnn != null); Contract.Requires(cnn.State == ConnectionState.Open); Contract.Requires(!string.IsNullOrWhiteSpace(sql)); Contract.Requires(parameters != null); Contract.Requires(keyFunc != null); - Contract.Ensures(Contract.Result>() != null); + Contract.Ensures(Contract.Result>() != null); using (var cmd = cnn.CreateCommand()) { SetupCommand(cmd, cnn, sql, parameters); diff --git a/Mapper/DataReaderExtensions.cs b/Mapper/DataReaderExtensions.cs index 2a38748..fc44a7c 100644 --- a/Mapper/DataReaderExtensions.cs +++ b/Mapper/DataReaderExtensions.cs @@ -131,13 +131,13 @@ public static async Task> ToDictionaryAsyncReads all the records in the lookup, group by key, using the supplied to generate the key - public static Lookup ToLookup(this IDataReader reader, Func keyFunc) + public static HashLookup ToLookup(this IDataReader reader, Func keyFunc) { Contract.Requires(reader != null); Contract.Requires(keyFunc != null); Contract.Requires(reader.IsClosed == false); var map = GetMappingFunc(reader); - var lookup = new Lookup(); + var lookup = new HashLookup(); while (reader.Read()) { TValue value = map(reader); @@ -148,13 +148,13 @@ public static Lookup ToLookup(this IDataReader reade } /// Reads all the records in the lookup, group by key, using the supplied to generate the key - public static async Task> ToLookupAsync(this SqlDataReader reader, Func keyFunc) + public static async Task> ToLookupAsync(this SqlDataReader reader, Func keyFunc) { Contract.Requires(reader != null); Contract.Requires(keyFunc != null); Contract.Requires(reader.IsClosed == false); var map = GetMappingFunc(reader); - var lookup = new Lookup(); + var lookup = new HashLookup(); while (await reader.ReadAsync()) { TValue value = map(reader); diff --git a/Mapper/Lookup.cs b/Mapper/HashLookup.cs similarity index 62% rename from Mapper/Lookup.cs rename to Mapper/HashLookup.cs index e5cae63..dd61fb3 100644 --- a/Mapper/Lookup.cs +++ b/Mapper/HashLookup.cs @@ -13,7 +13,7 @@ namespace Mapper /// /// A key to many value dictionary data type /// - public class Lookup : ILookup + public class HashLookup : ILookup { private static readonly TElement[] Empty = new TElement[0]; private readonly IEqualityComparer _comparer; @@ -21,17 +21,42 @@ public class Lookup : ILookup private Grouping _lastGrouping; private int _count; - public Lookup(IEqualityComparer comparer = null) + public HashLookup(IEqualityComparer comparer = null) { _comparer = comparer ?? EqualityComparer.Default; _groupings = new Grouping[7]; } + /// Gets the number of groupings (unique keys) in the . + /// Does NOT return the number of items in the lookup public int Count => _count; - public void Add(TKey key, TElement item) + /// + /// Add an to the lookup using supplied + /// + public void Add(TKey key, TElement element) { - GetGrouping(key, create: true).Add(item); + GetOrAddGrouping(key).Add(element); + } + + /// + /// Add some to the lookup using the to get the key for each element + /// + public void AddRange(IEnumerable items, Func keyFunc) + { + Contract.Requires(items != null); + Contract.Requires(keyFunc != null); + + TKey lastKey = default(TKey); + Grouping grouping = null; + foreach (var element in items) + { + var key = keyFunc(element); + if (grouping == null || _comparer.Equals(key, lastKey) == false) + grouping = GetOrAddGrouping(key); + grouping.Add(element); + lastKey = key; + } } /// Gets the sequence of values indexed by a specified key. @@ -42,14 +67,20 @@ public IEnumerable this[TKey key] { get { - Grouping grouping = GetGrouping(key, create: false); + Contract.Ensures(Contract.Result>() != null); + int hashCode = InternalGetHashCode(key); + Grouping grouping = FindGrouping(key, hashCode); return grouping ?? (IEnumerable) Empty; } } + /// + /// Determines whether a specified key exists in the . + /// public bool Contains(TKey key) { - return GetGrouping(key, create: false) != null; + int hashCode = InternalGetHashCode(key); + return FindGrouping(key, hashCode) != null; } public IEnumerator> GetEnumerator() @@ -77,20 +108,27 @@ internal int InternalGetHashCode(TKey key) return (key == null) ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; } - internal Grouping GetGrouping(TKey key, bool create) + internal Grouping GetOrAddGrouping(TKey key) { int hashCode = InternalGetHashCode(key); - for (Grouping grouping = _groupings[hashCode % _groupings.Length]; grouping != null; grouping = grouping._hashNext) + var grouping = FindGrouping(key, hashCode); + return grouping ?? AddGrouping(key, hashCode); + } + + private Grouping FindGrouping(TKey key, int hashCode) + { + for (Grouping grouping = _groupings[hashCode%_groupings.Length]; grouping != null; grouping = grouping._hashNext) { if (grouping._hashCode == hashCode && _comparer.Equals(grouping._key, key)) { return grouping; } } + return null; + } - if (!create) - return null; - + private Grouping AddGrouping(TKey key, int hashCode) + { if (_count == _groupings.Length) { Resize(); diff --git a/Mapper/Mapper.csproj b/Mapper/Mapper.csproj index 5fad352..2f6fd5d 100644 --- a/Mapper/Mapper.csproj +++ b/Mapper/Mapper.csproj @@ -230,7 +230,7 @@ - + diff --git a/Mapper/Mapper.nuspec b/Mapper/Mapper.nuspec index 31b0f4a..6e31097 100644 --- a/Mapper/Mapper.nuspec +++ b/Mapper/Mapper.nuspec @@ -2,7 +2,7 @@ Mapper - 1.0.1.0 + 1.0.1.1 BusterWood A convention-based object cloner, object-object mapper (like AutoMapper), IDataReader to object mapper, object to IDbDataParameter mapper, etc. https://github.com/busterwood/mapper diff --git a/Mapper/Properties/AssemblyInfo.cs b/Mapper/Properties/AssemblyInfo.cs index 5e68704..b327563 100644 --- a/Mapper/Properties/AssemblyInfo.cs +++ b/Mapper/Properties/AssemblyInfo.cs @@ -12,6 +12,6 @@ [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.1.0")] +[assembly: AssemblyFileVersion("1.0.1.1")] [assembly: InternalsVisibleTo("Mapper.UnitTests")] \ No newline at end of file