Search code examples
c#sqlnhibernatequeryover

Make custom Restriction NHibernate for QueryOver


I'm stuck in a simple problem, I'd like to add a new custom restriction in NHibernate. I want to write a simple QueryOver with a fulltext index, an example with a Projections is here How to use Full Text Search for any property with QueryOver API

But I need a more flexibility so I'd like something like

criteria = criteria.WhereRestrictionOn(() => table.COLUMN_WITHFULLTEXTINDEX).Contains(valueToCheck);

Is it possible? I'm trying in the latest to days surfing over the NHibernate source code but I could't get anything usefull.

Thanks


Solution

  • Since I must manage two different dbs dialect (SQL SERVER and ORACLE) I made the following.

    A class that has all the custom criterions, now only one for full text

    /// <summary>
    /// Full custom criterions
    /// </summary>
    public static class CustomCriterions
    {
        static CustomCriterions()
        {
            ExpressionProcessor.RegisterCustomMethodCall(() => FullTextSearch(null, ""), ProcessFullTextSearch);
        }
    
        /// <summary>
        /// Only a dummy method to force the static constructor <see cref="CustomCriterions"/>
        /// </summary>
        public static void Register()
        {
        }
    
            public static bool FullTextSearch(this string objectProperty, string valueToCheck)
            {
                throw new Exception("Not to be used directly - use inside QueryOver expression");
            }
    
            private static ICriterion ProcessFullTextSearch(MethodCallExpression mce)
            {
    
                var arg0 = ExpressionProcessor.FindMemberProjection(mce.Arguments[0]).AsProjection();
                var arg1 = ExpressionProcessor.FindMemberProjection(mce.Arguments[1]).AsProjection();
    
                var projection = Projections.SqlFunction("contains",
                                        NHibernateUtil.Boolean,
                                        arg0,
                                        arg1);
    
                return new FullTextCriterion(projection);
            }
    
    
        }
    

    Then a class who have to manage custom criterion

    /// <summary>
        /// Custom criterion to have a full text search
        /// </summary>
        public class FullTextCriterion : AbstractCriterion
        {
            private readonly IProjection _projection;
    
            public FullTextCriterion(IProjection projection)
            {
                _projection = projection;
            }
    
    
            public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
            {
                var typedValues = new List<TypedValue>();
    
                if (_projection != null)
                {
                    typedValues.AddRange(_projection.GetTypedValues(criteria, criteriaQuery));
                }
                typedValues.Add(GetParameterTypedValue(criteria, criteriaQuery));
    
                return typedValues.ToArray();
            }
    
            private TypedValue GetParameterTypedValue(ICriteria criteria, ICriteriaQuery criteriaQuery)
            {
                return CriterionUtil.GetTypedValues(criteriaQuery, criteria, _projection, null).Single();
            }
    
            public override IProjection[] GetProjections()
            {
                return new[] { _projection };
            }
    
            public override string ToString()
            {
                return _projection.ToString();
            }
    
            public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
            {
                var columnNames = CriterionUtil.GetColumnNamesForSimpleExpression(
                    null, _projection, criteriaQuery, criteria, this, string.Empty);
    
                return DbUtil.GetFullText(columnNames);
    
            }
        }
    

    FullTextCriterion isn't strictly necessary but the ORACLE syntax is

    CONTAINS (a, b)>0

    So I must to add the ">0".

    DbUtil build the syntax by the dialect, e.g. in ORACLE

    public SqlString GetFullText(SqlString[] columnNames) {

        var sqlBuilder = new SqlStringBuilder(4 * columnNames.Length);
    
        sqlBuilder.Add(columnNames[0]);
        sqlBuilder.Add("> 0");
    
        return sqlBuilder.ToSqlString();
    }
    

    Without using FullTextCriterion for ORACLE dialect I could use more simplest solutions that use custom projection instead custom criterion: NHibernate QueryOver Coalesce a property to another property

    In addition, another simplest solution is to call the template as explained here http://www.andrewwhitaker.com/blog/2014/08/15/queryover-series-part-7-using-sql-functions/ and call it directly in ProcessFullTextSearch method. In this another solution it can write only a dummy ProjectionAsCriterion class which get only

        public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            var columnNames = CriterionUtil.GetColumnNamesForSimpleExpression(
                null, _projection, criteriaQuery, criteria, this, string.Empty);
    
            return columnNames[0];
    
        }
    

    The template it could be written into the dialect class as

        RegisterFunction("fulltextsearch", new SQLFunctionTemplate(NHibernateUtil.Boolean, "contains(?1, ?2) > 0"));