diff --git a/src/NHibernate.Test/Async/Linq/SelectionTests.cs b/src/NHibernate.Test/Async/Linq/SelectionTests.cs index 8d3e0556cc..b031a77b5b 100644 --- a/src/NHibernate.Test/Async/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Async/Linq/SelectionTests.cs @@ -517,10 +517,31 @@ public async Task CanCastToCustomRegisteredTypeAsync() Assert.That(await (db.Users.Where(o => (NullableInt32) o.Id == 1).ToListAsync()), Has.Count.EqualTo(1)); } + [Test] + public async Task ToHashSetAsync() + { + var hashSet = await (db.Employees.Select(employee => employee.TitleOfCourtesy).ToHashSetAsync()); + Assert.That(hashSet.Count, Is.EqualTo(4)); + } + + [Test] + public async Task ToHashSet_With_ComparerAsync() + { + var hashSet = await (db.Employees.Select(employee => employee.Address).ToHashSetAsync(new AddressByCountryComparer())); + Assert.That(hashSet.Count, Is.EqualTo(2)); + } + public class Wrapper { public T item; public string message; } + + private class AddressByCountryComparer : IEqualityComparer
+ { + public bool Equals(Address x, Address y) => x?.Country == y?.Country; + + public int GetHashCode(Address obj) => obj.Country.GetHashCode(); + } } } diff --git a/src/NHibernate.Test/Linq/SelectionTests.cs b/src/NHibernate.Test/Linq/SelectionTests.cs index 6114e38c44..9b87850acd 100644 --- a/src/NHibernate.Test/Linq/SelectionTests.cs +++ b/src/NHibernate.Test/Linq/SelectionTests.cs @@ -556,10 +556,71 @@ public void CanCastToCustomRegisteredType() Assert.That(db.Users.Where(o => (NullableInt32) o.Id == 1).ToList(), Has.Count.EqualTo(1)); } + [Test] + public void ToHashSet() + { + var hashSet = db.Employees.Select(employee => employee.TitleOfCourtesy).ToHashSet(); + Assert.That(hashSet.Count, Is.EqualTo(4)); + } + + [Test] + public void ToHashSet_With_Comparer() + { + var hashSet = db.Employees.Select(employee => employee.Address).ToHashSet(new AddressByCountryComparer()); + Assert.That(hashSet.Count, Is.EqualTo(2)); + } + + [Test] + public void ToDictionary() + { + var dictionary = db.Employees.OrderBy(e => e.EmployeeId).Take(3).ToDictionary(e => e.EmployeeId); + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(3)); + + Assert.That(dictionary[1].EmployeeId, Is.EqualTo(1)); + Assert.That(dictionary[2].EmployeeId, Is.EqualTo(2)); + Assert.That(dictionary[3].EmployeeId, Is.EqualTo(3)); + }); + } + + [Test] + public void ToDictionary_With_Element_Selector() + { + var dictionary = db.Employees.OrderBy(e => e.EmployeeId).Take(3).ToDictionary(e => e.Address.PostalCode, e => e.FirstName); + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(3)); + + Assert.That(dictionary["98122"], Is.EqualTo("Nancy")); + Assert.That(dictionary["98401"], Is.EqualTo("Andrew")); + Assert.That(dictionary["98033"], Is.EqualTo("Janet")); + }); + } + + [Test] + public void ToDictionary_With_Element_Selector_And_Comparer() + { + Assert.Throws(() => db.Employees.ToDictionary(e => e.Address, e => e.FirstName, new AddressByCountryComparer()), "An item with the same key has already been added."); + } + + [Test] + public void ToDictionary_With_Comparer() + { + Assert.Throws(() => db.Employees.ToDictionary(e => e.Address, new AddressByCountryComparer()), "An item with the same key has already been added."); + } + public class Wrapper { public T item; public string message; } + + private class AddressByCountryComparer : IEqualityComparer
+ { + public bool Equals(Address x, Address y) => x?.Country == y?.Country; + + public int GetHashCode(Address obj) => obj.Country.GetHashCode(); + } } } diff --git a/src/NHibernate/Linq/LinqExtensionMethods.cs b/src/NHibernate/Linq/LinqExtensionMethods.cs index 12b84e6e18..89c024f612 100644 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -2400,6 +2400,179 @@ async Task> InternalToListAsync() #endregion + #region ToHashSetAsync + + /// + /// Executes the query and returns its result as a . + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// An to return a HashSet from. + /// A cancellation token that can be used to cancel the work. + /// The type of the elements of . + /// A task that represents the asynchronous operation. + /// The task result contains a that contains elements from the input sequence. + /// is . + /// is not a . + public static Task> ToHashSetAsync(this IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) => ToHashSetAsync(source, null, cancellationToken); + + /// + /// Executes the query and returns its result as a . + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// An to return a HashSet from. + /// The implementation to use when comparing values in the set, or null to use the default implementation for the set type. + /// A cancellation token that can be used to cancel the work. + /// The type of the elements of . + /// + /// A task that represents the asynchronous operation. + /// The task result contains a that contains elements from the input sequence. + /// + /// is . + /// is not a . + public static async Task> ToHashSetAsync(this IQueryable source, IEqualityComparer comparer, CancellationToken cancellationToken = default(CancellationToken)) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (source.Provider is not INhQueryProvider) + { + throw new NotSupportedException($"Source {nameof(source.Provider)} must be a {nameof(INhQueryProvider)}"); + } + + var hashSet = new HashSet(comparer); + foreach (var item in await source.ToListAsync(cancellationToken).ConfigureAwait(false)) + { + hashSet.Add(item); + } + + return hashSet; + } + + #endregion + + #region ToDictionaryAsync + + /// + /// Executes the query and returns its result as a according to a specified key selector function. + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// The type of the elements of + /// The type of the key returned by + /// An to return a Dictionary from. + /// A function to extract a key from each element. + /// A cancellation token that can be used to cancel the work. + /// + /// A task that represents the asynchronous operation. + /// The task result contains a that contains selected keys and values. + /// + /// is . + /// is not a . + public static Task> ToDictionaryAsync(this IQueryable source, Func keySelector, CancellationToken cancellationToken = default(CancellationToken)) + where TKey : notnull => ToDictionaryAsync(source, keySelector, e => e, comparer: null, cancellationToken); + + /// + /// Executes the query and returns its result as a according to a specified key selector function and a comparer. + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// The type of the elements of + /// The type of the key returned by + /// An to return a Dictionary from. + /// A function to extract a key from each element. + /// An to compare keys. + /// A cancellation token that can be used to cancel the work. + /// + /// A task that represents the asynchronous operation. + /// The task result contains a that contains selected keys and values. + /// + /// is . + /// is not a . + public static Task> ToDictionaryAsync(this IQueryable source, Func keySelector, IEqualityComparer comparer, CancellationToken cancellationToken = default(CancellationToken)) + where TKey : notnull => ToDictionaryAsync(source, keySelector, e => e, comparer, cancellationToken); + + /// + /// Executes the query and returns its result as a according to a specified key selector function and an element selector function. + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// The type of the elements of + /// The type of the key returned by + /// The type of the value returned by . + /// An to return a Dictionary from. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// A cancellation token that can be used to cancel the work. + /// + /// A task that represents the asynchronous operation. + /// The task result contains a that contains selected keys and values. + /// + /// is . + /// is not a . + public static Task> ToDictionaryAsync(this IQueryable source, Func keySelector, Func elementSelector, CancellationToken cancellationToken = default(CancellationToken)) + where TKey : notnull => ToDictionaryAsync(source, keySelector, elementSelector, comparer: null, cancellationToken); + + /// + /// Executes the query and returns its result as a according to a specified key selector function, a comparer, and an element selector function. + /// + /// + /// Note that this method still allocates an intermediate instance from which the result is created. + /// + /// The type of the elements of + /// The type of the key returned by + /// The type of the value returned by . + /// An to return a Dictionary from. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An to compare keys. + /// A cancellation token that can be used to cancel the work. + /// + /// A task that represents the asynchronous operation. + /// The task result contains a that contains selected keys and values. + /// + /// is . + /// is not a . + /// is . + /// is . + public static async Task> ToDictionaryAsync(this IQueryable source, Func keySelector, Func elementSelector, IEqualityComparer comparer, + CancellationToken cancellationToken = default(CancellationToken)) where TKey : notnull + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (source.Provider is not INhQueryProvider) + { + throw new NotSupportedException($"Source {nameof(source.Provider)} must be a {nameof(INhQueryProvider)}"); + } + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + var dictionary = new Dictionary(comparer); + foreach (var element in await source.ToListAsync(cancellationToken).ConfigureAwait(false)) + { + dictionary.Add(keySelector(element), elementSelector(element)); + } + + return dictionary; + } + + #endregion + /// /// Wraps the query in a deferred which enumeration will trigger a batch of all pending future queries. ///