Skip to content

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

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

Closed
dbakuntsev opened this issue Sep 16, 2020 · 6 comments · Fixed by #2674
Closed

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

dbakuntsev opened this issue Sep 16, 2020 · 6 comments · Fixed by #2674

Comments

@dbakuntsev
Copy link

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

Does it work with 5.2?

@dbakuntsev
Copy link
Author

Does it work with 5.2?

It does not work with 5.2.7. From the looks of it, this regression may have happened between major versions 4.x and 5.x.

@bahusoid
Copy link
Member

As a workaround you can use Any instead:

session.Query<Video>().Any(x => x == subtitle?.Video)

@orudge
Copy link

orudge commented Feb 8, 2021

This is something I've also identified, which worked in 4.x but fails with 5.0 or later. We are able to work around it as suggested by using .Any, but it would be good if a fix can be found.

@fredericDelaporte
Copy link
Member

We are implementing a fix for 5.3.x, but have not yet decided whether to back-port it down to 5.0.x or not, see here.

@fredericDelaporte fredericDelaporte changed the title A recognition error occurred using System.Linq.Queryable.Contains Recognition error occurs using System.Linq.Queryable.Contains Feb 14, 2021
@bahusoid bahusoid linked a pull request Feb 14, 2021 that will close this issue
@fredericDelaporte
Copy link
Member

Fixed by #2674

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants