【问题标题】:Nhibernate escaping special character within linq queryNhibernate在linq查询中转义特殊字符
【发布时间】:2014-12-14 17:36:29
【问题描述】:

我有一张这样的桌子:

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

【问题讨论】:

    标签: c# sql linq nhibernate fluent-nhibernate


    【解决方案1】:

    好的,这是一个相当复杂的答案,很可能存在问题,但我能够让 like 操作员与 escape 一起工作。

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

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

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

      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 扩展方法创建一个生成器。这个类的职责是获取调用方法的信息并返回一个 HQL 树结构,该结构最终将转换为 SQL:

      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", '\\'))
      

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

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

    【讨论】:

    • 不错!您的解决方案按我的预期工作!我所做的唯一更改:我没有创建新的 ASTFactory,而是这样做var factory = treeBuilder.Constant(null).Factory 来获取内部工厂。如果这是 100% 正确,我不知道,但它有效
    【解决方案2】:

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

    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 样式作为下划线 [_]

    【讨论】:

    • 这个解决方案适用于 MS-SQL,但不适用于 Oracle 数据库。
    • 很可能是的... MS SQL 服务器确实支持这样的正则表达式。是的......但老实说,没有这样的甲骨文经验......对不起。但您仍然可以使用 EndsWith()StartsWith()Contains() 等方法,并传递 Oracle 风格的表达式。应该是这样的……
    • 不,遗憾的是,当我执行 Contains("%_aa") 之类的操作时,这将返回值“val_aa”和“valaa”。这是错误的。如果我这样做 Contains("%\_aa") 则根本不会返回任何内容。我需要以某种方式将转义字符传递给服务器
    • 我无法测试 Oracle,但这可以/应该工作 .EndsWith(@"_aa")) (也适用于 SQL Server)
    • 遗憾的是,这种方法返回的条目太多,因为“_”将被解释为任何字符,因此“_aa”或“1aa”或“Xaa”也将匹配标准
    猜你喜欢
    • 2012-08-23
    • 1970-01-01
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多