Closed
Description
Background
While chasing down another NHibernate-related error, I came across this. This was working in 4.x (verified with 4.0.4.4000).
Description
Typically, when handling IQueryable<T>
instances, the expectation is that invoking Contains(T)
will yield a database query that is equivalent of an efficient EXISTS SQL statement. As of 5.3.3, however, the following exception is generated by the sample source code below:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: A recognition error occurred. [.Contains[UserQuery+Video, query_wpqaxa, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null](NHibernate.Linq.NhQueryable`1[UserQuery+Video], p1<VideoProxy>, )] ---> Antlr.Runtime.NoViableAltException: A recognition error occurred.
at NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.statement()
--- End of inner exception stack trace ---
at NHibernate.Hql.Ast.ANTLR.ErrorCounter.ThrowQueryException()
at NHibernate.Hql.Ast.ANTLR.HqlSqlTranslator.Translate()
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Analyze(String collectionRole)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.DoCompile(IDictionary`2 replacements, Boolean shallow, String collectionRole)
at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, IASTNode ast, String queryIdentifier, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
at NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query)
at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.Contains[TSource](IQueryable`1 source, TSource item)
Sample Source Code
void Main()
{
using (var sessionFactory = ConfigureNHibernate().BuildSessionFactory())
using (var session = sessionFactory.OpenSession())
{
var subtitle = session.Get<Subtitle>(1);
// *** The exception occurs here, regardless of whether subtitle?.Video is null or not.
if (!session.Query<Video>().Contains(subtitle?.Video))
Console.WriteLine("error");
else
Console.WriteLine("OK");
}
}
public interface IEntity
{
}
public class Video : IEntity
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual IList<Subtitle> Subtitles { get; set; }
}
public class Subtitle : IEntity
{
public virtual int Id { get; set; }
public virtual string Language { get; set; }
public virtual Video Video { get; set; }
}
bool IsEntity(Type t) => typeof(IEntity).IsAssignableFrom(t);
Configuration ConfigureNHibernate()
{
var configuration = new Configuration();
configuration
.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>())
.DataBaseIntegration(db =>
{
db.ConnectionString = null; // Provide a database connection string
db.Dialect<NHibernate.Dialect.MsSql2008Dialect>();
})
.AddAssembly(this.GetType().Assembly)
;
var mapper = new ConventionModelMapper();
mapper.IsEntity((type, declared) => IsEntity(type));
mapper.IsRootEntity((type, declared) => IsEntity(type) && !type.IsAbstract);
mapper.BeforeMapClass += (modelInspector, type, classCustomizer) =>
{
classCustomizer.Id(c => c.Generator(Generators.Identity));
classCustomizer.Table(type.Name + "s");
};
mapper.BeforeMapManyToOne += (modelInspector, propertyPath, map) =>
{
map.Column(propertyPath.LocalMember.GetPropertyOrFieldType().Name + "Fk");
map.Cascade(Cascade.Persist);
};
mapper.BeforeMapBag += (modelInspector, propertyPath, map) =>
{
map.Key(keyMapper => keyMapper.Column(propertyPath.GetContainerEntity(modelInspector).Name + "Fk"));
map.Cascade(Cascade.All);
};
var mapping = mapper.CompileMappingFor(this.GetType().Assembly.GetExportedTypes().Where(t => IsEntity(t) && !t.IsAbstract));
configuration.AddDeserializedMapping(mapping, "mapping");
return configuration;
}
Database Schema & Sample Data
CREATE TABLE [dbo].[Videos](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](512) NOT NULL,
CONSTRAINT [PK_Videos] PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Subtitles](
[Id] [int] IDENTITY(1,1) NOT NULL,
[VideoFk] [int] NOT NULL,
[Language] [nvarchar](64) NOT NULL,
CONSTRAINT [PK_Subtitles] PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Subtitles] WITH CHECK ADD CONSTRAINT [FK_Subtitles_Videos] FOREIGN KEY([VideoFk])
REFERENCES [dbo].[Videos] ([Id])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Subtitles] CHECK CONSTRAINT [FK_Subtitles_Videos]
GO
INSERT INTO [dbo].[Videos] ([Title]) VALUES ('Sample');
INSERT INTO [dbo].[Subtitles] ([VideoFk], [Language]) VALUES (SCOPE_IDENTITY(), 'Sample');