Nhibernate在linq查询中转义特殊字符

时间:2014-10-19 18:10:01

标签: c# sql linq nhibernate fluent-nhibernate

我有一张表:

TABLE_XY

|ID |SOURCE|
|1  |value_aa|
|2  |other_aa|
|3  |eeeaa|  

生成的查询应为:

select * from TABLE_XY where SOURCE like '%\_aa' ESCAPE '\'​

我知道有两种选择可以满足我的需求

var result = 
            session.QueryOver<TableXy>()
            .WhereRestrictionOn(x => x.Source)
            .IsLike("%\_aa", MatchMode.Exact, '\\')
            .List();

var result2 = session
     .CreateCriteria<TableXy>()
     .Add(LikeExpression("Source", "%\\_aa", MatchMode.Exact, '\\', false))
     .List(); 

但我必须使用基于Linq的实现。我正在使用动态创建的表达式树,有时将使用Linq to Object Provider或Linq to Nhibernate执行。 但目前只支持这种方法:

  var result = session
       .Query<TableXy>()
       .Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))
       .ToList();

如何扩展Nhibernate Linq Provider以支持?

SqlMethods.IsLike(string source, string pattern, char? escape);

2 个答案:

答案 0 :(得分:2)

好的,这是一个非常复杂的答案,很可能会遇到问题,但我能够让一个like运营商使用escape件。

这涉及几个步骤,但基本上我们正在做的是添加一种新的HqlTreeNode类型,可以处理escape运算符的like部分。

  1. 创建一个您将在LINQ查询中使用的扩展方法。这种方法不需要实现 - 我们稍后会提供:

    public static class LinqExtensions
    {
        public static bool IsLikeWithEscapeChar(
            this string input,
            string like,
            char? escapeChar)
        {
            throw new NotImplementedException();
        }
    }
    
  2. 创建一个HqlEscape树节点,我们将用它来表示escape运算符的like部分:

    public class HqlEscape : HqlExpression
    {
        public HqlEscape(IASTFactory factory, params HqlTreeNode[] children)
            : base(HqlSqlWalker.ESCAPE, "escape", factory, children)
        {
        }
    }
    
  3. 创建HqlLikeWithEscape树节点。默认的HqlLike节点无法处理escape部分,因此我们需要创建一个可以处理三个子节点的新节点:

    public class HqlLikeWithEscape : HqlBooleanExpression
    {
        public HqlLikeWithEscape(IASTFactory factory, HqlExpression lhs, HqlExpression rhs, HqlEscape escape)
            : base(HqlSqlWalker.LIKE, "like", factory, lhs, rhs, escape)
        {
        }
    }
    
  4. 为我们之前定义的IsLikeWithEscapeChar扩展方法创建一个生成器。这堂课&#39;责任是获取调用方法的信息并返回最终将转换为SQL的HQL树结构:

    public class CustomLikeGenerator : BaseHqlGeneratorForMethod
    {
        public CustomLikeGenerator()
        {
            this.SupportedMethods = new[]
            {
                ReflectionHelper.GetMethodDefinition(
                    () => LinqExtensions.IsLikeWithEscapeChar(null, null, null))
            };
        }
    
        public override HqlTreeNode BuildHql(
            MethodInfo method,
            System.Linq.Expressions.Expression targetObject,
            ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
            HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
        {
            // Is there a better way to do this?
            var factory = new ASTFactory(new ASTTreeAdaptor());
            HqlTreeNode escapeCharNode = visitor.Visit(arguments[2]).AsExpression();
            var escapeNode = new HqlEscape(factory, escapeCharNode);
    
            HqlLikeWithEscape likeClauseNode =
                new HqlLikeWithEscape(
                    factory,
                    visitor.Visit(arguments[0]).AsExpression(),
                    visitor.Visit(arguments[1]).AsExpression(),
                    escapeNode);
    
            return likeClauseNode;
        }
    }
    

    如您所见,我们已经使用了之前定义的新HQL树节点。这种方法的主要缺点是需要我手动创建ASTFactoryASTTreeAdaptor。这些类的使用通常封装在HqlTreeBuilder内,但HqlTreeBuilder并不适合用于子类。如果有人提出一些建议,我将不胜感激。

  5. 创建一个新的LINQ to HQL生成器注册表。这个类只是将我们的扩展方法与我们在步骤4中提供的HQL实现相关联:

    public class LinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
    {
        public LinqToHqlGeneratorsRegistry() : base()
        {
            RegisterGenerator(
                ReflectionHelper.GetMethodDefinition(() => LinqExtensions.IsLikeWithEscapeChar(null, null, null)),
                new CustomLikeGenerator());
        }
    }
    
  6. 更新您的配置以使用新的LinqToHqlGeneratorsRegistry

    cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
    
  7. (最后)在查询中使用新的扩展方法:

    session.Query<Person>().Where(p => p.FirstName.IsLikeWithEscapeChar("%Foo", '\\'))
    

    请注意,您需要指定通配符。这可以平滑,但这不会太难做。

  8. 这是我第一次以这种方式扩展HQL,这个解决方案可能会出现问题。我只能在SQL Server上进行测试,但我确信它应该可以工作,因为它创建了与HQL查询相同的树结构。

答案 1 :(得分:1)

这里的解决方案应该非常简单:

var result = session
    .Query<TableXy>()

    // instead of this
    //.Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))

    // This will add sign % at the beginning only
    .Where(x => x.Source.EndsWith("[_]aa"));
    // or wrap it on both sides with sign: % 

    .Where(x => x.Source.Contains("[_]aa"));
    .ToList();

诀窍是使用 ruglar like expression 样式作为下划线 [_]