MultiQuery/ToFuture broken with Contains (in)
Description
Environment
is duplicated by
is related to
testing discovered
Activity

Alex Zaytsev February 20, 2013 at 8:43 PM
It seems not

Daniel Laberge February 20, 2013 at 3:19 PM
Will this be included in 3.3.1 GA?

Alex Zaytsev June 8, 2012 at 5:26 PM
Ok, I've found shorter way to fix this issue.
Fix commited to master 92ba8462db29e5b1d547d3b8701dddede558f927

ChristopherB May 26, 2012 at 9:39 PM
Here is a work around I created in case it helps any one:
using System;
using System.Linq;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Linq;
using System.Collections.Generic;
public static class NHExtensions
{
public static IQueryable<TSource> AppendContains<TSource, TResult>(this IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector,
IEnumerable<TResult> options)
{
if (!options.Any())
throw new ArgumentException("At least one options must be specified.", "options");
Expression orExpression = null;
Expression selectorExpression = (MemberExpression)selector.Body;
foreach (var item in options)
{
Expression equalExpression = Expression.Equal(selectorExpression, Expression.Constant(item));
orExpression = orExpression == null ? equalExpression :
Expression.OrElse(orExpression, equalExpression);
}
var funcType = Expression.GetFuncType(typeof(TSource), typeof(bool));
var sourceParam = Expression.Parameter(typeof(TSource), "source");
var whereClause = (Expression<Func<TSource, bool>>)Expression.Lambda(funcType, orExpression, sourceParam);
return source.Where(whereClause);
}
}
Here is an example usage:
var users = (from u in session.Query<Users>()
where u.UserType == "Admin"
select u).AppendContains(u => user.UserId, new[]{ 1, 2, 3 });
This will generate SQL similar to SELECT ... FROM User WHERE UserType = 'Admin' AND (UserId = 1 OR UserId = 3 or UserId = 3)
I've only tested this in very limited circumstances, so use at your own risk. The downsides are that this isn't as fluent as using .Contains(), and that if you have F future queries that depend on P options, then you end up with F*P total parameters to the query (not including any other parameters that are necessary) so the number of parameters can increase quickly if you aren't careful.
Hope it helps a few people until a real fix is in place.
Chris

Daniel Laberge March 14, 2012 at 3:04 PM
@Alexander I. Zaytsev: Thanks! I will continue watching this issue as I imagine it will be marked as resolved when 3.4 ships?
Details
Details
Assignee

Reporter

There is an error in NamedParameterSpecification handling when using multiqueries or linq .ToFuture() with a query having an "in" clause.
Here's a failing unit test, put it in LinqFutureFixture.cs:
[Test]
public void CanUseToFutureWithContains()
{
using (var s = sessions.OpenSession())
{
var ids = new[] { 1, 2, 3 };
var persons10 = s.Query<Person>()
.Where(p => ids.Contains(p.Id))
.FetchMany(p => p.Children)
.Skip(5)
.Take(10)
.ToFuture().ToList();
Assert.Pass();
}
}
Stack trace:
System.Collections.Generic.KeyNotFoundException : The given key was not present in the dictionary.
at System.ThrowHelper.ThrowKeyNotFoundException()
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at NHibernate.Param.NamedParameterSpecification.SetEffectiveType(QueryParameters queryParameters) in NamedParameterSpecification.cs: line 70
at NHibernate.Param.ParametersBackTrackExtensions.ResetEffectiveExpectedType(IEnumerable`1 parameterSpecs, QueryParameters queryParameters) in ParametersBackTrackExtensions.cs: line 48
at NHibernate.Hql.Ast.ANTLR.Loader.QueryLoader.ResetEffectiveExpectedType(IEnumerable`1 parameterSpecs, QueryParameters queryParameters) in QueryLoader.cs: line 428
at NHibernate.Loader.Loader.CreateSqlCommand(QueryParameters queryParameters, ISessionImplementor session) in Loader.cs: line 1649
at NHibernate.Impl.MultiQueryImpl.AggregateQueriesInformation() in MultiQueryImpl.cs: line 641
at NHibernate.Impl.MultiQueryImpl.get_Parameters() in MultiQueryImpl.cs: line 774
at NHibernate.Impl.MultiQueryImpl.CreateCombinedQueryParameters() in MultiQueryImpl.cs: line 754
at NHibernate.Impl.MultiQueryImpl.List() in MultiQueryImpl.cs: line 400
at NHibernate.Impl.FutureQueryBatch.GetResultsFrom(IMultiQuery multiApproach) in FutureQueryBatch.cs: line 24
at NHibernate.Impl.FutureBatch`2.GetResults() in FutureBatch.cs: line 73
at NHibernate.Impl.FutureBatch`2.get_Results() in FutureBatch.cs: line 29
at NHibernate.Impl.FutureBatch`2.GetCurrentResult(Int32 currentIndex) in FutureBatch.cs: line 79
at NHibernate.Impl.FutureBatch`2.<>c_DisplayClass4`1.<GetEnumerator>b_3() in FutureBatch.cs: line 63
at NHibernate.Impl.DelayedEnumerator`1.<get_Enumerable>d__0.MoveNext() in DelayedEnumerator.cs: line 26
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList(IEnumerable`1 source)
at NHibernate.Test.NHSpecificTest.Futures.LinqFutureFixture.CanUseToFutureWithContains