-
Notifications
You must be signed in to change notification settings - Fork 933
Fix IFilter.SetParameterList not supporting HashSet<T> arguments #1611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix IFilter.SetParameterList not supporting HashSet<T> arguments #1611
Conversation
.SetParameterList("regions", new HashSet<string> { "LA", "APAC" }); | ||
|
||
log.Debug("Performing query of Salespersons"); | ||
var salespersons = session.CreateQuery("from Salesperson").List(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before the fix, this case does not throw, but convert the hashset to a single string it uses as single argument in the in
clause, causing it to fail retrieving expected results.
.SetParameterList("guids", new HashSet<Guid> { testData.Product1Guid, testData.Product2Guid }); | ||
|
||
log.Debug("Performing query of Products"); | ||
var products = session.CreateQuery("from Product").List(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before the fix, this case throws an invalid cast exception, which seems dependent on the dataprovider. (I have not reproduced the same one than #1585 report, but I have checked only with PostgreSQL.)
|
{ | ||
collectionSpan = collectionValue.Count; | ||
bindFragment = string.Join(", ", Enumerable.Repeat(typeBindFragment, collectionValue.Count).ToArray()); | ||
collectionSpan = listValue.Cast<object>().Count(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it result in full enumeration and unnecessary boxing for all struct elements?
If we still need to deal with IEnumerable interface I think it's better to add internal Count() extension:
internal static int Count(this IEnumerable source)
{
if (source is ICollection col)
return col.Count;
int count = 0;
var e = source.GetEnumerator();
//in case it's generic disposable enumerator
using (e as IDisposable)
{
while (e.MoveNext())
{
count++;
}
}
return count;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right. But I was unwilling to add this to the recently obsoleted EnumerableExtensions
utility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's obsoleted to be public. I think it's still can survive as internal helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit amended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it result in full enumeration and unnecessary boxing for all struct elements?
Not in every case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in every case.
Could you give an example? I'm looking at Cast and Count implementation and don't see any shortcuts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you give an example?
Cast
IEnumerable<T>
is co-variant. Cast
has a shortcut for it:
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) {
IEnumerable<TResult> typedSource = source as IEnumerable<TResult>;
if (typedSource != null) return typedSource;
if (source == null) throw Error.ArgumentNull("source");
return CastIterator<TResult>(source);
}
If collection is generic and T is class, cast will chose this path. CastIterator
would be involved only if collection is not generic or T is struct.
In our case we first cast ICollection<T>
to IEnumerable
then do .Cast<T>
which will do the upcast to IEnumerable<T>
without involving CastIterator
.
Count
Count has shortcuts also:
public static int Count<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator()) {
checked {
while (e.MoveNext()) count++;
}
}
return count;
}
Because we did not perform CastIterator
it will just call .Count
on our collection.
UPD: Added notice about that T needs to be referenced type.
UPD2: https://dotnetfiddle.net/KHAZBA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems I misread your comment. Indeed for structs it will always result in a cast and full enumeration.
67474d3
to
ec86c45
Compare
Instead done as another PR (#1612) build over this one. |
src/NHibernate/Impl/FilterImpl.cs
Outdated
@@ -16,6 +17,7 @@ public class FilterImpl : IFilter | |||
private FilterDefinition definition; | |||
|
|||
private readonly IDictionary<string, object> parameters = new Dictionary<string, object>(); | |||
private readonly IDictionary<string, IEnumerable> _parameterLists = new Dictionary<string, IEnumerable>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why non generic IEnumerable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is not possible to put anything more specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
System.Array
is possible. But it will require to "materialize" the incoming collection.
src/NHibernate/Util/EnumerableExt.cs
Outdated
{ | ||
internal static class EnumerableExt | ||
{ | ||
internal static int Count(this IEnumerable source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method takes precedence over standard Enumerable.Count<T>
in some cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed (forced push).
46ec534
to
6687043
Compare
5.1.1 or 5.2? (I think this non-regression bug fix is simple enough for 5.1.1.) |
I've made a PR: fredericDelaporte#4 |
src/NHibernate/Impl/FilterImpl.cs
Outdated
/// Get a span of collection parameter by name. <see langword="null" /> if the parameter is not a collectino or | ||
/// there is no such parameter exist. | ||
/// Get the span of a value list parameter by name. <see langword="null" /> if the parameter is not a value list | ||
/// or if there is no such parameter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed "collection" for "value list" for consistency with the xml comment of SetParameterList
above.
I intend to squash the last three commits in one with Alexander as co-author. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved.
The question remains the same: do we want to this to be in 5.1.x or 5.2?
Now that we have started merging 5.2 stuff in master, I am keen on putting only regressions in 5.1.x, in order to limit the likelihood of having conflicts when merging it back to master. |
Still, let's do 5.1.2. |
* Add proper SetUp and Teardown * Add missing using ()
Fixes nhibernate#1585 Co-authored-by: Alexander Zaytsev <[email protected]>
6be41cf
to
a6c4a5a
Compare
Squashed and 5.1.x rebased, waiting builds. (Well, maybe not TeamCity which has a two days long queue.) |
Fixes #1585