Skip to content

Recognition error occurs using System.Linq.Queryable.Contains #2544

Closed
@dbakuntsev

Description

@dbakuntsev

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');

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions