Skip to content

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

Merged
merged 2 commits into from
Apr 13, 2018

Conversation

fredericDelaporte
Copy link
Member

Fixes #1585

.SetParameterList("regions", new HashSet<string> { "LA", "APAC" });

log.Debug("Performing query of Salespersons");
var salespersons = session.CreateQuery("from Salesperson").List();
Copy link
Member Author

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();
Copy link
Member Author

@fredericDelaporte fredericDelaporte Mar 13, 2018

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.)

@fredericDelaporte
Copy link
Member Author

TypedValue is not involved in the cases I have reproduced, but it likely represents an issue for FilterKey equality. I could add a commit in this PR for it.

{
collectionSpan = collectionValue.Count;
bindFragment = string.Join(", ", Enumerable.Repeat(typeBindFragment, collectionValue.Count).ToArray());
collectionSpan = listValue.Cast<object>().Count();
Copy link
Member

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;
}

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit amended.

Copy link
Member

@hazzik hazzik Mar 13, 2018

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.

Copy link
Member

@bahusoid bahusoid Mar 15, 2018

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.

Copy link
Member

@hazzik hazzik Mar 16, 2018

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

Copy link
Member

@hazzik hazzik Mar 16, 2018

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.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Mar 13, 2018

I could add a commit in this PR for it.

Instead done as another PR (#1612) build over this one.

@@ -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>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why non generic IEnumerable?

Copy link
Member Author

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.

Copy link
Member

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.

{
internal static class EnumerableExt
{
internal static int Count(this IEnumerable source)
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed (forced push).

@fredericDelaporte fredericDelaporte force-pushed the ParameterListHashset branch 2 times, most recently from 46ec534 to 6687043 Compare March 14, 2018 01:00
@fredericDelaporte
Copy link
Member Author

5.1.1 or 5.2? (I think this non-regression bug fix is simple enough for 5.1.1.)

@hazzik
Copy link
Member

hazzik commented Apr 11, 2018

I've made a PR: fredericDelaporte#4

image

/// 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.
Copy link
Member Author

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.

@fredericDelaporte
Copy link
Member Author

I intend to squash the last three commits in one with Alexander as co-author.

Copy link
Member

@hazzik hazzik left a 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?

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Apr 12, 2018

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.

@hazzik
Copy link
Member

hazzik commented Apr 12, 2018

Still, let's do 5.1.2.

@hazzik hazzik changed the base branch from master to 5.1.x April 12, 2018 12:46
@hazzik hazzik added this to the 5.1.2 milestone Apr 12, 2018
fredericDelaporte and others added 2 commits April 12, 2018 15:47
 * Add proper SetUp and Teardown
 * Add missing using ()
@fredericDelaporte
Copy link
Member Author

Squashed and 5.1.x rebased, waiting builds. (Well, maybe not TeamCity which has a two days long queue.)

@hazzik hazzik merged commit 56d3f3d into nhibernate:5.1.x Apr 13, 2018
@fredericDelaporte fredericDelaporte deleted the ParameterListHashset branch April 13, 2018 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants