2014-10-19 2 views
3

У меня есть таблица, как:Nhibernate спасаясь специальный символ в Linq запрос

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 для поддержки?

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

ответ

2

Хорошо, это довольно вовлечен ответ и там могут быть проблемы с ним, но я смог получить оператора like с номером escape w orking.

Это включает в себя несколько шагов, но в основном мы делаем добавление нового типа HqlTreeNode, который может обрабатывать часть escape, входящую в состав оператора like.

  1. Создайте метод расширения, который вы будете использовать в запросах LINQ. Этот метод не нуждается в реализации - we'll предусматривает, что позже:

    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 расширения, которое мы определили ранее.Ответственность Этот класс должен взять информацию метод вызывается с и возвращает структуру дерева 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 мы определенных ранее. Основной недостаток этого подхода заключается в том, что мне потребовалось вручную создать ASTFactory и ASTTreeAdaptor. Использование этих классов обычно инкапсулируется внутри HqlTreeBuilder, но HqlTreeBuilder не поддается подклассу. Был бы признателен за внесенный вклад в это, если у кого-то есть совет.

  5. Создайте новый реестр генераторов LINQ to HQL. Этот класс просто просто связывает наш метод расширения с реализацией HQL мы предоставили в шаге 4:

    public class LinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry 
    { 
        public LinqToHqlGeneratorsRegistry() : base() 
        { 
         RegisterGenerator(
          ReflectionHelper.GetMethodDefinition(() => LinqExtensions.IsLikeWithEscapeChar(null, null, null)), 
          new CustomLikeGenerator()); 
        } 
    } 
    
  6. обновить конфигурацию, чтобы использовать новый LinqToHqlGeneratorsRegistry:

    cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>(); 
    
  7. (Наконец) использовать новое расширение метод в запросе:

    Обратите внимание, что вам необходимо указать символ подстановки. Это можно было бы сгладить, но это было бы непросто сделать.

Это первый раз, когда я расширил HQL таким образом, так что снова могут возникнуть проблемы с этим решением. Я смог проверить только на SQL Server, но я уверен, что он должен работать, учитывая, что он создает ту же древовидную структуру, что и запрос HQL.

+0

Nice! Ваше решение работает так, как я ожидал! Единственное изменение, которое я сделал: вместо создания нового ASTFactory я делаю это 'var factory = treeBuilder.Constant (null) .Factory', чтобы получить внутреннюю фабрику. Я не знаю, если это на 100% правильно, но это работает – Matthias

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 как выражение стиль для подчеркивания [_]

+0

Ну, это решение работает с MS-SQL, но не с базами данных Oracle. – Matthias

+0

Скорее всего да ... MS SQL-сервер поддерживает регулярные выражения, подобные этому. да ... Но, честно говоря, такого опыта с Oracle ... извините. Но вы все равно можете использовать такие методы, как ** EndsWith() '**, **' StartsWith() '**, **' Contains() '** ... и передать выражение типа Oracle как стилизованное. Это должно быть так ... –

+0

Не печально, когда я делаю что-то вроде Contains («% _ aa»), это вернет значения «val_aa» и «valaa». что неправильно. если я делаю Contains («% \\ _ aa»), ничего не возвращается вообще. Мне нужно как-то передать escape-символ на сервер – Matthias

Смежные вопросы